Class: HexaPDF::Layout::Frame
- Inherits:
-
Object
- Object
- HexaPDF::Layout::Frame
- Includes:
- Geom2D::Utils
- Defined in:
- lib/hexapdf/layout/frame.rb
Overview
A Frame describes the available space for placing boxes and provides additional methods for calculating the needed information for the actual placement.
Usage
After a Frame object is initialized, the #draw method can be used to draw a box onto frame. If drawing is successful, the next box can be drawn. Otherwise, #find_next_region should be called to determine the next region for placing the box. If the call returns true, a region was found and #draw can be tried again. Once #find_next_region returns false the frame has no more space for placing boxes.
Frame Shape and Contour Line
A frame’s shape is used to determine the available space for laying out boxes and its contour line is used whenever text should be flown around objects. They are normally the same but can differ if a box with an arbitrary contour line is drawn onto the frame.
Initially, a frame has a rectangular shape. However, once boxes are added and the frame’s available area gets reduced, a frame may have a polygon set consisting of arbitrary rectilinear polygons as shape.
In contrast to the frame’s shape its contour line may be a completely arbitrary polygon set.
Instance Attribute Summary collapse
-
#available_height ⇒ Object
readonly
The available height for placing a box.
-
#available_width ⇒ Object
readonly
The available width for placing a box.
-
#bottom ⇒ Object
readonly
The y-coordinate of the bottom-left corner.
-
#contour_line ⇒ Object
readonly
The contour line of the frame, a Geom2D::PolygonSet consisting of arbitrary polygons.
-
#height ⇒ Object
readonly
The height of the frame.
-
#left ⇒ Object
readonly
The x-coordinate of the bottom-left corner.
-
#shape ⇒ Object
readonly
The shape of the frame, a Geom2D::PolygonSet consisting of rectilinear polygons.
-
#width ⇒ Object
readonly
The width of the frame.
-
#x ⇒ Object
readonly
The x-coordinate where the next box will be placed.
-
#y ⇒ Object
readonly
The y-coordinate where the next box will be placed.
Instance Method Summary collapse
-
#draw(canvas, box) ⇒ Object
Draws the given box onto the canvas at the frame’s current position.
-
#find_next_region ⇒ Object
Finds the next region for placing boxes.
-
#initialize(left, bottom, width, height, contour_line: nil) ⇒ Frame
constructor
Creates a new Frame object for the given rectangular area.
-
#remove_area(polygon) ⇒ Object
Removes the given rectilinear polygon from both the frame’s shape and the frame’s contour line.
-
#width_specification(offset = 0) ⇒ Object
Returns a width specification for the frame’s contour line that can be used, for example, with TextLayouter.
Constructor Details
#initialize(left, bottom, width, height, contour_line: nil) ⇒ Frame
Creates a new Frame object for the given rectangular area.
If the contour line of the frame is not specified, then the rectangular area is used as contour line.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/hexapdf/layout/frame.rb', line 109 def initialize(left, bottom, width, height, contour_line: nil) @left = left @bottom = bottom @width = width @height = height @contour_line = contour_line || Geom2D::PolygonSet.new( [create_rectangle(left, bottom, left + width, bottom + height)] ) @shape = Geom2D::PolygonSet.new( [create_rectangle(left, bottom, left + width, bottom + height)] ) @x = left @y = bottom + height @available_width = width @available_height = height @region_selection = :max_height end |
Instance Attribute Details
#available_height ⇒ Object (readonly)
The available height for placing a box.
Also see the note in the #x documentation for further information.
103 104 105 |
# File 'lib/hexapdf/layout/frame.rb', line 103 def available_height @available_height end |
#available_width ⇒ Object (readonly)
The available width for placing a box.
Also see the note in the #x documentation for further information.
98 99 100 |
# File 'lib/hexapdf/layout/frame.rb', line 98 def available_width @available_width end |
#bottom ⇒ Object (readonly)
The y-coordinate of the bottom-left corner.
70 71 72 |
# File 'lib/hexapdf/layout/frame.rb', line 70 def bottom @bottom end |
#contour_line ⇒ Object (readonly)
The contour line of the frame, a Geom2D::PolygonSet consisting of arbitrary polygons.
82 83 84 |
# File 'lib/hexapdf/layout/frame.rb', line 82 def contour_line @contour_line end |
#height ⇒ Object (readonly)
The height of the frame.
76 77 78 |
# File 'lib/hexapdf/layout/frame.rb', line 76 def height @height end |
#left ⇒ Object (readonly)
The x-coordinate of the bottom-left corner.
67 68 69 |
# File 'lib/hexapdf/layout/frame.rb', line 67 def left @left end |
#shape ⇒ Object (readonly)
The shape of the frame, a Geom2D::PolygonSet consisting of rectilinear polygons.
79 80 81 |
# File 'lib/hexapdf/layout/frame.rb', line 79 def shape @shape end |
#width ⇒ Object (readonly)
The width of the frame.
73 74 75 |
# File 'lib/hexapdf/layout/frame.rb', line 73 def width @width end |
#x ⇒ Object (readonly)
The x-coordinate where the next box will be placed.
Note: Since the algorithm for #draw takes the margin of a box into account, the actual x-coordinate (and y-coordinate, available width and available height) might be different.
88 89 90 |
# File 'lib/hexapdf/layout/frame.rb', line 88 def x @x end |
#y ⇒ Object (readonly)
The y-coordinate where the next box will be placed.
Also see the note in the #x documentation for further information.
93 94 95 |
# File 'lib/hexapdf/layout/frame.rb', line 93 def y @y end |
Instance Method Details
#draw(canvas, box) ⇒ Object
Draws the given box onto the canvas at the frame’s current position. Returns true if drawing was possible, false otherwise.
When positioning the box, the style properties “position”, “position_hint” and “margin” are taken into account. Note that the margin is ignored if a box’s side coincides with the frame’s original boundary.
After a box is successfully drawn, the frame’s shape and contour line are adjusted to remove the occupied area.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/hexapdf/layout/frame.rb', line 137 def draw(canvas, box) aw = available_width ah = available_height used_margin_left = used_margin_right = used_margin_top = 0 if box.style.position != :absolute if box.style.margin? margin = box.style.margin ah -= margin.bottom unless float_equal(@y - ah, @bottom) ah -= used_margin_top = margin.top unless float_equal(@y, @bottom + @height) aw -= used_margin_right = margin.right unless float_equal(@x + aw, @left + @width) aw -= used_margin_left = margin.left unless float_equal(@x, @left) end return false unless box.fit(aw, ah, self) end width = box.width height = box.height case box.style.position when :absolute x, y = box.style.position_hint x += left y += bottom rectangle = if box.style.margin? margin = box.style.margin create_rectangle(x - margin.left, y - margin.bottom, x + width + margin.right, y + height + margin.top) else create_rectangle(x, y, x + width, y + height) end when :float x = @x + used_margin_left x += aw - width if box.style.position_hint == :right y = @y - height - used_margin_top # We can use the real margins from the box because they either have the desired effect or # just extend the rectangle outside the frame. rectangle = create_rectangle(x - (margin&.left || 0), y - (margin&.bottom || 0), x + width + (margin&.right || 0), @y) when :flow x = 0 y = @y - height rectangle = create_rectangle(left, y, left + self.width, @y) else x = case box.style.position_hint when :right @x + used_margin_left + aw - width when :center max_margin = [used_margin_left, used_margin_right].max # If we have enough space left for equal margins, we center perfectly if available_width - width >= 2 * max_margin @x + (available_width - width) / 2.0 else @x + used_margin_left + (aw - width) / 2.0 end else @x + used_margin_left end y = @y - height - used_margin_top rectangle = create_rectangle(left, y - (margin&.bottom || 0), left + self.width, @y) end box.draw(canvas, x, y) remove_area(rectangle) true end |
#find_next_region ⇒ Object
Finds the next region for placing boxes. Returns false if no useful region was found.
This method should be called after drawing a box using #draw was not successful. It finds a different region on each invocation. So if a box doesn’t fit into the first region, this method should be called again to find another region and to try again.
The first tried region starts at the top-most, left-most vertex of the polygon and uses the maximum width. The next tried region uses the maximum height. If both don’t work, part of the frame’s shape is removed to try again.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/hexapdf/layout/frame.rb', line 215 def find_next_region case @region_selection when :max_width find_max_width_region @region_selection = :max_height when :max_height x, y, aw, ah = @x, @y, @available_width, @available_height find_max_height_region if @x == x && @y == y && @available_width == aw && @available_height == ah trim_shape else @region_selection = :trim_shape end else trim_shape end available_width != 0 end |
#remove_area(polygon) ⇒ Object
Removes the given rectilinear polygon from both the frame’s shape and the frame’s contour line.
237 238 239 240 241 242 243 |
# File 'lib/hexapdf/layout/frame.rb', line 237 def remove_area(polygon) @shape = Geom2D::Algorithms::PolygonOperation.run(@shape, polygon, :difference) @contour_line = Geom2D::Algorithms::PolygonOperation.run(@contour_line, polygon, :difference) @region_selection = :max_width find_next_region end |
#width_specification(offset = 0) ⇒ Object
Returns a width specification for the frame’s contour line that can be used, for example, with TextLayouter.
Since not all text may start at the top of the frame, the offset argument can be used to specify a vertical offset from the top of the frame where layouting should start.
To be compatible with TextLayouter, the top left corner of the bounding box of the frame’s contour line is the origin of the coordinate system for the width specification, with positive x-values to the right and positive y-values downwards.
Depending on the complexity of the frame, the result may be any of the allowed width specifications of TextLayouter#fit.
257 258 259 |
# File 'lib/hexapdf/layout/frame.rb', line 257 def width_specification(offset = 0) WidthFromPolygon.new(@contour_line, offset) end |