hexapdf 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/LICENSE +1 -1
  4. data/Rakefile +1 -1
  5. data/examples/016-frame_automatic_box_placement.rb +7 -2
  6. data/examples/017-frame_text_flow.rb +10 -18
  7. data/examples/020-column_box.rb +40 -0
  8. data/examples/021-list_box.rb +26 -0
  9. data/lib/hexapdf/cli/batch.rb +1 -1
  10. data/lib/hexapdf/cli/command.rb +1 -1
  11. data/lib/hexapdf/cli/files.rb +1 -1
  12. data/lib/hexapdf/cli/fonts.rb +1 -1
  13. data/lib/hexapdf/cli/form.rb +2 -2
  14. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  15. data/lib/hexapdf/cli/images.rb +1 -1
  16. data/lib/hexapdf/cli/info.rb +2 -2
  17. data/lib/hexapdf/cli/inspect.rb +2 -2
  18. data/lib/hexapdf/cli/merge.rb +1 -1
  19. data/lib/hexapdf/cli/modify.rb +1 -1
  20. data/lib/hexapdf/cli/optimize.rb +1 -1
  21. data/lib/hexapdf/cli/split.rb +1 -1
  22. data/lib/hexapdf/cli/watermark.rb +1 -1
  23. data/lib/hexapdf/cli.rb +1 -1
  24. data/lib/hexapdf/composer.rb +45 -126
  25. data/lib/hexapdf/configuration.rb +17 -1
  26. data/lib/hexapdf/content/canvas.rb +1 -1
  27. data/lib/hexapdf/content/color_space.rb +1 -1
  28. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +2 -1
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  32. data/lib/hexapdf/content/graphic_object.rb +1 -1
  33. data/lib/hexapdf/content/graphics_state.rb +1 -1
  34. data/lib/hexapdf/content/operator.rb +1 -1
  35. data/lib/hexapdf/content/parser.rb +1 -1
  36. data/lib/hexapdf/content/processor.rb +1 -1
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +1 -1
  39. data/lib/hexapdf/data_dir.rb +1 -1
  40. data/lib/hexapdf/dictionary.rb +1 -1
  41. data/lib/hexapdf/dictionary_fields.rb +1 -1
  42. data/lib/hexapdf/document/files.rb +1 -1
  43. data/lib/hexapdf/document/fonts.rb +1 -1
  44. data/lib/hexapdf/document/images.rb +1 -1
  45. data/lib/hexapdf/document/layout.rb +397 -0
  46. data/lib/hexapdf/document/pages.rb +17 -1
  47. data/lib/hexapdf/document/signatures.rb +5 -4
  48. data/lib/hexapdf/document.rb +8 -1
  49. data/lib/hexapdf/encryption/aes.rb +1 -1
  50. data/lib/hexapdf/encryption/arc4.rb +1 -1
  51. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  52. data/lib/hexapdf/encryption/fast_arc4.rb +30 -21
  53. data/lib/hexapdf/encryption/identity.rb +1 -1
  54. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  55. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  56. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  57. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  58. data/lib/hexapdf/encryption.rb +1 -1
  59. data/lib/hexapdf/error.rb +1 -1
  60. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  61. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  62. data/lib/hexapdf/filter/crypt.rb +1 -1
  63. data/lib/hexapdf/filter/encryption.rb +1 -1
  64. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  65. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  66. data/lib/hexapdf/filter/pass_through.rb +1 -1
  67. data/lib/hexapdf/filter/predictor.rb +1 -1
  68. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  69. data/lib/hexapdf/filter.rb +1 -1
  70. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  71. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  72. data/lib/hexapdf/font/cmap.rb +1 -1
  73. data/lib/hexapdf/font/encoding/base.rb +1 -1
  74. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  75. data/lib/hexapdf/font/encoding/glyph_list.rb +2 -2
  76. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  77. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  79. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  82. data/lib/hexapdf/font/encoding.rb +1 -1
  83. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  84. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  85. data/lib/hexapdf/font/true_type/font.rb +1 -1
  86. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  87. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  88. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  89. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  90. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  91. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  92. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  93. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  94. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  95. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  96. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  97. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  98. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  99. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table.rb +1 -1
  102. data/lib/hexapdf/font/true_type.rb +1 -1
  103. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  104. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  105. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  106. data/lib/hexapdf/font/type1/font.rb +1 -1
  107. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  108. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  109. data/lib/hexapdf/font/type1.rb +1 -1
  110. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  111. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  112. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  113. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  114. data/lib/hexapdf/font_loader.rb +1 -1
  115. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  116. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  117. data/lib/hexapdf/image_loader/png.rb +1 -1
  118. data/lib/hexapdf/image_loader.rb +1 -1
  119. data/lib/hexapdf/importer.rb +1 -1
  120. data/lib/hexapdf/layout/box.rb +121 -22
  121. data/lib/hexapdf/layout/box_fitter.rb +136 -0
  122. data/lib/hexapdf/layout/column_box.rb +247 -0
  123. data/lib/hexapdf/layout/frame.rb +155 -139
  124. data/lib/hexapdf/layout/image_box.rb +19 -4
  125. data/lib/hexapdf/layout/inline_box.rb +1 -1
  126. data/lib/hexapdf/layout/line.rb +1 -1
  127. data/lib/hexapdf/layout/list_box.rb +355 -0
  128. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  129. data/lib/hexapdf/layout/style.rb +5 -1
  130. data/lib/hexapdf/layout/text_box.rb +20 -9
  131. data/lib/hexapdf/layout/text_fragment.rb +3 -2
  132. data/lib/hexapdf/layout/text_layouter.rb +17 -2
  133. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  134. data/lib/hexapdf/layout/width_from_polygon.rb +12 -7
  135. data/lib/hexapdf/layout.rb +4 -1
  136. data/lib/hexapdf/name_tree_node.rb +1 -1
  137. data/lib/hexapdf/number_tree_node.rb +1 -1
  138. data/lib/hexapdf/object.rb +1 -1
  139. data/lib/hexapdf/parser.rb +1 -8
  140. data/lib/hexapdf/pdf_array.rb +1 -1
  141. data/lib/hexapdf/rectangle.rb +1 -1
  142. data/lib/hexapdf/reference.rb +1 -1
  143. data/lib/hexapdf/revision.rb +1 -1
  144. data/lib/hexapdf/revisions.rb +1 -1
  145. data/lib/hexapdf/serializer.rb +1 -1
  146. data/lib/hexapdf/stream.rb +1 -1
  147. data/lib/hexapdf/task/dereference.rb +1 -1
  148. data/lib/hexapdf/task/optimize.rb +1 -1
  149. data/lib/hexapdf/task.rb +1 -1
  150. data/lib/hexapdf/tokenizer.rb +1 -1
  151. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  152. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  153. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  154. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  155. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  156. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  157. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  158. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  159. data/lib/hexapdf/type/acro_form.rb +1 -1
  160. data/lib/hexapdf/type/action.rb +1 -1
  161. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  162. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  163. data/lib/hexapdf/type/actions/launch.rb +1 -1
  164. data/lib/hexapdf/type/actions/uri.rb +1 -1
  165. data/lib/hexapdf/type/actions.rb +1 -1
  166. data/lib/hexapdf/type/annotation.rb +1 -1
  167. data/lib/hexapdf/type/annotations/link.rb +1 -1
  168. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  169. data/lib/hexapdf/type/annotations/text.rb +1 -1
  170. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  171. data/lib/hexapdf/type/annotations.rb +1 -1
  172. data/lib/hexapdf/type/catalog.rb +1 -1
  173. data/lib/hexapdf/type/cid_font.rb +1 -1
  174. data/lib/hexapdf/type/embedded_file.rb +1 -1
  175. data/lib/hexapdf/type/file_specification.rb +1 -1
  176. data/lib/hexapdf/type/font.rb +1 -1
  177. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  178. data/lib/hexapdf/type/font_simple.rb +1 -1
  179. data/lib/hexapdf/type/font_true_type.rb +1 -1
  180. data/lib/hexapdf/type/font_type0.rb +1 -1
  181. data/lib/hexapdf/type/font_type1.rb +1 -1
  182. data/lib/hexapdf/type/font_type3.rb +1 -1
  183. data/lib/hexapdf/type/form.rb +1 -1
  184. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  185. data/lib/hexapdf/type/icon_fit.rb +1 -1
  186. data/lib/hexapdf/type/image.rb +1 -1
  187. data/lib/hexapdf/type/info.rb +1 -1
  188. data/lib/hexapdf/type/names.rb +1 -1
  189. data/lib/hexapdf/type/object_stream.rb +1 -1
  190. data/lib/hexapdf/type/page.rb +1 -1
  191. data/lib/hexapdf/type/page_tree_node.rb +19 -2
  192. data/lib/hexapdf/type/resources.rb +1 -1
  193. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  194. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -1
  195. data/lib/hexapdf/type/signature/handler.rb +1 -1
  196. data/lib/hexapdf/type/signature/verification_result.rb +1 -1
  197. data/lib/hexapdf/type/signature.rb +1 -1
  198. data/lib/hexapdf/type/trailer.rb +2 -2
  199. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  200. data/lib/hexapdf/type/xref_stream.rb +1 -1
  201. data/lib/hexapdf/type.rb +1 -1
  202. data/lib/hexapdf/utils/bit_field.rb +1 -1
  203. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  204. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  205. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  206. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/object_hash.rb +1 -1
  208. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  209. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  210. data/lib/hexapdf/version.rb +2 -2
  211. data/lib/hexapdf/writer.rb +9 -7
  212. data/lib/hexapdf/xref_section.rb +1 -1
  213. data/lib/hexapdf.rb +1 -1
  214. data/test/hexapdf/content/graphic_object/test_geom2d.rb +1 -1
  215. data/test/hexapdf/document/test_destinations.rb +1 -1
  216. data/test/hexapdf/document/test_images.rb +1 -1
  217. data/test/hexapdf/document/test_layout.rb +264 -0
  218. data/test/hexapdf/document/test_pages.rb +9 -0
  219. data/test/hexapdf/document/test_signatures.rb +10 -3
  220. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  221. data/test/hexapdf/font/encoding/test_glyph_list.rb +4 -0
  222. data/test/hexapdf/layout/test_box.rb +53 -3
  223. data/test/hexapdf/layout/test_box_fitter.rb +62 -0
  224. data/test/hexapdf/layout/test_column_box.rb +159 -0
  225. data/test/hexapdf/layout/test_frame.rb +99 -38
  226. data/test/hexapdf/layout/test_image_box.rb +1 -1
  227. data/test/hexapdf/layout/test_list_box.rb +249 -0
  228. data/test/hexapdf/layout/test_text_box.rb +17 -2
  229. data/test/hexapdf/layout/test_text_fragment.rb +1 -1
  230. data/test/hexapdf/layout/test_text_layouter.rb +42 -17
  231. data/test/hexapdf/layout/test_width_from_polygon.rb +13 -0
  232. data/test/hexapdf/test_composer.rb +11 -0
  233. data/test/hexapdf/test_dictionary_fields.rb +9 -9
  234. data/test/hexapdf/test_document.rb +4 -4
  235. data/test/hexapdf/test_filter.rb +1 -1
  236. data/test/hexapdf/test_parser.rb +0 -2
  237. data/test/hexapdf/test_revisions.rb +2 -2
  238. data/test/hexapdf/test_serializer.rb +1 -5
  239. data/test/hexapdf/test_writer.rb +58 -3
  240. data/test/hexapdf/type/test_page_tree_node.rb +21 -1
  241. data/test/hexapdf/type/test_trailer.rb +3 -3
  242. data/test/test_helper.rb +5 -1
  243. metadata +28 -3
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -75,53 +75,72 @@ module HexaPDF
75
75
  # fitting, splitting or drawing a box. Note that the margin is ignored if a box's side coincides
76
76
  # with the frame's original boundary.
77
77
  #
78
- # == Frame Shape and Contour Line
78
+ # == Frame Shape
79
79
  #
80
- # A frame's shape is used to determine the available space for laying out boxes and its contour
81
- # line is used whenever text should be flown around objects. They are normally the same but can
82
- # differ if a box with an arbitrary contour line is drawn onto the frame.
80
+ # A frame's shape is used to determine the available space for laying out boxes.
83
81
  #
84
82
  # Initially, a frame has a rectangular shape. However, once boxes are added and the frame's
85
83
  # available area gets reduced, a frame may have a polygon set consisting of arbitrary
86
84
  # rectilinear polygons as shape.
87
85
  #
88
- # In contrast to the frame's shape its contour line may be a completely arbitrary polygon set.
86
+ # It is also possible to provide a different initial shape on initialization.
89
87
  class Frame
90
88
 
91
89
  include Geom2D::Utils
92
90
 
93
- # Internal class for storing data of a fitted box.
94
- class FitData
91
+ # Stores the result of fitting a box in a Frame.
92
+ class FitResult
95
93
 
96
94
  # The box that was fitted into the frame.
97
95
  attr_accessor :box
98
96
 
99
- # The available width for this particular box.
97
+ # The horizontal position where the box will be drawn.
98
+ attr_accessor :x
99
+
100
+ # The vertical position where the box will be drawn.
101
+ attr_accessor :y
102
+
103
+ # The available width in the frame for this particular box.
100
104
  attr_accessor :available_width
101
105
 
102
- # The available height for this particular box.
106
+ # The available height in the frame for this particular box.
103
107
  attr_accessor :available_height
104
108
 
105
- # The left margin to use instead of +box.style.margin.left+.
106
- attr_accessor :margin_left
109
+ # The rectangle (a Geom2D::Polygon object) that will be removed from the frame when drawing
110
+ # the box.
111
+ attr_accessor :mask
107
112
 
108
- # The right margin to use instead of +box.style.margin.right+.
109
- attr_accessor :margin_right
113
+ # Initialize the result object for the given box.
114
+ def initialize(box)
115
+ @box = box
116
+ @available_width = 0
117
+ @available_height = 0
118
+ @success = false
119
+ end
110
120
 
111
- # The top margin to use instead of +box.style.margin.top+.
112
- attr_accessor :margin_top
121
+ # Marks the fitting status as success.
122
+ def success!
123
+ @success = true
124
+ end
113
125
 
114
- # Initialize the object by calling #reset.
115
- def initialize
116
- reset
126
+ # Returns +true+ if fitting was successful.
127
+ def success?
128
+ @success
117
129
  end
118
130
 
119
- # Resets the object.
120
- def reset(box = nil, available_width = 0, available_height = 0)
121
- @box = box
122
- @available_width = available_width
123
- @available_height = available_height
124
- @margin_left = @margin_right = @margin_top = 0
131
+ # Draws the #box onto the canvas at (#x, #y).
132
+ #
133
+ # The configuration option "debug" can be used to add visual debug output with respect to
134
+ # box placement.
135
+ def draw(canvas)
136
+ if canvas.context.document.config['debug']
137
+ canvas.save_graphics_state do
138
+ canvas.fill_color("green").stroke_color("darkgreen").
139
+ opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
140
+ draw(:geom2d, object: mask, path_only: true).fill_stroke
141
+ end
142
+ end
143
+ box.draw(canvas, x, y)
125
144
  end
126
145
 
127
146
  end
@@ -163,130 +182,129 @@ module HexaPDF
163
182
  attr_reader :available_height
164
183
 
165
184
  # Creates a new Frame object for the given rectangular area.
166
- def initialize(left, bottom, width, height, contour_line: nil)
185
+ def initialize(left, bottom, width, height, shape: nil)
167
186
  @left = left
168
187
  @bottom = bottom
169
188
  @width = width
170
189
  @height = height
171
- @contour_line = contour_line
172
- @shape = Geom2D::PolygonSet.new(
190
+ @shape = shape || Geom2D::PolygonSet.new(
173
191
  [create_rectangle(left, bottom, left + width, bottom + height)]
174
192
  )
175
193
  @x = left
176
194
  @y = bottom + height
177
195
  @available_width = width
178
196
  @available_height = height
197
+
198
+ find_max_width_region if shape
179
199
  @region_selection = :max_height
180
- @fit_data = FitData.new
181
200
  end
182
201
 
183
- # Fits the given box into the current region of available space.
202
+ # Fits the given box into the current region of available space and returns a FitResult
203
+ # object.
204
+ #
205
+ # Use the FitResult#success? method to determine whether fitting was successful.
184
206
  def fit(box)
185
- aw = available_width
186
- ah = available_height
187
- @fit_data.reset(box, aw, ah)
207
+ fit_result = FitResult.new(box)
208
+ return fit_result if full?
209
+
210
+ position = if box.style.position != :flow || box.supports_position_flow?
211
+ box.style.position
212
+ else
213
+ :default
214
+ end
188
215
 
189
- if full?
190
- false
191
- elsif box.style.position == :absolute
216
+ if position == :absolute
192
217
  x, y = box.style.position_hint
193
- box.fit(width - x, height - y, self)
194
- true
195
- else
196
- if box.style.margin?
197
- margin = box.style.margin
198
- ah -= margin.bottom unless float_equal(@y - ah, @bottom)
199
- ah -= @fit_data.margin_top = margin.top unless float_equal(@y, @bottom + @height)
200
- aw -= @fit_data.margin_right = margin.right unless float_equal(@x + aw, @left + @width)
201
- aw -= @fit_data.margin_left = margin.left unless float_equal(@x, @left)
202
- @fit_data.available_width = aw
203
- @fit_data.available_height = ah
204
- end
205
218
 
219
+ aw = width - x
220
+ ah = height - y
206
221
  box.fit(aw, ah, self)
207
- end
208
- end
209
-
210
- # Tries to split the (fitted) box into two parts, where the first part needs to fit into the
211
- # available space, and returns both parts.
212
- #
213
- # If the given box is not the last fitted box, #fit is called before splitting the box.
214
- #
215
- # See Box#split for further details.
216
- def split(box)
217
- fit(box) unless box == @fit_data.box
218
- boxes = box.split(@fit_data.available_width, @fit_data.available_height, self)
219
- @fit_data.reset unless boxes[0] == @fit_data.box
220
- boxes
221
- end
222
-
223
- # Draws the given (fitted) box onto the canvas at the frame's current position. Returns +true+
224
- # if drawing was possible, +false+ otherwise.
225
- #
226
- # If the given box is not the last fitted box, #fit is called before drawing the box.
227
- #
228
- # After a box is successfully drawn, the frame's shape and contour line are adjusted to remove
229
- # the occupied area.
230
- def draw(canvas, box)
231
- unless box == @fit_data.box
232
- fit(box) || return
233
- end
222
+ fit_result.success!
234
223
 
235
- width = box.width
236
- height = box.height
237
- margin = box.style.margin if box.style.margin?
238
-
239
- if height == 0
240
- @fit_data.reset
241
- return true
242
- end
243
-
244
- case box.style.position
245
- when :absolute
246
- x, y = box.style.position_hint
247
224
  x += left
248
225
  y += bottom
249
226
  rectangle = if box.style.margin?
227
+ margin = box.style.margin
250
228
  create_rectangle(x - margin.left, y - margin.bottom,
251
- x + width + margin.right, y + height + margin.top)
229
+ x + box.width + margin.right, y + box.height + margin.top)
252
230
  else
253
- create_rectangle(x, y, x + width, y + height)
231
+ create_rectangle(x, y, x + box.width, y + box.height)
254
232
  end
255
- when :flow
256
- x = 0
257
- y = @y - height
258
- rectangle = create_rectangle(left, y, left + self.width, @y)
259
233
  else
260
- x = case box.style.position_hint
261
- when :right
262
- @x + @fit_data.margin_left + @fit_data.available_width - width
263
- when :center
264
- max_margin = [@fit_data.margin_left, @fit_data.margin_right].max
265
- # If we have enough space left for equal margins, we center perfectly
266
- if available_width - width >= 2 * max_margin
267
- @x + (available_width - width) / 2.0
268
- else
269
- @x + @fit_data.margin_left + (@fit_data.available_width - width) / 2.0
234
+ aw = available_width
235
+ ah = available_height
236
+
237
+ margin_top = margin_right = margin_left = 0
238
+ if box.style.margin?
239
+ margin = box.style.margin
240
+ aw -= margin_right = margin.right unless float_equal(@x + aw, @left + @width)
241
+ aw -= margin_left = margin.left unless float_equal(@x, @left)
242
+ ah -= margin.bottom unless float_equal(@y - ah, @bottom)
243
+ ah -= margin_top = margin.top unless float_equal(@y, @bottom + @height)
244
+ end
245
+
246
+ fit_result.success! if box.fit(aw, ah, self)
247
+
248
+ width = box.width
249
+ height = box.height
250
+
251
+ case position
252
+ when :flow
253
+ x = 0
254
+ y = @y - height
255
+ rectangle = create_rectangle(left, [bottom, y - (margin&.bottom || 0)].max,
256
+ left + self.width, @y)
257
+ else
258
+ x = case box.style.position_hint
259
+ when nil, :left
260
+ @x + margin_left
261
+ when :right
262
+ @x + margin_left + aw - width
263
+ when :center
264
+ max_margin = [margin_left, margin_right].max
265
+ # If we have enough space left for equal margins, we center perfectly
266
+ if available_width - width >= 2 * max_margin
267
+ @x + (available_width - width) / 2.0
268
+ else
269
+ @x + margin_left + (aw - width) / 2.0
270
+ end
270
271
  end
271
- else
272
- @x + @fit_data.margin_left
273
- end
274
- y = @y - height - @fit_data.margin_top
275
- rectangle = if box.style.position == :float
276
- # We use the real margins from the box because they either have the desired
277
- # effect or just extend the rectangle outside the frame.
278
- create_rectangle(x - (margin&.left || 0), y - (margin&.bottom || 0),
279
- x + width + (margin&.right || 0), @y)
280
- else
281
- create_rectangle(left, y - (margin&.bottom || 0), left + self.width, @y)
282
- end
272
+ y = @y - height - margin_top
273
+ rectangle = if position == :float
274
+ create_rectangle([left, x - (margin&.left || 0)].max,
275
+ [bottom, y - (margin&.bottom || 0)].max,
276
+ [left + self.width, x + width + (margin&.right || 0)].min,
277
+ @y)
278
+ else
279
+ create_rectangle(left, [bottom, y - (margin&.bottom || 0)].max,
280
+ left + self.width, @y)
281
+ end
282
+ end
283
283
  end
284
284
 
285
- box.draw(canvas, x, y)
286
- remove_area(rectangle)
287
- @fit_data.reset
285
+ fit_result.available_width = aw
286
+ fit_result.available_height = ah
287
+ fit_result.x = x
288
+ fit_result.y = y
289
+ fit_result.mask = rectangle
290
+ fit_result
291
+ end
292
+
293
+ # Tries to split the box of the given FitResult into two parts and returns both parts.
294
+ #
295
+ # See Box#split for further details.
296
+ def split(fit_result)
297
+ fit_result.box.split(fit_result.available_width, fit_result.available_height, self)
298
+ end
288
299
 
289
- true
300
+ # Draws the box of the given FitResult onto the canvas at the fitted position.
301
+ #
302
+ # After a box is successfully drawn, the frame's shape is adjusted to remove the occupied
303
+ # area.
304
+ def draw(canvas, fit_result)
305
+ return if fit_result.box.height == 0 || fit_result.box.width == 0
306
+ fit_result.draw(canvas)
307
+ remove_area(fit_result.mask)
290
308
  end
291
309
 
292
310
  # Finds the next region for placing boxes. Returns +false+ if no useful region was found.
@@ -315,18 +333,12 @@ module HexaPDF
315
333
  trim_shape
316
334
  end
317
335
 
318
- @fit_data.reset
319
336
  available_width != 0
320
337
  end
321
338
 
322
- # Removes the given *rectilinear* polygon from both the frame's shape and the frame's contour
323
- # line.
339
+ # Removes the given *rectilinear* polygon from the frame's shape.
324
340
  def remove_area(polygon)
325
341
  @shape = Geom2D::Algorithms::PolygonOperation.run(@shape, polygon, :difference)
326
- if @contour_line
327
- @contour_line = Geom2D::Algorithms::PolygonOperation.run(@contour_line, polygon,
328
- :difference)
329
- end
330
342
  @region_selection = :max_width
331
343
  find_next_region
332
344
  end
@@ -336,25 +348,20 @@ module HexaPDF
336
348
  available_width == 0
337
349
  end
338
350
 
339
- # The contour line of the frame, a Geom2D::PolygonSet consisting of arbitrary polygons.
340
- def contour_line
341
- @contour_line || @shape
342
- end
343
-
344
- # Returns a width specification for the frame's contour line that can be used, for example,
345
- # with TextLayouter.
351
+ # Returns a width specification for the frame's shape that can be used, for example, with
352
+ # TextLayouter.
346
353
  #
347
354
  # Since not all text may start at the top of the frame, the offset argument can be used to
348
355
  # specify a vertical offset from the top of the frame where layouting should start.
349
356
  #
350
357
  # To be compatible with TextLayouter, the top left corner of the bounding box of the frame's
351
- # contour line is the origin of the coordinate system for the width specification, with
352
- # positive x-values to the right and positive y-values downwards.
358
+ # shape is the origin of the coordinate system for the width specification, with positive
359
+ # x-values to the right and positive y-values downwards.
353
360
  #
354
361
  # Depending on the complexity of the frame, the result may be any of the allowed width
355
362
  # specifications of TextLayouter#fit.
356
363
  def width_specification(offset = 0)
357
- WidthFromPolygon.new(contour_line, offset)
364
+ WidthFromPolygon.new(shape, offset)
358
365
  end
359
366
 
360
367
  private
@@ -402,9 +409,18 @@ module HexaPDF
402
409
  # Just use the second top-most segment
403
410
  # TODO: not the optimal solution!
404
411
  index = segments.rindex {|s| s.start_point.y < @y }
405
- y = segments[index].start_point.y
406
- remove_area(Geom2D::Polygon([left, y], [left + width, y],
407
- [left + width, @y], [left, @y]))
412
+ segment = segments[index]
413
+ y = segment.start_point.y
414
+ polygon = if segment.min.x == @x
415
+ # Trim the rectangular part from the left to the segment's length
416
+ Geom2D::Polygon([@x, @y], [@x, y],
417
+ [@x + segment.length, y], [@x + segment.length, @y])
418
+ else
419
+ # Trim the whole slice between the two top-most segments
420
+ Geom2D::Polygon([left, y], [left + width, y],
421
+ [left + width, @y], [left, @y])
422
+ end
423
+ remove_area(polygon)
408
424
  end
409
425
 
410
426
  # Finds and sets the top-left point for the next region. This is always the top-most,
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -40,13 +40,27 @@ module HexaPDF
40
40
 
41
41
  # An Image box object is used for displaying an image.
42
42
  #
43
+ # It can either be used directly or through the HexaPDF::Composer#image method.
44
+ #
43
45
  # How an image is displayed inside an image box, depends on whether the +width+ and/or +height+
44
46
  # of the box has been set:
45
47
  #
46
48
  # * If one of them has been set, the other is adjusted to retain the image ratio.
49
+ #
50
+ # #>pdf-composer100
51
+ # composer.image(machu_picchu, width: 40)
52
+ # composer.image(machu_picchu, height: 40)
53
+ #
47
54
  # * If both have been set, both are used as is.
55
+ #
56
+ # #>pdf-composer100
57
+ # composer.image(machu_picchu, width: 100, height: 30)
58
+ #
48
59
  # * If neither has been set, the image is scaled to fit the available space.
49
60
  #
61
+ # #>pdf-composer100
62
+ # composer.image(machu_picchu)
63
+ #
50
64
  # Also see: HexaPDF::Content::Canvas#image
51
65
  class ImageBox < Box
52
66
 
@@ -55,13 +69,14 @@ module HexaPDF
55
69
 
56
70
  # Creates a new Image box object for the given +image+ argument which needs to be an image
57
71
  # object (e.g. returned by HexaPDF::Document::Images#add).
58
- def initialize(image, **kwargs)
72
+ def initialize(image:, **kwargs)
59
73
  super(**kwargs)
60
74
  @image = image
61
75
  end
62
76
 
63
- # Fits the image into the available space.
64
- def fit(available_width, available_height, _)
77
+ # Fits the image into the available space, taking the initially set width and height into
78
+ # account (see the class description for details).
79
+ def fit(available_width, available_height, _frame)
65
80
  image_width = @image.width.to_f
66
81
  image_height = @image.height.to_f
67
82
  image_ratio = image_width / image_height
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as