hexapdf 0.5.0 → 0.6.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -2
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +1 -1
  5. data/VERSION +1 -1
  6. data/examples/boxes.rb +68 -0
  7. data/examples/graphics.rb +12 -12
  8. data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
  9. data/examples/text_layouter_inline_boxes.rb +66 -0
  10. data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
  11. data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
  12. data/examples/text_layouter_styling.rb +125 -0
  13. data/examples/truetype.rb +5 -7
  14. data/lib/hexapdf/cli/command.rb +1 -0
  15. data/lib/hexapdf/configuration.rb +170 -106
  16. data/lib/hexapdf/content/canvas.rb +41 -36
  17. data/lib/hexapdf/content/graphics_state.rb +15 -0
  18. data/lib/hexapdf/content/operator.rb +1 -1
  19. data/lib/hexapdf/dictionary.rb +20 -8
  20. data/lib/hexapdf/dictionary_fields.rb +8 -6
  21. data/lib/hexapdf/document.rb +25 -26
  22. data/lib/hexapdf/document/fonts.rb +4 -4
  23. data/lib/hexapdf/document/images.rb +2 -2
  24. data/lib/hexapdf/document/pages.rb +16 -16
  25. data/lib/hexapdf/encryption/security_handler.rb +41 -9
  26. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  27. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  28. data/lib/hexapdf/filter/predictor.rb +7 -1
  29. data/lib/hexapdf/font/true_type/font.rb +20 -0
  30. data/lib/hexapdf/font/type1/font.rb +23 -0
  31. data/lib/hexapdf/font_loader.rb +1 -0
  32. data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
  33. data/lib/hexapdf/font_loader/from_file.rb +65 -0
  34. data/lib/hexapdf/image_loader/png.rb +2 -2
  35. data/lib/hexapdf/layout.rb +3 -2
  36. data/lib/hexapdf/layout/box.rb +146 -0
  37. data/lib/hexapdf/layout/inline_box.rb +40 -31
  38. data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
  39. data/lib/hexapdf/layout/style.rb +630 -41
  40. data/lib/hexapdf/layout/text_fragment.rb +80 -12
  41. data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
  42. data/lib/hexapdf/number_tree_node.rb +1 -1
  43. data/lib/hexapdf/parser.rb +4 -1
  44. data/lib/hexapdf/revisions.rb +11 -4
  45. data/lib/hexapdf/stream.rb +8 -9
  46. data/lib/hexapdf/tokenizer.rb +5 -3
  47. data/lib/hexapdf/type.rb +3 -0
  48. data/lib/hexapdf/type/action.rb +56 -0
  49. data/lib/hexapdf/type/actions.rb +52 -0
  50. data/lib/hexapdf/type/actions/go_to.rb +52 -0
  51. data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
  52. data/lib/hexapdf/type/actions/launch.rb +73 -0
  53. data/lib/hexapdf/type/actions/uri.rb +65 -0
  54. data/lib/hexapdf/type/annotation.rb +85 -0
  55. data/lib/hexapdf/type/annotations.rb +51 -0
  56. data/lib/hexapdf/type/annotations/link.rb +70 -0
  57. data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
  58. data/lib/hexapdf/type/annotations/text.rb +81 -0
  59. data/lib/hexapdf/type/catalog.rb +3 -1
  60. data/lib/hexapdf/type/embedded_file.rb +6 -11
  61. data/lib/hexapdf/type/file_specification.rb +4 -6
  62. data/lib/hexapdf/type/font.rb +3 -1
  63. data/lib/hexapdf/type/font_descriptor.rb +18 -16
  64. data/lib/hexapdf/type/form.rb +3 -1
  65. data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
  66. data/lib/hexapdf/type/image.rb +4 -2
  67. data/lib/hexapdf/type/info.rb +2 -5
  68. data/lib/hexapdf/type/names.rb +2 -5
  69. data/lib/hexapdf/type/object_stream.rb +2 -1
  70. data/lib/hexapdf/type/page.rb +14 -1
  71. data/lib/hexapdf/type/page_tree_node.rb +9 -6
  72. data/lib/hexapdf/type/resources.rb +2 -5
  73. data/lib/hexapdf/type/trailer.rb +2 -5
  74. data/lib/hexapdf/type/viewer_preferences.rb +2 -5
  75. data/lib/hexapdf/type/xref_stream.rb +3 -1
  76. data/lib/hexapdf/version.rb +1 -1
  77. data/test/hexapdf/common_tokenizer_tests.rb +3 -1
  78. data/test/hexapdf/content/test_canvas.rb +29 -3
  79. data/test/hexapdf/content/test_graphics_state.rb +11 -0
  80. data/test/hexapdf/content/test_operator.rb +3 -2
  81. data/test/hexapdf/document/test_fonts.rb +8 -8
  82. data/test/hexapdf/document/test_images.rb +4 -12
  83. data/test/hexapdf/document/test_pages.rb +7 -7
  84. data/test/hexapdf/encryption/test_security_handler.rb +1 -5
  85. data/test/hexapdf/filter/test_predictor.rb +40 -12
  86. data/test/hexapdf/font/true_type/test_font.rb +16 -0
  87. data/test/hexapdf/font/type1/test_font.rb +30 -0
  88. data/test/hexapdf/font_loader/test_from_file.rb +29 -0
  89. data/test/hexapdf/font_loader/test_standard14.rb +4 -3
  90. data/test/hexapdf/layout/test_box.rb +104 -0
  91. data/test/hexapdf/layout/test_inline_box.rb +24 -10
  92. data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
  93. data/test/hexapdf/layout/test_style.rb +519 -31
  94. data/test/hexapdf/layout/test_text_fragment.rb +136 -15
  95. data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
  96. data/test/hexapdf/layout/test_text_shaper.rb +1 -1
  97. data/test/hexapdf/test_configuration.rb +12 -6
  98. data/test/hexapdf/test_dictionary.rb +27 -2
  99. data/test/hexapdf/test_dictionary_fields.rb +10 -1
  100. data/test/hexapdf/test_document.rb +14 -13
  101. data/test/hexapdf/test_parser.rb +12 -0
  102. data/test/hexapdf/test_revisions.rb +34 -0
  103. data/test/hexapdf/test_stream.rb +1 -1
  104. data/test/hexapdf/test_type.rb +18 -0
  105. data/test/hexapdf/test_writer.rb +2 -2
  106. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  107. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  108. data/test/hexapdf/type/annotations/test_link.rb +19 -0
  109. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  110. data/test/hexapdf/type/annotations/test_text.rb +38 -0
  111. data/test/hexapdf/type/test_annotation.rb +38 -0
  112. data/test/hexapdf/type/test_file_specification.rb +0 -7
  113. data/test/hexapdf/type/test_info.rb +0 -5
  114. data/test/hexapdf/type/test_page.rb +14 -0
  115. data/test/hexapdf/type/test_page_tree_node.rb +4 -1
  116. data/test/hexapdf/type/test_trailer.rb +0 -4
  117. data/test/test_helper.rb +6 -3
  118. metadata +36 -15
  119. data/examples/text_box_inline_boxes.rb +0 -56
  120. data/examples/text_box_styling.rb +0 -72
  121. data/test/hexapdf/type/test_embedded_file.rb +0 -16
  122. data/test/hexapdf/type/test_names.rb +0 -9
@@ -132,6 +132,32 @@ module HexaPDF
132
132
  # EncryptionDictionary class.
133
133
  class SecurityHandler
134
134
 
135
+ # Provides additional encryption specific information for HexaPDF::StreamData objects.
136
+ class EncryptedStreamData < StreamData
137
+
138
+ # The encryption key.
139
+ attr_reader :key
140
+
141
+ # The encryption algorithm.
142
+ attr_reader :algorithm
143
+
144
+ # Creates a new encrypted stream data object by utilizing the given stream data object as
145
+ # template. The arguments +key+ and +algorithm+ are used for decrypting purposes.
146
+ def initialize(obj, key, algorithm)
147
+ obj.instance_variables.each {|v| instance_variable_set(v, obj.instance_variable_get(v))}
148
+ @key = key
149
+ @algorithm = algorithm
150
+ end
151
+
152
+ alias :undecrypted_fiber :fiber
153
+
154
+ # Returns a fiber like HexaPDF::StreamData#fiber, but one wrapped in a decrypting fiber.
155
+ def fiber(*args)
156
+ @algorithm.decryption_fiber(@key, super(*args))
157
+ end
158
+
159
+ end
160
+
135
161
  # :call-seq:
136
162
  # SecurityHandler.set_up_encryption(document, handler_name, **options) -> handler
137
163
  #
@@ -143,8 +169,8 @@ module HexaPDF
143
169
  #
144
170
  # See: #set_up_encryption (for the common encryption options).
145
171
  def self.set_up_encryption(document, handler_name, **options)
146
- handler = GlobalConfiguration.constantize('encryption.filter_map', handler_name) do
147
- GlobalConfiguration.constantize('encryption.sub_filter_map', handler_name) do
172
+ handler = document.config.constantize('encryption.filter_map', handler_name) do
173
+ document.config.constantize('encryption.sub_filter_map', handler_name) do
148
174
  raise HexaPDF::EncryptionError, "Could not find the specified security handler"
149
175
  end
150
176
  end
@@ -170,8 +196,8 @@ module HexaPDF
170
196
  if dict.nil?
171
197
  raise HexaPDF::EncryptionError, "No /Encrypt dictionary found"
172
198
  end
173
- handler = HexaPDF::GlobalConfiguration.constantize('encryption.filter_map', dict[:Filter]) do
174
- HexaPDF::GlobalConfiguration.constantize('encryption.sub_filter_map', dict[:SubFilter]) do
199
+ handler = document.config.constantize('encryption.filter_map', dict[:Filter]) do
200
+ document.config.constantize('encryption.sub_filter_map', dict[:SubFilter]) do
175
201
  raise HexaPDF::EncryptionError, "Could not find a suitable security handler"
176
202
  end
177
203
  end
@@ -237,8 +263,7 @@ module HexaPDF
237
263
  unless string_algorithm == stream_algorithm
238
264
  key = object_key(obj.oid, obj.gen, stream_algorithm)
239
265
  end
240
- obj.raw_stream.filter.unshift(:Encryption)
241
- obj.raw_stream.decode_parms.unshift(key: key, algorithm: stream_algorithm)
266
+ obj.data.stream = EncryptedStreamData.new(obj.raw_stream, key, stream_algorithm)
242
267
  end
243
268
 
244
269
  obj
@@ -259,7 +284,14 @@ module HexaPDF
259
284
  return obj.stream_encoder if obj.type == :XRef
260
285
 
261
286
  key = object_key(obj.oid, obj.gen, stream_algorithm)
262
- obj.stream_encoder(:Encryption, key: key, algorithm: stream_algorithm)
287
+ source = obj.stream_source
288
+ result = obj.stream_encoder(source)
289
+ if result == source && obj.raw_stream.kind_of?(EncryptedStreamData) &&
290
+ obj.raw_stream.key == key && obj.raw_stream.algorithm == stream_algorithm
291
+ obj.raw_stream.undecrypted_fiber
292
+ else
293
+ stream_algorithm.encryption_fiber(key, result)
294
+ end
263
295
  end
264
296
 
265
297
  # Computes the encryption key and sets up the algorithms for encrypting the document based on
@@ -420,12 +452,12 @@ module HexaPDF
420
452
 
421
453
  # Returns the class that is used for ARC4 encryption.
422
454
  def arc4_algorithm
423
- @arc4_algorithm ||= HexaPDF::GlobalConfiguration.constantize('encryption.arc4')
455
+ @arc4_algorithm ||= document.config.constantize('encryption.arc4')
424
456
  end
425
457
 
426
458
  # Returns the class that is used for AES encryption.
427
459
  def aes_algorithm
428
- @aes_algorithm ||= HexaPDF::GlobalConfiguration.constantize('encryption.aes')
460
+ @aes_algorithm ||= document.config.constantize('encryption.aes')
429
461
  end
430
462
 
431
463
  # Returns the class that is used for the identity algorithm which passes back the data as is
@@ -81,7 +81,7 @@ module HexaPDF
81
81
  Fiber.yield(data)
82
82
  end
83
83
  begin
84
- inflater.finish
84
+ (data = inflater.finish).empty? ? nil : data
85
85
  rescue
86
86
  raise FilterError, "Problem while decoding Flate encoded stream: #{$!}"
87
87
  end
@@ -85,7 +85,7 @@ module HexaPDF
85
85
  case table.size
86
86
  when 510, 1022, 2046
87
87
  code_length += 1
88
- when 4095
88
+ when 4096
89
89
  if code != CLEAR_TABLE
90
90
  raise FilterError, "Maximum of 12bit for codes in LZW stream exceeded"
91
91
  end
@@ -33,6 +33,7 @@
33
33
 
34
34
  require 'fiber'
35
35
  require 'hexapdf/error'
36
+ require 'hexapdf/configuration'
36
37
  require 'hexapdf/utils/bit_stream'
37
38
 
38
39
  module HexaPDF
@@ -254,8 +255,13 @@ module HexaPDF
254
255
  Fiber.yield(result) unless result.empty?
255
256
  end
256
257
 
257
- unless pos == data.length
258
+ if pos != data.length && GlobalConfiguration['filter.predictor.strict']
258
259
  raise FilterError, "Data is missing for PNG predictor"
260
+ elsif pos != data.length && data.length != 1
261
+ result = ''.b
262
+ bytes_per_row = data.length - pos
263
+ row_action.call(result)
264
+ result
259
265
  end
260
266
  end
261
267
  end
@@ -162,6 +162,26 @@ module HexaPDF
162
162
  weight / 5
163
163
  end
164
164
 
165
+ # Returns the distance from the baseline to the top of the underline.
166
+ def underline_position
167
+ self[:post].underline_position
168
+ end
169
+
170
+ # Returns the stroke width for the underline.
171
+ def underline_thickness
172
+ self[:post].underline_thickness
173
+ end
174
+
175
+ # Returns the distance from the baseline to the top of the strikeout line.
176
+ def strikeout_position
177
+ self[:"OS/2"].strikeout_position
178
+ end
179
+
180
+ # Returns the stroke width for the strikeout line.
181
+ def strikeout_thickness
182
+ self[:"OS/2"].strikeout_size
183
+ end
184
+
165
185
  # Returns th glyph ID of the missing glyph, i.e. 0.
166
186
  def missing_glyph_id
167
187
  0
@@ -76,6 +76,7 @@ module HexaPDF
76
76
  def_delegators :@metrics, :weight, :weight_class, :bounding_box, :italic_angle
77
77
  def_delegators :@metrics, :ascender, :descender, :cap_height, :x_height
78
78
  def_delegators :@metrics, :dominant_horizontal_stem_width, :dominant_vertical_stem_width
79
+ def_delegators :@metrics, :underline_thickness
79
80
 
80
81
  # Creates a new Type1 font object with the given font metrics.
81
82
  def initialize(metrics)
@@ -128,6 +129,28 @@ module HexaPDF
128
129
  end
129
130
  end
130
131
 
132
+ # Returns the distance from the baseline to the top of the underline.
133
+ def underline_position
134
+ @metrics.underline_position + @metrics.underline_thickness / 2.0
135
+ end
136
+
137
+ # Returns the distance from the baseline to the top of the strikeout line.
138
+ def strikeout_position
139
+ # We use the suggested value from the OpenType spec.
140
+ 225
141
+ end
142
+
143
+ # Returns the thickness of the strikeout line.
144
+ def strikeout_thickness
145
+ # The OpenType spec suggests the width of an em-dash or 50, so we use that as
146
+ # approximation since the AFM files don't contain this information.
147
+ if (bbox = @metrics.character_metrics[:emdash]&.bbox)
148
+ bbox[3] - bbox[1]
149
+ else
150
+ 50
151
+ end
152
+ end
153
+
131
154
  end
132
155
 
133
156
  end
@@ -79,6 +79,7 @@ module HexaPDF
79
79
 
80
80
  autoload(:Standard14, 'hexapdf/font_loader/standard14')
81
81
  autoload(:FromConfiguration, 'hexapdf/font_loader/from_configuration')
82
+ autoload(:FromFile, 'hexapdf/font_loader/from_file')
82
83
 
83
84
  end
84
85
 
@@ -32,6 +32,7 @@
32
32
  #++
33
33
 
34
34
  require 'hexapdf/font/true_type_wrapper'
35
+ require 'hexapdf/font_loader/from_file'
35
36
 
36
37
  module HexaPDF
37
38
  module FontLoader
@@ -62,9 +63,7 @@ module HexaPDF
62
63
  unless File.file?(file)
63
64
  raise HexaPDF::Error, "The configured font file #{file} does not exist"
64
65
  end
65
-
66
- font = HexaPDF::Font::TrueType::Font.new(File.open(file))
67
- HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: subset)
66
+ FromFile.call(document, file, subset: subset)
68
67
  end
69
68
 
70
69
  end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ require 'hexapdf/font/true_type_wrapper'
35
+
36
+ module HexaPDF
37
+ module FontLoader
38
+
39
+ # This module interprets the font name as file name and tries to load it.
40
+ module FromFile
41
+
42
+ # Loads the given font by interpreting the font name as file name.
43
+ #
44
+ # The file object representing the font file is *not* closed and if needed must be closed by
45
+ # the caller once the font is not needed anymore.
46
+ #
47
+ # +document+::
48
+ # The PDF document to associate the font object with.
49
+ #
50
+ # +name+::
51
+ # The file name.
52
+ #
53
+ # +subset+::
54
+ # Specifies whether the font should be subset if possible.
55
+ def self.call(document, name, subset: true, **)
56
+ return nil unless File.file?(name)
57
+
58
+ font = HexaPDF::Font::TrueType::Font.new(File.open(name, 'rb'))
59
+ HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: subset)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -347,7 +347,7 @@ module HexaPDF
347
347
  image_data = ''.b
348
348
  mask_data = ''.b
349
349
 
350
- flate_decode = GlobalConfiguration.constantize('filter.map', :FlateDecode)
350
+ flate_decode = @document.config.constantize('filter.map', :FlateDecode)
351
351
  source = flate_decode.decoder(Fiber.new(&image_data_proc(offset)))
352
352
 
353
353
  data = ''.b
@@ -381,7 +381,7 @@ module HexaPDF
381
381
  bpc = decode_parms[:BitsPerComponent]
382
382
  bytes_per_row = (width * bpc + 7) / 8 + 1
383
383
 
384
- flate_decode = GlobalConfiguration.constantize('filter.map', :FlateDecode)
384
+ flate_decode = @document.config.constantize('filter.map', :FlateDecode)
385
385
  source = flate_decode.decoder(Fiber.new(&image_data_proc(offset)))
386
386
 
387
387
  mask_data = ''.b
@@ -42,9 +42,10 @@ module HexaPDF
42
42
  autoload(:Style, 'hexapdf/layout/style')
43
43
  autoload(:TextFragment, 'hexapdf/layout/text_fragment')
44
44
  autoload(:InlineBox, 'hexapdf/layout/inline_box')
45
- autoload(:LineFragment, 'hexapdf/layout/line_fragment')
45
+ autoload(:Line, 'hexapdf/layout/line')
46
46
  autoload(:TextShaper, 'hexapdf/layout/text_shaper')
47
- autoload(:TextBox, 'hexapdf/layout/text_box')
47
+ autoload(:TextLayouter, 'hexapdf/layout/text_layouter')
48
+ autoload(:Box, 'hexapdf/layout/box')
48
49
 
49
50
  end
50
51
 
@@ -0,0 +1,146 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+ require 'hexapdf/layout/style'
34
+
35
+ module HexaPDF
36
+ module Layout
37
+
38
+ # The base class for all layout boxes.
39
+ #
40
+ # HexaPDF uses the following box model:
41
+ #
42
+ # * Each box can specify a content width and content height. Padding, border and margin are
43
+ # *outside* of this content rectangle.
44
+ #
45
+ # * The #width and #height accessors can be used to get the width and height of the box
46
+ # including padding and the border.
47
+ #
48
+ class Box
49
+
50
+ # The width of the content box, i.e. without padding or borders.
51
+ #
52
+ # The value 0 means that the width is dynamically determined.
53
+ attr_reader :content_width
54
+
55
+ # The height of the content box, i.e. without padding or borders.
56
+ #
57
+ # The value 0 means that the height is dynamically determined.
58
+ attr_reader :content_height
59
+
60
+ # The style to be applied.
61
+ #
62
+ # Only the following properties are used:
63
+ #
64
+ # * Style#background_color
65
+ # * Style#padding
66
+ # * Style#border
67
+ # * Style#overlay_callback
68
+ # * Style#underlay_callback
69
+ attr_reader :style
70
+
71
+ # :call-seq:
72
+ # Box.new(content_width: 0, content_height: 0, style: Style.new) {|canvas, box| block} -> box
73
+ # Box.new(width: 0, height: 0, style: Style.new) {|canvas, box| block} -> box
74
+ #
75
+ # Creates a new Box object with the given width and height for its content that uses the
76
+ # provided block when it is asked to draw itself on a canvas (see #draw).
77
+ #
78
+ # Alternative to specifying the content width/height, it is also possible to specify the box
79
+ # width/height. The content width is then immediately calculated using the border and padding
80
+ # information from the style and stored.
81
+ #
82
+ # Since the final location of the box is not known beforehand, the drawing operations inside
83
+ # the block should draw inside the rectangle (0, 0, content_width, content_height) - note that
84
+ # the width and height of the box may not be known beforehand.
85
+ def initialize(content_width: 0, content_height: 0, width: 0, height: 0,
86
+ style: Style.new, &block)
87
+ @style = (style.kind_of?(Style) ? style : Style.new(style))
88
+ @draw_block = block
89
+ @content_width = content_width
90
+ @content_width = [width - self.width, 0].max if width != 0 && @content_width == 0
91
+ @content_height = content_height
92
+ @content_height = [height - self.height, 0].max if height != 0 && @content_height == 0
93
+ end
94
+
95
+ # Returns the width of the box, including padding and border widths.
96
+ def width
97
+ @content_width + @style.padding.left + @style.padding.right +
98
+ @style.border.width.left + @style.border.width.right
99
+ end
100
+
101
+ # Returns the height of the box, including padding and border widths.
102
+ def height
103
+ @content_height + @style.padding.top + @style.padding.bottom +
104
+ @style.border.width.top + @style.border.width.bottom
105
+ end
106
+
107
+ # :call-seq:
108
+ # box.draw(canvas, x, y)
109
+ #
110
+ # Draws the contents of the box onto the canvas at the position (x, y).
111
+ #
112
+ # The coordinate system is translated so that the origin is at the lower left corner of the
113
+ # contents box during the drawing operations.
114
+ def draw(canvas, x, y)
115
+ if style.background_color
116
+ canvas.save_graphics_state do
117
+ canvas.fill_color(style.background_color).rectangle(x, y, width, height).fill
118
+ end
119
+ end
120
+
121
+ style.underlays.draw(canvas, x, y, self)
122
+
123
+ unless style.border.none?
124
+ style.border.draw(canvas, x, y, width, height)
125
+ end
126
+
127
+ if @draw_block
128
+ canvas.translate(x + style.padding.left + style.border.width.left,
129
+ y + style.padding.bottom + style.border.width.bottom) do
130
+ @draw_block.call(canvas, self)
131
+ end
132
+ end
133
+
134
+ style.overlays.draw(canvas, x, y, self)
135
+ end
136
+
137
+ # Returns +true+ if no drawing operations are performed.
138
+ def empty?
139
+ !(@draw_block || style.background_color || !style.underlays.none? ||
140
+ !style.border.none? || !style.overlays.none?)
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end