/// Normalized rectangle (0–1) on the receipt photo for review highlights. library; class ReceiptBounds { const ReceiptBounds({ required this.left, required this.top, required this.right, required this.bottom, }); final double left; final double top; final double right; final double bottom; bool get isEmpty => right <= left || bottom <= top; ReceiptBounds union(ReceiptBounds other) { if (isEmpty) return other; if (other.isEmpty) return this; return ReceiptBounds( left: left < other.left ? left : other.left, top: top < other.top ? top : other.top, right: right > other.right ? right : other.right, bottom: bottom > other.bottom ? bottom : other.bottom, ); } static ReceiptBounds? merge(Iterable bounds) { ReceiptBounds? merged; for (final box in bounds) { if (box.isEmpty) continue; merged = merged == null ? box : merged.union(box); } return merged; } static ReceiptBounds fromPixelRect({ required double left, required double top, required double right, required double bottom, required double imageWidth, required double imageHeight, }) { if (imageWidth <= 0 || imageHeight <= 0) { return const ReceiptBounds(left: 0, top: 0, right: 0, bottom: 0); } return ReceiptBounds( left: left / imageWidth, top: top / imageHeight, right: right / imageWidth, bottom: bottom / imageHeight, ); } }