hexapdf 0.32.1 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -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/cli.rb +4 -0
  22. data/lib/hexapdf/composer.rb +104 -52
  23. data/lib/hexapdf/configuration.rb +44 -39
  24. data/lib/hexapdf/content/canvas.rb +393 -267
  25. data/lib/hexapdf/content/color_space.rb +72 -25
  26. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  27. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  28. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  29. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  30. data/lib/hexapdf/content/graphic_object.rb +6 -7
  31. data/lib/hexapdf/content/graphics_state.rb +54 -45
  32. data/lib/hexapdf/content/operator.rb +52 -54
  33. data/lib/hexapdf/content/parser.rb +2 -2
  34. data/lib/hexapdf/content/processor.rb +15 -15
  35. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  36. data/lib/hexapdf/content.rb +5 -0
  37. data/lib/hexapdf/dictionary.rb +6 -5
  38. data/lib/hexapdf/dictionary_fields.rb +42 -14
  39. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  40. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  41. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  42. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  43. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  44. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  45. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  46. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  47. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  48. data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
  49. data/lib/hexapdf/digital_signature.rb +7 -2
  50. data/lib/hexapdf/document/destinations.rb +12 -11
  51. data/lib/hexapdf/document/files.rb +1 -1
  52. data/lib/hexapdf/document/fonts.rb +1 -1
  53. data/lib/hexapdf/document/layout.rb +167 -39
  54. data/lib/hexapdf/document/pages.rb +3 -2
  55. data/lib/hexapdf/document.rb +89 -55
  56. data/lib/hexapdf/encryption/aes.rb +5 -5
  57. data/lib/hexapdf/encryption/arc4.rb +1 -1
  58. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  59. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/identity.rb +1 -1
  61. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  62. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  63. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  64. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  65. data/lib/hexapdf/encryption.rb +7 -2
  66. data/lib/hexapdf/error.rb +18 -0
  67. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  68. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  69. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  70. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  71. data/lib/hexapdf/filter/pass_through.rb +1 -1
  72. data/lib/hexapdf/filter/predictor.rb +1 -1
  73. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  74. data/lib/hexapdf/filter.rb +55 -6
  75. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  76. data/lib/hexapdf/font/cmap.rb +1 -1
  77. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  79. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  80. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  82. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  83. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  84. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  85. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  86. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  87. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  88. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  89. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  90. data/lib/hexapdf/font_loader.rb +3 -0
  91. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  92. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  93. data/lib/hexapdf/image_loader/png.rb +2 -2
  94. data/lib/hexapdf/image_loader.rb +1 -1
  95. data/lib/hexapdf/importer.rb +13 -0
  96. data/lib/hexapdf/layout/box.rb +9 -2
  97. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  98. data/lib/hexapdf/layout/column_box.rb +18 -4
  99. data/lib/hexapdf/layout/frame.rb +30 -12
  100. data/lib/hexapdf/layout/image_box.rb +5 -0
  101. data/lib/hexapdf/layout/inline_box.rb +1 -0
  102. data/lib/hexapdf/layout/list_box.rb +17 -1
  103. data/lib/hexapdf/layout/page_style.rb +4 -4
  104. data/lib/hexapdf/layout/style.rb +18 -3
  105. data/lib/hexapdf/layout/table_box.rb +682 -0
  106. data/lib/hexapdf/layout/text_box.rb +5 -3
  107. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  108. data/lib/hexapdf/layout/text_layouter.rb +12 -4
  109. data/lib/hexapdf/layout.rb +1 -0
  110. data/lib/hexapdf/name_tree_node.rb +1 -1
  111. data/lib/hexapdf/number_tree_node.rb +1 -1
  112. data/lib/hexapdf/object.rb +18 -7
  113. data/lib/hexapdf/parser.rb +8 -8
  114. data/lib/hexapdf/pdf_array.rb +1 -1
  115. data/lib/hexapdf/rectangle.rb +1 -1
  116. data/lib/hexapdf/reference.rb +1 -1
  117. data/lib/hexapdf/revision.rb +1 -1
  118. data/lib/hexapdf/revisions.rb +3 -3
  119. data/lib/hexapdf/serializer.rb +15 -15
  120. data/lib/hexapdf/stream.rb +4 -2
  121. data/lib/hexapdf/tokenizer.rb +14 -14
  122. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  123. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  124. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  125. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  126. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  128. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  130. data/lib/hexapdf/type/acro_form.rb +1 -1
  131. data/lib/hexapdf/type/action.rb +1 -1
  132. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  133. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  134. data/lib/hexapdf/type/actions/launch.rb +1 -1
  135. data/lib/hexapdf/type/actions/uri.rb +1 -1
  136. data/lib/hexapdf/type/actions.rb +1 -1
  137. data/lib/hexapdf/type/annotation.rb +3 -3
  138. data/lib/hexapdf/type/annotations/link.rb +1 -1
  139. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  140. data/lib/hexapdf/type/annotations/text.rb +1 -1
  141. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  142. data/lib/hexapdf/type/annotations.rb +1 -1
  143. data/lib/hexapdf/type/catalog.rb +1 -1
  144. data/lib/hexapdf/type/cid_font.rb +3 -3
  145. data/lib/hexapdf/type/embedded_file.rb +1 -1
  146. data/lib/hexapdf/type/file_specification.rb +2 -2
  147. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  148. data/lib/hexapdf/type/font_simple.rb +2 -2
  149. data/lib/hexapdf/type/font_type0.rb +3 -3
  150. data/lib/hexapdf/type/font_type3.rb +1 -1
  151. data/lib/hexapdf/type/form.rb +1 -1
  152. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  153. data/lib/hexapdf/type/icon_fit.rb +1 -1
  154. data/lib/hexapdf/type/image.rb +1 -1
  155. data/lib/hexapdf/type/info.rb +1 -1
  156. data/lib/hexapdf/type/mark_information.rb +1 -1
  157. data/lib/hexapdf/type/names.rb +2 -2
  158. data/lib/hexapdf/type/object_stream.rb +7 -3
  159. data/lib/hexapdf/type/outline.rb +1 -1
  160. data/lib/hexapdf/type/outline_item.rb +1 -1
  161. data/lib/hexapdf/type/page.rb +19 -10
  162. data/lib/hexapdf/type/page_label.rb +1 -1
  163. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  164. data/lib/hexapdf/type/resources.rb +1 -1
  165. data/lib/hexapdf/type/trailer.rb +2 -2
  166. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  167. data/lib/hexapdf/type/xref_stream.rb +2 -2
  168. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  169. data/lib/hexapdf/version.rb +1 -1
  170. data/lib/hexapdf/writer.rb +4 -4
  171. data/lib/hexapdf/xref_section.rb +2 -2
  172. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  173. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  174. data/test/hexapdf/content/test_canvas.rb +0 -1
  175. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  176. data/test/hexapdf/document/test_files.rb +2 -2
  177. data/test/hexapdf/document/test_layout.rb +98 -0
  178. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  179. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  180. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  181. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  182. data/test/hexapdf/layout/test_box.rb +1 -1
  183. data/test/hexapdf/layout/test_column_box.rb +65 -21
  184. data/test/hexapdf/layout/test_frame.rb +14 -14
  185. data/test/hexapdf/layout/test_image_box.rb +4 -0
  186. data/test/hexapdf/layout/test_inline_box.rb +5 -0
  187. data/test/hexapdf/layout/test_list_box.rb +40 -6
  188. data/test/hexapdf/layout/test_page_style.rb +3 -2
  189. data/test/hexapdf/layout/test_style.rb +50 -0
  190. data/test/hexapdf/layout/test_table_box.rb +722 -0
  191. data/test/hexapdf/layout/test_text_box.rb +18 -0
  192. data/test/hexapdf/layout/test_text_layouter.rb +4 -0
  193. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  194. data/test/hexapdf/test_document.rb +1 -0
  195. data/test/hexapdf/test_filter.rb +8 -0
  196. data/test/hexapdf/test_importer.rb +9 -0
  197. data/test/hexapdf/test_object.rb +16 -5
  198. data/test/hexapdf/test_parser.rb +1 -1
  199. data/test/hexapdf/test_stream.rb +7 -0
  200. data/test/hexapdf/test_writer.rb +3 -3
  201. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  202. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  203. data/test/hexapdf/type/test_object_stream.rb +9 -3
  204. data/test/hexapdf/type/test_page.rb +18 -4
  205. 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.