Class: Tabula::Rectangle
- Inherits:
-
Object
- Object
- Tabula::Rectangle
- Includes:
- Comparable
- Defined in:
- lib/tabula/core/rectangle.rb
Overview
Represents a rectangle with position and dimensions. Coordinates use PDF coordinate system (origin at bottom-left).
Constant Summary collapse
- VERTICAL_COMPARISON_THRESHOLD =
Threshold for vertical overlap comparison (40% overlap)
0.4
Instance Attribute Summary collapse
-
#height ⇒ Object
Returns the value of attribute height.
-
#left ⇒ Object
Returns the value of attribute left.
-
#top ⇒ Object
Returns the value of attribute top.
-
#width ⇒ Object
Returns the value of attribute width.
Class Method Summary collapse
-
.bounding_box_of(rectangles) ⇒ Object
Compute bounding box of multiple rectangles.
-
.from_bounds(top, left, bottom, right) ⇒ Object
Create rectangle from bounds [top, left, bottom, right].
-
.from_points(p1, p2) ⇒ Object
Create rectangle from two points.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
Comparator for sorting by position (top to bottom, left to right).
- #==(other) ⇒ Object (also: #eql?)
- #area ⇒ Object
- #bottom ⇒ Object
- #bottom=(value) ⇒ Object
- #bounds ⇒ Object
- #center ⇒ Object
-
#contains?(other) ⇒ Boolean
Check if this rectangle fully contains another.
-
#contains_point?(point) ⇒ Boolean
Check if this rectangle contains a point.
- #dup ⇒ Object
- #hash ⇒ Object
-
#horizontal_overlap(other) ⇒ Object
Calculate horizontal overlap with another rectangle.
-
#horizontally_overlaps?(other, threshold = 0.0) ⇒ Boolean
Check if rectangles overlap horizontally.
-
#initialize(top, left, width, height) ⇒ Rectangle
constructor
A new instance of Rectangle.
- #inspect ⇒ Object
-
#intersection(other) ⇒ Object
Return intersection rectangle, or nil if no intersection.
-
#intersects?(other) ⇒ Boolean
Check if this rectangle intersects another.
-
#merge(other) ⇒ Object
Merge this rectangle with another, returning the bounding box.
-
#merge!(other) ⇒ Object
Merge in place.
-
#overlap_ratio(other) ⇒ Object
Calculate overlap ratio (intersection area / union area).
- #points ⇒ Object
- #right ⇒ Object
- #right=(value) ⇒ Object
- #to_s ⇒ Object
-
#vertical_overlap(other) ⇒ Object
Calculate vertical overlap with another rectangle.
-
#vertically_overlaps?(other, threshold = VERTICAL_COMPARISON_THRESHOLD) ⇒ Boolean
Check if rectangles overlap vertically.
- #x ⇒ Object
- #x=(value) ⇒ Object
- #y ⇒ Object
- #y=(value) ⇒ Object
Constructor Details
#initialize(top, left, width, height) ⇒ Rectangle
Returns a new instance of Rectangle.
12 13 14 15 16 17 |
# File 'lib/tabula/core/rectangle.rb', line 12 def initialize(top, left, width, height) @top = top.to_f @left = left.to_f @width = width.to_f @height = height.to_f end |
Instance Attribute Details
#height ⇒ Object
Returns the value of attribute height.
10 11 12 |
# File 'lib/tabula/core/rectangle.rb', line 10 def height @height end |
#left ⇒ Object
Returns the value of attribute left.
10 11 12 |
# File 'lib/tabula/core/rectangle.rb', line 10 def left @left end |
#top ⇒ Object
Returns the value of attribute top.
10 11 12 |
# File 'lib/tabula/core/rectangle.rb', line 10 def top @top end |
#width ⇒ Object
Returns the value of attribute width.
10 11 12 |
# File 'lib/tabula/core/rectangle.rb', line 10 def width @width end |
Class Method Details
.bounding_box_of(rectangles) ⇒ Object
Compute bounding box of multiple rectangles
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/tabula/core/rectangle.rb', line 34 def self.bounding_box_of(rectangles) return nil if rectangles.empty? top = rectangles.map(&:top).min left = rectangles.map(&:left).min bottom = rectangles.map(&:bottom).max right = rectangles.map(&:right).max from_bounds(top, left, bottom, right) end |
.from_bounds(top, left, bottom, right) ⇒ Object
Create rectangle from bounds [top, left, bottom, right]
20 21 22 |
# File 'lib/tabula/core/rectangle.rb', line 20 def self.from_bounds(top, left, bottom, right) new(top, left, right - left, bottom - top) end |
.from_points(p1, p2) ⇒ Object
Create rectangle from two points
25 26 27 28 29 30 31 |
# File 'lib/tabula/core/rectangle.rb', line 25 def self.from_points(p1, p2) top = [p1.y, p2.y].min left = [p1.x, p2.x].min bottom = [p1.y, p2.y].max right = [p1.x, p2.x].max from_bounds(top, left, bottom, right) end |
Instance Method Details
#<=>(other) ⇒ Object
Comparator for sorting by position (top to bottom, left to right)
209 210 211 212 213 214 |
# File 'lib/tabula/core/rectangle.rb', line 209 def <=>(other) result = top <=> other.top return result unless result.zero? left <=> other.left end |
#==(other) ⇒ Object Also known as: eql?
185 186 187 188 189 |
# File 'lib/tabula/core/rectangle.rb', line 185 def ==(other) return false unless other.is_a?(Rectangle) top == other.top && left == other.left && width == other.width && height == other.height end |
#area ⇒ Object
77 78 79 |
# File 'lib/tabula/core/rectangle.rb', line 77 def area width * height end |
#bottom ⇒ Object
45 46 47 |
# File 'lib/tabula/core/rectangle.rb', line 45 def bottom top + height end |
#bottom=(value) ⇒ Object
49 50 51 |
# File 'lib/tabula/core/rectangle.rb', line 49 def bottom=(value) @height = value - top end |
#bounds ⇒ Object
85 86 87 |
# File 'lib/tabula/core/rectangle.rb', line 85 def bounds [top, left, bottom, right] end |
#center ⇒ Object
81 82 83 |
# File 'lib/tabula/core/rectangle.rb', line 81 def center Point.new(left + (width / 2.0), top + (height / 2.0)) end |
#contains?(other) ⇒ Boolean
Check if this rectangle fully contains another
144 145 146 |
# File 'lib/tabula/core/rectangle.rb', line 144 def contains?(other) left <= other.left && right >= other.right && top <= other.top && bottom >= other.bottom end |
#contains_point?(point) ⇒ Boolean
Check if this rectangle contains a point
139 140 141 |
# File 'lib/tabula/core/rectangle.rb', line 139 def contains_point?(point) point.x.between?(left, right) && point.y >= top && point.y <= bottom end |
#dup ⇒ Object
196 197 198 |
# File 'lib/tabula/core/rectangle.rb', line 196 def dup Rectangle.new(top, left, width, height) end |
#hash ⇒ Object
192 193 194 |
# File 'lib/tabula/core/rectangle.rb', line 192 def hash [top, left, width, height].hash end |
#horizontal_overlap(other) ⇒ Object
Calculate horizontal overlap with another rectangle
104 105 106 |
# File 'lib/tabula/core/rectangle.rb', line 104 def horizontal_overlap(other) [0, [right, other.right].min - [left, other.left].max].max end |
#horizontally_overlaps?(other, threshold = 0.0) ⇒ Boolean
Check if rectangles overlap horizontally
118 119 120 121 122 123 124 125 |
# File 'lib/tabula/core/rectangle.rb', line 118 def horizontally_overlaps?(other, threshold = 0.0) overlap = horizontal_overlap(other) min_width = [width, other.width].min return true if min_width.zero? && overlap.zero? return false if min_width.zero? (overlap / min_width) > threshold end |
#inspect ⇒ Object
204 205 206 |
# File 'lib/tabula/core/rectangle.rb', line 204 def inspect to_s end |
#intersection(other) ⇒ Object
Return intersection rectangle, or nil if no intersection
174 175 176 177 178 179 180 181 182 183 |
# File 'lib/tabula/core/rectangle.rb', line 174 def intersection(other) return nil unless intersects?(other) Rectangle.from_bounds( [top, other.top].max, [left, other.left].max, [bottom, other.bottom].min, [right, other.right].min ) end |
#intersects?(other) ⇒ Boolean
Check if this rectangle intersects another
149 150 151 |
# File 'lib/tabula/core/rectangle.rb', line 149 def intersects?(other) !(other.left > right || other.right < left || other.top > bottom || other.bottom < top) end |
#merge(other) ⇒ Object
Merge this rectangle with another, returning the bounding box
154 155 156 157 158 159 160 161 |
# File 'lib/tabula/core/rectangle.rb', line 154 def merge(other) Rectangle.from_bounds( [top, other.top].min, [left, other.left].min, [bottom, other.bottom].max, [right, other.right].max ) end |
#merge!(other) ⇒ Object
Merge in place
164 165 166 167 168 169 170 171 |
# File 'lib/tabula/core/rectangle.rb', line 164 def merge!(other) merged = merge(other) @top = merged.top @left = merged.left @width = merged.width @height = merged.height self end |
#overlap_ratio(other) ⇒ Object
Calculate overlap ratio (intersection area / union area)
128 129 130 131 132 133 134 135 136 |
# File 'lib/tabula/core/rectangle.rb', line 128 def overlap_ratio(other) intersection_area = vertical_overlap(other) * horizontal_overlap(other) return 0.0 if intersection_area.zero? union_area = area + other.area - intersection_area return 0.0 if union_area.zero? intersection_area / union_area end |
#points ⇒ Object
89 90 91 92 93 94 95 96 |
# File 'lib/tabula/core/rectangle.rb', line 89 def points [ Point.new(left, top), Point.new(right, top), Point.new(right, bottom), Point.new(left, bottom) ] end |
#right ⇒ Object
53 54 55 |
# File 'lib/tabula/core/rectangle.rb', line 53 def right left + width end |
#right=(value) ⇒ Object
57 58 59 |
# File 'lib/tabula/core/rectangle.rb', line 57 def right=(value) @width = value - left end |
#to_s ⇒ Object
200 201 202 |
# File 'lib/tabula/core/rectangle.rb', line 200 def to_s "Rectangle[top=#{top}, left=#{left}, width=#{width}, height=#{height}]" end |
#vertical_overlap(other) ⇒ Object
Calculate vertical overlap with another rectangle
99 100 101 |
# File 'lib/tabula/core/rectangle.rb', line 99 def vertical_overlap(other) [0, [bottom, other.bottom].min - [top, other.top].max].max end |
#vertically_overlaps?(other, threshold = VERTICAL_COMPARISON_THRESHOLD) ⇒ Boolean
Check if rectangles overlap vertically
109 110 111 112 113 114 115 |
# File 'lib/tabula/core/rectangle.rb', line 109 def vertically_overlaps?(other, threshold = VERTICAL_COMPARISON_THRESHOLD) overlap = vertical_overlap(other) min_height = [height, other.height].min return false if min_height.zero? (overlap / min_height) >= threshold end |
#x ⇒ Object
61 62 63 |
# File 'lib/tabula/core/rectangle.rb', line 61 def x left end |
#x=(value) ⇒ Object
65 66 67 |
# File 'lib/tabula/core/rectangle.rb', line 65 def x=(value) self.left = value end |
#y ⇒ Object
69 70 71 |
# File 'lib/tabula/core/rectangle.rb', line 69 def y top end |
#y=(value) ⇒ Object
73 74 75 |
# File 'lib/tabula/core/rectangle.rb', line 73 def y=(value) self.top = value end |