Class: Pdfrb::Layout::PolygonFrame

Inherits:
Frame
  • Object
show all
Defined in:
lib/pdfrb/layout/polygon_frame.rb

Overview

Polygon-aware Frame: extends Frame to compute available area against an arbitrary polygon shape, not just a rectangle. Useful for L-shaped text regions, text-around-image, and irregular page layouts.

The default Frame.find_available_area always returns the top-left rectangle; this subclass finds the largest inscribed rectangle of (width × height) inside the polygon, scanning row-by-row to find the leftmost x where the requested box fits at the requested height.

Constant Summary collapse

DEFAULT_STEP =
1.0

Instance Attribute Summary collapse

Attributes inherited from Frame

#bottom, #height, #left, #shape, #width

Instance Method Summary collapse

Methods inherited from Frame

#empty?, #full?, #remove_area, #right, #top

Constructor Details

#initialize(left:, bottom:, width:, height:, polygon: nil, step: DEFAULT_STEP) ⇒ PolygonFrame

Returns a new instance of PolygonFrame.

Parameters:

  • polygon (Array<Array<Numeric>>) (defaults to: nil)

    array of [x, y] vertex pairs. The polygon is implicitly closed.

  • step (Numeric) (defaults to: DEFAULT_STEP)

    scan resolution in PDF units; smaller steps find tighter fits but take longer.



24
25
26
27
28
29
30
# File 'lib/pdfrb/layout/polygon_frame.rb', line 24

def initialize(left:, bottom:, width:, height:, polygon: nil, step: DEFAULT_STEP)
  super(left: left, bottom: bottom, width: width, height: height)
  @polygon = polygon || [[left, bottom], [left + width, bottom],
                         [left + width, bottom + height],
                         [left, bottom + height]]
  @step = step.to_f
end

Instance Attribute Details

#polygonObject (readonly)

Returns the value of attribute polygon.



18
19
20
# File 'lib/pdfrb/layout/polygon_frame.rb', line 18

def polygon
  @polygon
end

Instance Method Details

#bounding_box_bottomObject



65
66
67
# File 'lib/pdfrb/layout/polygon_frame.rb', line 65

def bounding_box_bottom
  @polygon.map { |_x, y| y }.min.to_f
end

#bounding_box_leftObject



69
70
71
# File 'lib/pdfrb/layout/polygon_frame.rb', line 69

def bounding_box_left
  @polygon.map { |x, _y| x }.min.to_f
end

#bounding_box_rightObject



73
74
75
# File 'lib/pdfrb/layout/polygon_frame.rb', line 73

def bounding_box_right
  @polygon.map { |x, _y| x }.max.to_f
end

#bounding_box_topObject



61
62
63
# File 'lib/pdfrb/layout/polygon_frame.rb', line 61

def bounding_box_top
  @polygon.map { |_x, y| y }.max.to_f
end

#contains_point?(x, y) ⇒ Boolean

Point-in-polygon test (ray casting).

Returns:

  • (Boolean)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/pdfrb/layout/polygon_frame.rb', line 78

def contains_point?(x, y)
  inside = false
  n = @polygon.length
  i = 0
  j = n - 1
  while i < n
    xi, yi = @polygon[i]
    xj, yj = @polygon[j]
    if ((yi > y) != (yj > y)) &&
        (x < ((xj - xi) * (y - yi) / (yj - yi)) + xi)
      inside = !inside
    end
    j = i
    i += 1
  end
  inside
end

#contains_rectangle?(x, y, w, h) ⇒ Boolean

Returns:

  • (Boolean)


96
97
98
99
100
101
102
103
# File 'lib/pdfrb/layout/polygon_frame.rb', line 96

def contains_rectangle?(x, y, w, h)
  [
    [x, y],
    [x + w, y],
    [x, y + h],
    [x + w, y + h],
  ].all? { |px, py| contains_point?(px, py) }
end

#find_available_area(width, height) ⇒ Object

Find the next available area inside the polygon that fits (width, height). Walks row by row from the top of the bounding box downward; at each row y, scans x from left toward the right edge, returning the first (x, y, w, h) position where all four corners of (x, y - h, w, h) are inside the polygon.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/pdfrb/layout/polygon_frame.rb', line 38

def find_available_area(width, height)
  w = width.to_f
  h = height.to_f
  return nil unless w.positive? && h.positive?

  bbox_top = bounding_box_top
  bbox_bottom = bounding_box_bottom
  bbox_left = bounding_box_left
  bbox_right = bounding_box_right

  y = bbox_top
  while y - h >= bbox_bottom
    x = bbox_left
    while x + w <= bbox_right
      return [x, y, w, h] if contains_rectangle?(x, y - h, w, h)

      x += @step
    end
    y -= @step
  end
  nil
end