hexapdf 0.32.2 → 0.33.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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/lib/hexapdf/cli/command.rb +5 -2
  19. data/lib/hexapdf/cli/form.rb +5 -5
  20. data/lib/hexapdf/cli/inspect.rb +3 -3
  21. data/lib/hexapdf/composer.rb +104 -52
  22. data/lib/hexapdf/configuration.rb +44 -39
  23. data/lib/hexapdf/content/canvas.rb +393 -267
  24. data/lib/hexapdf/content/color_space.rb +72 -25
  25. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  26. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  27. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  28. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  29. data/lib/hexapdf/content/graphic_object.rb +6 -7
  30. data/lib/hexapdf/content/graphics_state.rb +54 -45
  31. data/lib/hexapdf/content/operator.rb +52 -54
  32. data/lib/hexapdf/content/parser.rb +2 -2
  33. data/lib/hexapdf/content/processor.rb +15 -15
  34. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  35. data/lib/hexapdf/content.rb +5 -0
  36. data/lib/hexapdf/dictionary.rb +6 -5
  37. data/lib/hexapdf/dictionary_fields.rb +42 -14
  38. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  39. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  40. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  41. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  42. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  43. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  44. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  45. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  46. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  47. data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
  48. data/lib/hexapdf/digital_signature.rb +7 -2
  49. data/lib/hexapdf/document/destinations.rb +12 -11
  50. data/lib/hexapdf/document/files.rb +1 -1
  51. data/lib/hexapdf/document/fonts.rb +1 -1
  52. data/lib/hexapdf/document/layout.rb +167 -39
  53. data/lib/hexapdf/document/pages.rb +3 -2
  54. data/lib/hexapdf/document.rb +89 -55
  55. data/lib/hexapdf/encryption/aes.rb +5 -5
  56. data/lib/hexapdf/encryption/arc4.rb +1 -1
  57. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  58. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  59. data/lib/hexapdf/encryption/identity.rb +1 -1
  60. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  61. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  63. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  64. data/lib/hexapdf/encryption.rb +7 -2
  65. data/lib/hexapdf/error.rb +18 -0
  66. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  67. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  68. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  69. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  70. data/lib/hexapdf/filter/pass_through.rb +1 -1
  71. data/lib/hexapdf/filter/predictor.rb +1 -1
  72. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  73. data/lib/hexapdf/filter.rb +55 -6
  74. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  75. data/lib/hexapdf/font/cmap.rb +1 -1
  76. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  77. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  79. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  82. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  83. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  84. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  85. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  86. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  87. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  88. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  89. data/lib/hexapdf/font_loader.rb +3 -0
  90. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  91. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  92. data/lib/hexapdf/image_loader/png.rb +2 -2
  93. data/lib/hexapdf/image_loader.rb +1 -1
  94. data/lib/hexapdf/importer.rb +13 -0
  95. data/lib/hexapdf/layout/box.rb +9 -2
  96. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  97. data/lib/hexapdf/layout/column_box.rb +18 -4
  98. data/lib/hexapdf/layout/frame.rb +30 -12
  99. data/lib/hexapdf/layout/image_box.rb +5 -0
  100. data/lib/hexapdf/layout/inline_box.rb +1 -0
  101. data/lib/hexapdf/layout/list_box.rb +17 -1
  102. data/lib/hexapdf/layout/page_style.rb +4 -4
  103. data/lib/hexapdf/layout/style.rb +18 -3
  104. data/lib/hexapdf/layout/table_box.rb +682 -0
  105. data/lib/hexapdf/layout/text_box.rb +5 -3
  106. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  107. data/lib/hexapdf/layout/text_layouter.rb +12 -4
  108. data/lib/hexapdf/layout.rb +1 -0
  109. data/lib/hexapdf/name_tree_node.rb +1 -1
  110. data/lib/hexapdf/number_tree_node.rb +1 -1
  111. data/lib/hexapdf/object.rb +18 -7
  112. data/lib/hexapdf/parser.rb +7 -7
  113. data/lib/hexapdf/pdf_array.rb +1 -1
  114. data/lib/hexapdf/rectangle.rb +1 -1
  115. data/lib/hexapdf/reference.rb +1 -1
  116. data/lib/hexapdf/revision.rb +1 -1
  117. data/lib/hexapdf/revisions.rb +3 -3
  118. data/lib/hexapdf/serializer.rb +15 -15
  119. data/lib/hexapdf/stream.rb +4 -2
  120. data/lib/hexapdf/tokenizer.rb +14 -14
  121. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  122. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  123. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  124. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  125. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  127. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  128. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  129. data/lib/hexapdf/type/acro_form.rb +1 -1
  130. data/lib/hexapdf/type/action.rb +1 -1
  131. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  132. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  133. data/lib/hexapdf/type/actions/launch.rb +1 -1
  134. data/lib/hexapdf/type/actions/uri.rb +1 -1
  135. data/lib/hexapdf/type/actions.rb +1 -1
  136. data/lib/hexapdf/type/annotation.rb +3 -3
  137. data/lib/hexapdf/type/annotations/link.rb +1 -1
  138. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  139. data/lib/hexapdf/type/annotations/text.rb +1 -1
  140. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  141. data/lib/hexapdf/type/annotations.rb +1 -1
  142. data/lib/hexapdf/type/catalog.rb +1 -1
  143. data/lib/hexapdf/type/cid_font.rb +3 -3
  144. data/lib/hexapdf/type/embedded_file.rb +1 -1
  145. data/lib/hexapdf/type/file_specification.rb +2 -2
  146. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  147. data/lib/hexapdf/type/font_simple.rb +2 -2
  148. data/lib/hexapdf/type/font_type0.rb +3 -3
  149. data/lib/hexapdf/type/font_type3.rb +1 -1
  150. data/lib/hexapdf/type/form.rb +1 -1
  151. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  152. data/lib/hexapdf/type/icon_fit.rb +1 -1
  153. data/lib/hexapdf/type/image.rb +1 -1
  154. data/lib/hexapdf/type/info.rb +1 -1
  155. data/lib/hexapdf/type/mark_information.rb +1 -1
  156. data/lib/hexapdf/type/names.rb +2 -2
  157. data/lib/hexapdf/type/object_stream.rb +2 -1
  158. data/lib/hexapdf/type/outline.rb +1 -1
  159. data/lib/hexapdf/type/outline_item.rb +1 -1
  160. data/lib/hexapdf/type/page.rb +19 -10
  161. data/lib/hexapdf/type/page_label.rb +1 -1
  162. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  163. data/lib/hexapdf/type/resources.rb +1 -1
  164. data/lib/hexapdf/type/trailer.rb +2 -2
  165. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  166. data/lib/hexapdf/type/xref_stream.rb +2 -2
  167. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  168. data/lib/hexapdf/version.rb +1 -1
  169. data/lib/hexapdf/writer.rb +4 -4
  170. data/lib/hexapdf/xref_section.rb +2 -2
  171. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  172. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  173. data/test/hexapdf/content/test_canvas.rb +0 -1
  174. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  175. data/test/hexapdf/document/test_files.rb +2 -2
  176. data/test/hexapdf/document/test_layout.rb +98 -0
  177. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  178. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  179. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  180. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  181. data/test/hexapdf/layout/test_box.rb +1 -1
  182. data/test/hexapdf/layout/test_column_box.rb +65 -21
  183. data/test/hexapdf/layout/test_frame.rb +14 -14
  184. data/test/hexapdf/layout/test_image_box.rb +4 -0
  185. data/test/hexapdf/layout/test_inline_box.rb +5 -0
  186. data/test/hexapdf/layout/test_list_box.rb +40 -6
  187. data/test/hexapdf/layout/test_page_style.rb +3 -2
  188. data/test/hexapdf/layout/test_style.rb +50 -0
  189. data/test/hexapdf/layout/test_table_box.rb +722 -0
  190. data/test/hexapdf/layout/test_text_box.rb +18 -0
  191. data/test/hexapdf/layout/test_text_layouter.rb +4 -0
  192. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  193. data/test/hexapdf/test_document.rb +1 -0
  194. data/test/hexapdf/test_filter.rb +8 -0
  195. data/test/hexapdf/test_importer.rb +9 -0
  196. data/test/hexapdf/test_object.rb +16 -5
  197. data/test/hexapdf/test_stream.rb +7 -0
  198. data/test/hexapdf/test_writer.rb +3 -3
  199. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  200. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  201. data/test/hexapdf/type/test_page.rb +18 -4
  202. metadata +17 -8
@@ -48,6 +48,9 @@ module HexaPDF
48
48
  # Represents a single glyph of the wrapped font.
49
49
  class Glyph
50
50
 
51
+ # The associated font object.
52
+ attr_reader :font
53
+
51
54
  # The name of the glyph.
52
55
  attr_reader :name
53
56
  alias id name
@@ -164,6 +167,20 @@ module HexaPDF
164
167
  end
165
168
  end
166
169
 
170
+ # Returns a custom Glyph object which represents the given +string+ via the given glyph
171
+ # +name+.
172
+ #
173
+ # This functionality can be used to associate a single glyph name with multiple, different
174
+ # strings for replacement glyph purposes. When used in such a way, the used glyph name is
175
+ # often :question.
176
+ def custom_glyph(name, string)
177
+ unless @wrapped_font.metrics.character_metrics.key?(name)
178
+ raise HexaPDF::Error, "Glyph named #{name.inspect} not found in " \
179
+ "font '#{@wrapped_font.full_name}'"
180
+ end
181
+ Glyph.new(@wrapped_font, name, string)
182
+ end
183
+
167
184
  # Returns an array of glyph objects representing the characters in the UTF-8 encoded string.
168
185
  #
169
186
  # If a Unicode codepoint is not available as glyph object, it is tried to map the codepoint
@@ -188,9 +205,7 @@ module HexaPDF
188
205
  def encode(glyph)
189
206
  @encoded_glyphs[glyph.name] ||=
190
207
  begin
191
- if glyph.name == @wrapped_font.missing_glyph_id
192
- raise HexaPDF::Error, "Glyph for #{glyph.str.inspect} missing"
193
- end
208
+ raise HexaPDF::MissingGlyphError.new(glyph) if glyph.kind_of?(InvalidGlyph)
194
209
  code = @encoding.code(glyph.name)
195
210
  if code
196
211
  code.chr.freeze
@@ -199,7 +214,7 @@ module HexaPDF
199
214
  @encoding.code_to_name[@max_code] = glyph.name
200
215
  @max_code.chr.freeze
201
216
  else
202
- raise HexaPDF::Error, "Type1 encoding has no codepoint for #{glyph.name}"
217
+ raise HexaPDF::Error, "Used Type1 encoding has no codepoint for #{glyph.name.inspect}"
203
218
  end
204
219
  end
205
220
  end
@@ -43,13 +43,14 @@ module HexaPDF
43
43
  # This module uses the configuration option 'font.map' for loading a font.
44
44
  module FromConfiguration
45
45
 
46
- # Loads the given font by looking up the needed file in the 'font.map' configuration option.
46
+ # Returns a TrueType font wrapper for the given font by looking up the needed file in the
47
+ # 'font.map' configuration option.
47
48
  #
48
49
  # The file object representing the font file is *not* closed and if needed must be closed by
49
50
  # the caller once the font is not needed anymore.
50
51
  #
51
52
  # +document+::
52
- # The PDF document to associate the font object with.
53
+ # The PDF document to associate the font wrapper with.
53
54
  #
54
55
  # +name+::
55
56
  # The name of the font.
@@ -59,6 +60,8 @@ module HexaPDF
59
60
  #
60
61
  # +subset+::
61
62
  # Specifies whether the font should be subset if possible.
63
+ #
64
+ # This method uses the FromFile font loader behind the scenes.
62
65
  def self.call(document, name, variant: :none, subset: true)
63
66
  file = document.config['font.map'].dig(name, variant)
64
67
  return nil if file.nil?
@@ -39,15 +39,15 @@ require 'hexapdf/font/true_type_wrapper'
39
39
  module HexaPDF
40
40
  module FontLoader
41
41
 
42
- # This module interprets the font name either as file name and tries to load it, or as font
43
- # object to be wrapped directly.
42
+ # This module interprets the font name either as file name and tries to load it, or as TrueType
43
+ # font object to be wrapped directly.
44
44
  module FromFile
45
45
 
46
46
  # :call-seq:
47
47
  # FromFile.call(document, file_name, subset: true, **) -> wrapped_font
48
48
  # FromFile.call(document, font_object, subset: true, **) -> wrapped_font
49
49
  #
50
- # Returns an appropriate font wrapper for the given file name or font object.
50
+ # Returns an appropriate font wrapper for the given file name or TrueType font object.
51
51
  #
52
52
  # If a file name is given, the file object representing the font file is *not* closed and if
53
53
  # needed must be closed by the caller once the font is not needed anymore.
@@ -57,10 +57,10 @@ module HexaPDF
57
57
  # font file.
58
58
  #
59
59
  # +document+::
60
- # The PDF document to associate the font object with.
60
+ # The PDF document to associate the font wrapper with.
61
61
  #
62
62
  # +file_name+/+font_object+::
63
- # The file name or TrueType font object.
63
+ # The file name or a HexaPDF::Font::TrueType::Font object.
64
64
  #
65
65
  # +subset+::
66
66
  # Specifies whether the font should be subset if possible.
@@ -71,10 +71,10 @@ module HexaPDF
71
71
  },
72
72
  }.freeze
73
73
 
74
- # Creates a new font object backed by the AFM font metrics read from the file or IO stream.
74
+ # Returns a font wrapper for the named Standard PDF font.
75
75
  #
76
76
  # +document+::
77
- # The PDF document to associate the font object with.
77
+ # The PDF document to associate the font wrapper with.
78
78
  #
79
79
  # +name+::
80
80
  # The name of the built-in font. One of Times, Helvetica, Courier, Symbol or ZapfDingbats.
@@ -85,7 +85,7 @@ module HexaPDF
85
85
  #
86
86
  # +custom_encoding+::
87
87
  # For Times, Helvetica and Courier the standard encoding WinAnsiEncoding is used. If this
88
- # option is not wanted because access to other glyphs is needed, set this to +true+
88
+ # is not wanted because access to other glyphs is needed, set this to +true+
89
89
  def self.call(document, name, variant: :none, custom_encoding: false, **)
90
90
  name = MAPPING[name] && MAPPING[name][variant]
91
91
  return nil if name.nil?
@@ -63,6 +63,7 @@ module HexaPDF
63
63
  # Optionally, a font loader can provide a method +available_fonts(document)+ that returns a hash
64
64
  # where the keys are the font names and the values are the variants of all the provided fonts.
65
65
  #
66
+ #
66
67
  # == Font Wrappers
67
68
  #
68
69
  # A font wrapper needs to provide the following generic interface so that it can be used correctly
@@ -80,6 +81,8 @@ module HexaPDF
80
81
  # and returns an encoded string that can be decoded with the font dictionary returned by
81
82
  # \#dict.
82
83
  #
84
+ # HexaPDF contains a font wrapper implementation for the Standard 14 PDF fonts (see
85
+ # HexaPDF::Font::Type1Wrapper) and one for TrueType fonts (see HexaPDF::Font::TrueTypeWrapper).
83
86
  module FontLoader
84
87
 
85
88
  autoload(:Standard14, 'hexapdf/font_loader/standard14')
@@ -41,7 +41,7 @@ module HexaPDF
41
41
 
42
42
  # This module is used for loading images in the JPEG format from files or IO streams.
43
43
  #
44
- # See: PDF1.7 s7.4.8, ITU T.81 Annex B, ITU T.872
44
+ # See: PDF2.0 s7.4.8, ITU T.81 Annex B, ITU T.872
45
45
  module JPEG
46
46
 
47
47
  # The magic marker that tells us if the file/IO contains an image in JPEG format.
@@ -139,7 +139,7 @@ module HexaPDF
139
139
  break if components != 4 || invert_colors
140
140
  end
141
141
 
142
- # PDF1.7 s8.9.5.1
142
+ # PDF2.0 s8.9.5.1
143
143
  if bits != 8
144
144
  raise HexaPDF::Error, "Unsupported number of bits per component: #{bits}"
145
145
  end
@@ -46,7 +46,7 @@ module HexaPDF
46
46
  # image/xobject drawing methods of HexaPDF::Content::Canvas know how to handle them correctly so
47
47
  # that this doesn't matter from a user's point of view.
48
48
  #
49
- # See: PDF1.7 s8.10
49
+ # See: PDF2.0 s8.10
50
50
  module PDF
51
51
 
52
52
  # The magic marker that tells us if the file/IO contains an PDF file.
@@ -52,7 +52,7 @@ module HexaPDF
52
52
  #
53
53
  # All PNG specification section references are in reference to http://www.w3.org/TR/PNG/.
54
54
  #
55
- # See: PDF1.7 s7.4.4., s8.9
55
+ # See: PDF2.0 s7.4.4., s8.9
56
56
  class PNG
57
57
 
58
58
  # The magic marker that tells us if the file/IO contains an image in PNG format.
@@ -261,7 +261,7 @@ module HexaPDF
261
261
  # Returns a hash for a CalRGB color space definition using the x,y chromaticity coordinates
262
262
  # of the white point and the red, green and blue primaries.
263
263
  #
264
- # See: PDF1.7 s8.6.5.3
264
+ # See: PDF2.0 s8.6.5.3
265
265
  def calrgb_definition_from_chrm(xw, yw, xr, yr, xg, yg, xb, yb)
266
266
  z = yw * ((xg - xb) * yr - (xr - xb) * yg + (xr - xg) * yb)
267
267
 
@@ -59,7 +59,7 @@ module HexaPDF
59
59
  # The image XObject may use any implemented filter. For example, an image loader for JPEG files
60
60
  # would typically use the DCTDecode filter instead of decoding the image itself.
61
61
  #
62
- # See: PDF1.7 s8.9
62
+ # See: PDF2.0 s8.9
63
63
  module ImageLoader
64
64
 
65
65
  autoload(:JPEG, 'hexapdf/image_loader/jpeg')
@@ -68,6 +68,19 @@ module HexaPDF
68
68
  @map[destination.hash] ||= new(destination)
69
69
  end
70
70
 
71
+ # Imports the given +object+ (belonging to the +source+ document) by completely copying it and
72
+ # all referenced objects into the +destination+ object.
73
+ #
74
+ # Specifying +source+ is optionial if it can be determined through +object+.
75
+ #
76
+ # After the operation is finished, all state is discarded. This means that another call to this
77
+ # method for the same object will yield a new - and different - object. This is in contrast to
78
+ # using ::for together with #import which remembers and returns already imported objects (which
79
+ # is generally what one wants).
80
+ def self.copy(destination, object, source: nil)
81
+ new(NullableWeakRef.new(destination)).import(object, source: source)
82
+ end
83
+
71
84
  private_class_method :new
72
85
 
73
86
  attr_reader :destination #:nodoc:
@@ -60,18 +60,25 @@ module HexaPDF
60
60
  # instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
61
61
  # facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
62
62
  #
63
- # The methods #fit, #split or #split_content, and #draw or #draw_content need to be customized
64
- # according to the subclass's use case.
63
+ # The methods #fit, #supports_position_flow?, #split or #split_content, #empty?, and #draw or
64
+ # #draw_content need to be customized according to the subclass's use case.
65
65
  #
66
66
  # #fit:: This method should return +true+ if fitting was successful. Additionally, the
67
67
  # @fit_successful instance variable needs to be set to the fit result as it is used in
68
68
  # #split.
69
69
  #
70
+ # #supports_position_flow?::
71
+ # If the subclass supports the value :flow of the 'position' style property, this method
72
+ # needs to be overridden to return +true+.
73
+ #
70
74
  # #split:: This method splits the content so that the available space is used as good as
71
75
  # possible. The default implementation should be fine for most use-cases, so only
72
76
  # #split_content needs to be implemented. The method #create_split_box should be used
73
77
  # for getting a basic cloned box.
74
78
  #
79
+ # #empty?:: This method should return +true+ if the subclass won't draw anything when #draw is
80
+ # called.
81
+ #
75
82
  # #draw:: This method draws the content and the default implementation already handles things
76
83
  # like drawing the border and background. Therefore it's best to implement #draw_content
77
84
  # which should just draw the content.
@@ -98,7 +98,7 @@ module HexaPDF
98
98
  if result.success?
99
99
  current_frame.remove_area(result.mask)
100
100
  @content_heights[@frame_index] = [@content_heights[@frame_index],
101
- @initial_frame_y[@frame_index] - result.mask[0].y].max
101
+ @initial_frame_y[@frame_index] - result.mask.y].max
102
102
  @fit_results << result
103
103
  box = nil
104
104
  break
@@ -109,7 +109,7 @@ module HexaPDF
109
109
  if draw_box
110
110
  current_frame.remove_area(result.mask)
111
111
  @content_heights[@frame_index] = [@content_heights[@frame_index],
112
- @initial_frame_y[@frame_index] - result.mask[0].y].max
112
+ @initial_frame_y[@frame_index] - result.mask.y].max
113
113
  @fit_results << result
114
114
  elsif !current_frame.find_next_region
115
115
  @frame_index += 1
@@ -62,8 +62,9 @@ module HexaPDF
62
62
 
63
63
  # The columns definition.
64
64
  #
65
- # This is an array containing the widths of the columns. The size of the array is the number
66
- # of columns.
65
+ # If the value is an array, it needs to contain the widths of the columns. The size of the
66
+ # array determines the number of columns. Otherwise, if the value is an integer, the value
67
+ # defines the number of equally sized columns, i.e. a value of +N+ is equal to [-1]*N.
67
68
  #
68
69
  # If a negative integer is used for the width, the column is auto-sized. Such columns split
69
70
  # the remaining width (after substracting the widths of the fixed columns) proportionally
@@ -132,6 +133,11 @@ module HexaPDF
132
133
  true
133
134
  end
134
135
 
136
+ # Returns +true+ if no box was fitted into the columns.
137
+ def empty?
138
+ super && (!@box_fitter || @box_fitter.fit_results.empty?)
139
+ end
140
+
135
141
  # Fits the column box into the available space.
136
142
  #
137
143
  # If the style property 'position' is set to :flow, the columns might not be rectangles but
@@ -199,6 +205,8 @@ module HexaPDF
199
205
 
200
206
  @width = columns[-1].sum + reserved_width
201
207
  @height = @box_fitter.content_heights.max + reserved_height
208
+ @draw_pos_x = frame.x + reserved_width_left
209
+ @draw_pos_y = frame.y - @height + reserved_height_bottom
202
210
 
203
211
  @box_fitter.fit_successful?
204
212
  end
@@ -237,8 +245,14 @@ module HexaPDF
237
245
  end
238
246
 
239
247
  # Draws the child boxes onto the canvas at position [x, y].
240
- def draw_content(canvas, _x, _y)
241
- @box_fitter.fit_results.each {|result| result.draw(canvas) }
248
+ def draw_content(canvas, x, y)
249
+ if style.position != :flow && (x != @draw_pos_x || y != @draw_pos_y)
250
+ canvas.translate(x - @draw_pos_x, y - @draw_pos_y) do
251
+ @box_fitter.fit_results.each {|result| result.draw(canvas) }
252
+ end
253
+ else
254
+ @box_fitter.fit_results.each {|result| result.draw(canvas) }
255
+ end
242
256
  end
243
257
 
244
258
  end
@@ -106,8 +106,8 @@ module HexaPDF
106
106
  # The available height in the frame for this particular box.
107
107
  attr_accessor :available_height
108
108
 
109
- # The rectangle (a Geom2D::Polygon object) that will be removed from the frame when drawing
110
- # the box.
109
+ # The rectangle (a Geom2D::Rectangle object) that will be removed from the frame when
110
+ # drawing the box.
111
111
  attr_accessor :mask
112
112
 
113
113
  # Initialize the result object for the given box.
@@ -157,7 +157,8 @@ module HexaPDF
157
157
  # The height of the frame.
158
158
  attr_reader :height
159
159
 
160
- # The shape of the frame, a Geom2D::PolygonSet consisting of rectilinear polygons.
160
+ # The shape of the frame, either a Geom2D::Rectangle in the simple case or a
161
+ # Geom2D::PolygonSet consisting of rectilinear polygons in the more complex case.
161
162
  attr_reader :shape
162
163
 
163
164
  # The x-coordinate where the next box will be placed.
@@ -187,9 +188,8 @@ module HexaPDF
187
188
  @bottom = bottom
188
189
  @width = width
189
190
  @height = height
190
- @shape = shape || Geom2D::PolygonSet.new(
191
- [create_rectangle(left, bottom, left + width, bottom + height)]
192
- )
191
+ @shape = shape || create_rectangle(left, bottom, left + width, bottom + height)
192
+
193
193
  @x = left
194
194
  @y = bottom + height
195
195
  @available_width = width
@@ -319,8 +319,16 @@ module HexaPDF
319
319
  def find_next_region
320
320
  case @region_selection
321
321
  when :max_width
322
- find_max_width_region
323
- @region_selection = :max_height
322
+ if @shape.kind_of?(Geom2D::Rectangle)
323
+ @x = @shape.x
324
+ @y = @shape.y + @shape.height
325
+ @available_width = @shape.width
326
+ @available_height = @shape.height
327
+ @region_selection = :trim_shape
328
+ else
329
+ find_max_width_region
330
+ @region_selection = :max_height
331
+ end
324
332
  when :max_height
325
333
  x, y, aw, ah = @x, @y, @available_width, @available_height
326
334
  find_max_height_region
@@ -338,7 +346,17 @@ module HexaPDF
338
346
 
339
347
  # Removes the given *rectilinear* polygon from the frame's shape.
340
348
  def remove_area(polygon)
341
- @shape = Geom2D::Algorithms::PolygonOperation.run(@shape, polygon, :difference)
349
+ @shape = if @shape.kind_of?(Geom2D::Rectangle) && polygon.kind_of?(Geom2D::Rectangle) &&
350
+ float_equal(@shape.x, polygon.x) && float_equal(@shape.width, polygon.width) &&
351
+ float_equal(@shape.y + @shape.height, polygon.y + polygon.height)
352
+ if float_equal(@shape.height, polygon.height)
353
+ Geom2D::PolygonSet()
354
+ else
355
+ Geom2D::Rectangle(@shape.x, @shape.y, @shape.width, @shape.height - polygon.height)
356
+ end
357
+ else
358
+ Geom2D::Algorithms::PolygonOperation.run(@shape, polygon, :difference)
359
+ end
342
360
  @region_selection = :max_width
343
361
  find_next_region
344
362
  end
@@ -369,8 +387,7 @@ module HexaPDF
369
387
  # Creates a Geom2D::Polygon object representing the rectangle with the bottom left corner
370
388
  # (blx, bly) and the top right corner (trx, try).
371
389
  def create_rectangle(blx, bly, trx, try)
372
- Geom2D::Polygon(Geom2D::Point(blx, bly), Geom2D::Point(trx, bly),
373
- Geom2D::Point(trx, try), Geom2D::Point(blx, try))
390
+ Geom2D::Rectangle(blx, bly, trx - blx, try - bly)
374
391
  end
375
392
 
376
393
  # Finds the region with the maximum width.
@@ -404,7 +421,8 @@ module HexaPDF
404
421
 
405
422
  # Trims the frame's shape so that the next starting point is different.
406
423
  def trim_shape
407
- return unless (segments = find_starting_point)
424
+ @x = @y = @available_width = @available_height = 0
425
+ return if @shape.kind_of?(Geom2D::Rectangle) || !(segments = find_starting_point)
408
426
 
409
427
  # Just use the second top-most segment
410
428
  # TODO: not the optimal solution!
@@ -74,6 +74,11 @@ module HexaPDF
74
74
  @image = image
75
75
  end
76
76
 
77
+ # Returns +false+ since the image is always drawn if it fits.
78
+ def empty?
79
+ false
80
+ end
81
+
77
82
  # Fits the image into the available space, taking the initially set width and height into
78
83
  # account (see the class description for details).
79
84
  def fit(available_width, available_height, _frame)
@@ -73,6 +73,7 @@ module HexaPDF
73
73
  # The +valign+ argument can be used to specify the vertical alignment of the box relative to
74
74
  # other items in the Line.
75
75
  def initialize(box, valign: :baseline)
76
+ raise HexaPDF::Error, "Width of box not set" if box.width == 0
76
77
  @box = box
77
78
  @valign = valign
78
79
  @fit_result = Frame.new(0, 0, box.width, box.height == 0 ? 100_000 : box.height).fit(box)
@@ -177,6 +177,11 @@ module HexaPDF
177
177
  true
178
178
  end
179
179
 
180
+ # Returns +true+ if no box was fitted into the list box.
181
+ def empty?
182
+ super && (!@results || @results.all? {|box_fitter| box_fitter.fit_results.empty? })
183
+ end
184
+
180
185
  # Fits the list box into the available space.
181
186
  def fit(available_width, available_height, frame)
182
187
  @width = if @initial_width > 0
@@ -234,6 +239,8 @@ module HexaPDF
234
239
  (@results.count - 1) * item_spacing +
235
240
  reserved_height
236
241
 
242
+ @draw_pos_x = frame.x + reserved_width_left
243
+ @draw_pos_y = frame.y - @height + reserved_height_bottom
237
244
  @fit_successful = @results.all?(&:fit_successful?) && @results.size == @children.size
238
245
  end
239
246
 
@@ -333,7 +340,14 @@ module HexaPDF
333
340
  end
334
341
 
335
342
  # Draws the list items onto the canvas at position [x, y].
336
- def draw_content(canvas, _x, _y)
343
+ def draw_content(canvas, x, y)
344
+ translate = (style.position != :flow && (x != @draw_pos_x || y != @draw_pos_y))
345
+
346
+ if translate
347
+ canvas.save_graphics_state
348
+ canvas.translate(x - @draw_pos_x, y - @draw_pos_y)
349
+ end
350
+
337
351
  @results.each_with_index do |box_fitter, index|
338
352
  if index != 0 || !split_box? || @split_box == :show_first_marker
339
353
  box = item_marker_box(canvas.context.document, index)
@@ -343,6 +357,8 @@ module HexaPDF
343
357
  end
344
358
  box_fitter.fit_results.each {|result| result.draw(canvas) }
345
359
  end
360
+
361
+ canvas.restore_graphics_state if translate
346
362
  end
347
363
 
348
364
  end
@@ -95,8 +95,8 @@ module HexaPDF
95
95
  # If this attribute is +nil+ (the default), it means that this style should be used again.
96
96
  attr_accessor :next_style
97
97
 
98
- # Creates a new page style instance for the given page size and orientation. If a block is
99
- # given, it is used as template for defining the initial content.
98
+ # Creates a new page style instance for the given page size, orientation and next style
99
+ # values. If a block is given, it is used as template for defining the initial content.
100
100
  #
101
101
  # Example:
102
102
  #
@@ -105,12 +105,12 @@ module HexaPDF
105
105
  # style.next_style = :other
106
106
  # canvas.fill_color("fd0") { canvas.circle(100, 100, 50).fill }
107
107
  # end
108
- def initialize(page_size: :A4, orientation: :portrait, &block)
108
+ def initialize(page_size: :A4, orientation: :portrait, next_style: nil, &block)
109
109
  @page_size = page_size
110
110
  @orientation = orientation
111
111
  @template = block
112
112
  @frame = nil
113
- @next_style = nil
113
+ @next_style = next_style
114
114
  end
115
115
 
116
116
  # Creates a new page in the given document with this page style and returns it.
@@ -206,11 +206,16 @@ module HexaPDF
206
206
  # The styles of each edge. See Quad.
207
207
  attr_reader :style
208
208
 
209
+ # Specifies whether the border should be drawn inside the provided rectangle (+false+,
210
+ # default) or on it (+true+).
211
+ attr_accessor :draw_on_bounds
212
+
209
213
  # Creates a new border style. All arguments can be set to any value that a Quad can process.
210
- def initialize(width: 0, color: 0, style: :solid)
214
+ def initialize(width: 0, color: 0, style: :solid, draw_on_bounds: false)
211
215
  @width = Quad.new(width)
212
216
  @color = Quad.new(color)
213
217
  @style = Quad.new(style)
218
+ @draw_on_bounds = draw_on_bounds
214
219
  end
215
220
 
216
221
  # Duplicates a Border object's properties.
@@ -226,10 +231,20 @@ module HexaPDF
226
231
  width.simple? && width.top == 0
227
232
  end
228
233
 
229
- # Draws the border onto the canvas, inside the rectangle (x, y, w, h).
234
+ # Draws the border onto the canvas.
235
+ #
236
+ # Depending on #draw_on_bounds the border is drawn inside the rectangle (x, y, w, h) or on
237
+ # it.
230
238
  def draw(canvas, x, y, w, h)
231
239
  return if none?
232
240
 
241
+ if draw_on_bounds
242
+ x -= width.left / 2.0
243
+ y -= width.bottom / 2.0
244
+ w += (width.left + width.right) / 2.0
245
+ h += (width.top + width.bottom) / 2.0
246
+ end
247
+
233
248
  canvas.save_graphics_state do
234
249
  if width.simple? && color.simple? && style.simple?
235
250
  draw_simple_border(canvas, x, y, w, h)
@@ -444,7 +459,7 @@ module HexaPDF
444
459
  # Style objects using link annotations. Typical use cases would be linking to a (named)
445
460
  # destination on a different page or executing a URI action.
446
461
  #
447
- # See: PDF1.7 s12.5.6.6, Layers, HexaPDF::Type::Annotations::Link
462
+ # See: PDF2.0 s12.5.6.5, Layers, HexaPDF::Type::Annotations::Link
448
463
  class LinkLayer
449
464
 
450
465
  # Creates a new LinkLayer object.