hexapdf 1.2.0 → 1.4.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -0
  3. data/README.md +1 -1
  4. data/lib/hexapdf/cli/form.rb +9 -4
  5. data/lib/hexapdf/cli/inspect.rb +13 -4
  6. data/lib/hexapdf/composer.rb +14 -0
  7. data/lib/hexapdf/configuration.rb +15 -0
  8. data/lib/hexapdf/dictionary_fields.rb +1 -1
  9. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -2
  10. data/lib/hexapdf/document/annotations.rb +107 -2
  11. data/lib/hexapdf/document/layout.rb +94 -15
  12. data/lib/hexapdf/document/metadata.rb +10 -3
  13. data/lib/hexapdf/document.rb +9 -0
  14. data/lib/hexapdf/encryption/standard_security_handler.rb +7 -2
  15. data/lib/hexapdf/error.rb +11 -3
  16. data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
  17. data/lib/hexapdf/layout/box.rb +5 -0
  18. data/lib/hexapdf/layout/container_box.rb +63 -28
  19. data/lib/hexapdf/layout/style.rb +129 -20
  20. data/lib/hexapdf/layout/table_box.rb +20 -2
  21. data/lib/hexapdf/object.rb +2 -2
  22. data/lib/hexapdf/pdf_array.rb +25 -3
  23. data/lib/hexapdf/tokenizer.rb +4 -1
  24. data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
  25. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  26. data/lib/hexapdf/type/acro_form/form.rb +7 -6
  27. data/lib/hexapdf/type/annotation.rb +12 -0
  28. data/lib/hexapdf/type/annotations/appearance_generator.rb +169 -16
  29. data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
  30. data/lib/hexapdf/type/annotations/circle.rb +65 -0
  31. data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
  32. data/lib/hexapdf/type/annotations/line.rb +5 -192
  33. data/lib/hexapdf/type/annotations/line_ending_styling.rb +208 -0
  34. data/lib/hexapdf/type/annotations/markup_annotation.rb +0 -1
  35. data/lib/hexapdf/type/annotations/polygon.rb +64 -0
  36. data/lib/hexapdf/type/annotations/polygon_polyline.rb +109 -0
  37. data/lib/hexapdf/type/annotations/polyline.rb +64 -0
  38. data/lib/hexapdf/type/annotations/square.rb +65 -0
  39. data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
  40. data/lib/hexapdf/type/annotations/widget.rb +50 -20
  41. data/lib/hexapdf/type/annotations.rb +9 -0
  42. data/lib/hexapdf/type/measure.rb +57 -0
  43. data/lib/hexapdf/type.rb +1 -0
  44. data/lib/hexapdf/version.rb +1 -1
  45. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +0 -1
  46. data/test/hexapdf/document/test_annotations.rb +42 -0
  47. data/test/hexapdf/document/test_layout.rb +38 -10
  48. data/test/hexapdf/document/test_metadata.rb +13 -1
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -1
  50. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  51. data/test/hexapdf/layout/test_box.rb +8 -0
  52. data/test/hexapdf/layout/test_container_box.rb +34 -6
  53. data/test/hexapdf/layout/test_page_style.rb +1 -1
  54. data/test/hexapdf/layout/test_style.rb +46 -2
  55. data/test/hexapdf/layout/test_table_box.rb +14 -1
  56. data/test/hexapdf/test_composer.rb +7 -0
  57. data/test/hexapdf/test_dictionary_fields.rb +1 -0
  58. data/test/hexapdf/test_object.rb +1 -1
  59. data/test/hexapdf/test_pdf_array.rb +36 -3
  60. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
  61. data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
  62. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  63. data/test/hexapdf/type/acro_form/test_form.rb +17 -1
  64. data/test/hexapdf/type/annotations/test_appearance_generator.rb +210 -0
  65. data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
  66. data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
  67. data/test/hexapdf/type/annotations/test_line.rb +0 -45
  68. data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
  69. data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
  70. data/test/hexapdf/type/annotations/test_widget.rb +35 -0
  71. metadata +16 -2
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
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-2025 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # A square annotation displays a rectangle inside the annotation rectangle (the "square" name
44
+ # defined by the PDF specification is a bit misleading).
45
+ #
46
+ # Also see SquareCircle for more information.
47
+ #
48
+ # Example:
49
+ #
50
+ # #>pdf-small
51
+ # doc.annotations.create_rectangle(doc.pages[0], 20, 30, 60, 40).
52
+ # border_style(color: "hp-blue", width: 2, style: [3, 1]).
53
+ # interior_color("hp-orange").
54
+ # regenerate_appearance
55
+ #
56
+ # See: PDF2.0 s12.5.6.8, HexaPDF::Type::Annotations::SquareCircle,
57
+ class Square < SquareCircle
58
+
59
+ define_field :Subtype, type: Symbol, required: true, default: :Square
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,77 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
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-2025 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # This is the base class for the square and circle markup annotations which display a
44
+ # rectangle or ellipse inside the annotation rectangle.
45
+ #
46
+ # The styling is done through methods included by various modules:
47
+ #
48
+ # * Changing the line width, line dash pattern and color is done using the method
49
+ # BorderStyling#border_style. While that method allows special styling of the line (like
50
+ # :beveled), only a simple line dash pattern is supported by the square and circle
51
+ # annotations.
52
+ #
53
+ # * The interior color can be changed through InteriorColor#interior_color.
54
+ #
55
+ # * The border effect can be changed through BorderEffect#border_effect. Note that cloudy
56
+ # borders are not supported.
57
+ #
58
+ # See: PDF2.0 s12.5.6.8, HexaPDF::Type::Annotations::Square,
59
+ # HexaPDF::Type::Annotations::Circle, HexaPDF::Type::MarkupAnnotation
60
+ class SquareCircle < MarkupAnnotation
61
+
62
+ include BorderStyling
63
+ include BorderEffect
64
+ include InteriorColor
65
+
66
+ # Field Subtype is defined in the two subclasses
67
+ define_field :BS, type: :Border
68
+ define_field :IC, type: PDFArray, version: '1.4'
69
+ define_field :BE, type: :XXBorderEffect, version: '1.5'
70
+ # Array instead of Rectangle, see https://github.com/pdf-association/pdf-issues/issues/524
71
+ define_field :RD, type: PDFArray, version: '1.5'
72
+
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -124,15 +124,21 @@ module HexaPDF
124
124
  end
125
125
  end
126
126
 
127
- # Describes the marker style of a check box or radio button widget.
127
+ # Describes the marker style of a check box, radio button or push button widget.
128
128
  class MarkerStyle
129
129
 
130
- # The kind of marker that is shown inside the widget. Can either be one of the symbols
131
- # +:check+, +:circle+, +:cross+, +:diamond+, +:square+ or +:star+, or a one character
132
- # string. The latter is interpreted using the ZapfDingbats font.
130
+ # The kind of marker that is shown inside the widget.
133
131
  #
134
- # If an empty string is set, it is treated as if +nil+ was set, i.e. it shows the default
135
- # marker for the field type.
132
+ # Radion buttons and check boxes::
133
+ # Can either be one of the symbols +:check+, +:circle+, +:cross+, +:diamond+,
134
+ # +:square+ or +:star+, or a one character string. The latter is interpreted using the
135
+ # ZapfDingbats font.
136
+ #
137
+ # If an empty string is set, it is treated as if +nil+ was set, i.e. it shows the
138
+ # default marker for the field type.
139
+ #
140
+ # Push buttons:
141
+ # The caption string.
136
142
  attr_reader :style
137
143
 
138
144
  # The size of the marker in PDF points that is shown inside the widget. The special value
@@ -143,27 +149,40 @@ module HexaPDF
143
149
  # HexaPDF::Content::ColorSpace.
144
150
  attr_reader :color
145
151
 
152
+ # The resource name of the font that should be used for the caption.
153
+ #
154
+ # This is only used for push button widgets.
155
+ attr_reader :font_name
156
+
146
157
  # Creates a new instance with the given values.
147
- def initialize(style, size, color)
158
+ def initialize(style, size, color, font_name)
148
159
  @style = style
149
160
  @size = size
150
161
  @color = color
162
+ @font_name = font_name
151
163
  end
152
164
 
153
165
  end
154
166
 
155
167
  # :call-seq:
156
- # widget.marker_style => marker_style
157
- # widget.marker_style(style: nil, size: nil, color: nil) => widget
168
+ # widget.marker_style => marker_style
169
+ # widget.marker_style(style: nil, size: nil, color: nil, font_name: nil) => widget
158
170
  #
159
171
  # Returns a MarkerStyle instance representing the marker style of the widget when no
160
172
  # argument is given. Otherwise sets the button marker style of the widget and returns self.
161
173
  #
162
- # This method returns valid information only for check boxes and radio buttons!
174
+ # This method returns valid information only for check boxes, radio buttons and push buttons!
163
175
  #
164
- # When setting a marker style, arguments that are not provided will use the default: a black
165
- # auto-sized checkmark (i.e. :check for for check boxes) or circle (:circle for radio
166
- # buttons). This also means that multiple invocations will reset *all* prior values.
176
+ # When setting a marker style, arguments that are not provided will use the default:
177
+ #
178
+ # * For check boxes a black auto-sized checkmark (i.e. :check)
179
+ # * For radio buttons a black auto-sized circle (i.e. :circle)
180
+ # * For push buttons a black 9pt empty text using Helvetica
181
+ #
182
+ # This also means that multiple invocations will reset *all* prior values.
183
+ #
184
+ # Note that the +font_name+ argument must be a valid HexaPDF font name (this is in contrast
185
+ # to MarkerStyle#font_name which returns the resource name of the font).
167
186
  #
168
187
  # Note: The marker is called "normal caption" in the PDF 2.0 spec and the /CA entry of the
169
188
  # associated appearance characteristics dictionary. The marker size and color are set using
@@ -171,13 +190,18 @@ module HexaPDF
171
190
  # does it).
172
191
  #
173
192
  # See: PDF2.0 s12.5.6.19 and s12.7.4.3
174
- def marker_style(style: nil, size: nil, color: nil)
193
+ def marker_style(style: nil, size: nil, color: nil, font_name: nil)
175
194
  field = form_field
176
- if style || size || color
177
- style ||= (field.check_box? ? :check : :cicrle)
178
- size ||= 0
195
+ if style || size || color || font_name
196
+ style ||= case field.concrete_field_type
197
+ when :check_box then :check
198
+ when :radio_button then :circle
199
+ when :push_button then ''
200
+ end
201
+ size ||= (field.push_button? ? 9 : 0)
179
202
  color = Content::ColorSpace.device_color_from_specification(color || 0)
180
203
  serialized_color = Content::ColorSpace.serialize_device_color(color)
204
+ font_name ||= 'Helvetica'
181
205
 
182
206
  self[:MK] ||= {}
183
207
  self[:MK][:CA] = case style
@@ -191,7 +215,13 @@ module HexaPDF
191
215
  else
192
216
  raise ArgumentError, "Unknown value #{style} for argument 'style'"
193
217
  end
194
- self[:DA] = "/ZaDb #{size} Tf #{serialized_color}".strip
218
+ self[:DA] = if field.push_button?
219
+ name = document.acro_form(create: true).default_resources.
220
+ add_font(document.fonts.add(font_name).pdf_object)
221
+ "/#{name} #{size} Tf #{serialized_color}".strip
222
+ else
223
+ "/ZaDb #{size} Tf #{serialized_color}".strip
224
+ end
195
225
  else
196
226
  style = case self[:MK]&.[](:CA)
197
227
  when '4' then :check
@@ -211,12 +241,12 @@ module HexaPDF
211
241
  size = 0
212
242
  color = HexaPDF::Content::ColorSpace.prenormalized_device_color([0])
213
243
  if (da = self[:DA] || field[:DA])
214
- _, da_size, da_color = AcroForm::VariableTextField.parse_appearance_string(da)
244
+ font_name, da_size, da_color = AcroForm::VariableTextField.parse_appearance_string(da)
215
245
  size = da_size || size
216
246
  color = da_color || color
217
247
  end
218
248
 
219
- MarkerStyle.new(style, size, color)
249
+ MarkerStyle.new(style, size, color, font_name)
220
250
  end
221
251
  end
222
252
 
@@ -51,6 +51,15 @@ module HexaPDF
51
51
  autoload(:BorderStyling, 'hexapdf/type/annotations/border_styling')
52
52
  autoload(:Line, 'hexapdf/type/annotations/line')
53
53
  autoload(:AppearanceGenerator, 'hexapdf/type/annotations/appearance_generator')
54
+ autoload(:BorderEffect, 'hexapdf/type/annotations/border_effect')
55
+ autoload(:InteriorColor, 'hexapdf/type/annotations/interior_color')
56
+ autoload(:SquareCircle, 'hexapdf/type/annotations/square_circle')
57
+ autoload(:Square, 'hexapdf/type/annotations/square')
58
+ autoload(:Circle, 'hexapdf/type/annotations/circle')
59
+ autoload(:LineEndingStyling, 'hexapdf/type/annotations/line_ending_styling')
60
+ autoload(:PolygonPolyline, 'hexapdf/type/annotations/polygon_polyline')
61
+ autoload(:Polygon, 'hexapdf/type/annotations/polygon')
62
+ autoload(:Polyline, 'hexapdf/type/annotations/polyline')
54
63
 
55
64
  end
56
65
 
@@ -0,0 +1,57 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
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-2025 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/dictionary'
38
+
39
+ module HexaPDF
40
+ module Type
41
+
42
+ # A Measure dictionary specifies how measurement information should be formatted in textual
43
+ # form.
44
+ #
45
+ # See: PDF2.0 s12.9.1
46
+ class Measure < Dictionary
47
+
48
+ define_type :Measure
49
+
50
+ define_field :Type, type: Symbol, default: type
51
+ define_field :Subtype, type: Symbol, default: :RL,
52
+ allowed_values: [:RL, :GEO]
53
+
54
+ end
55
+
56
+ end
57
+ end
data/lib/hexapdf/type.rb CHANGED
@@ -88,6 +88,7 @@ module HexaPDF
88
88
  autoload(:Namespace, 'hexapdf/type/namespace')
89
89
  autoload(:MarkedContentReference, 'hexapdf/type/marked_content_reference')
90
90
  autoload(:ObjectReference, 'hexapdf/type/object_reference')
91
+ autoload(:Measure, 'hexapdf/type/measure')
91
92
 
92
93
  end
93
94
 
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.2.0'
40
+ VERSION = '1.4.0'
41
41
 
42
42
  end
@@ -151,7 +151,6 @@ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
151
151
  @handler.finalize_objects(@field, @obj)
152
152
  ref = @obj[:Reference][0]
153
153
  assert_equal(:DocMDP, ref[:TransformMethod])
154
- assert_equal(:SHA256, ref[:DigestMethod])
155
154
  assert_equal(1, ref[:TransformParams][:P])
156
155
  assert_equal(:'1.2', ref[:TransformParams][:V])
157
156
  assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
@@ -18,6 +18,8 @@ describe HexaPDF::Document::Annotations do
18
18
  it "delegates to the actual create_TYPE implementation" do
19
19
  annot = @annots.create(:line, @page, start_point: [0, 0], end_point: [10, 10])
20
20
  assert_equal(:Line, annot[:Subtype])
21
+ annot = @annots.create(:rectangle, @page, 10, 20, 30, 40)
22
+ assert_equal(:Square, annot[:Subtype])
21
23
  end
22
24
  end
23
25
 
@@ -30,4 +32,44 @@ describe HexaPDF::Document::Annotations do
30
32
  assert_equal(annot, @page[:Annots].first)
31
33
  end
32
34
  end
35
+
36
+ describe "create_rectangle" do
37
+ it "creates an appropriate square annotation object" do
38
+ annot = @annots.create(:rectangle, @page, 10, 20, 30, 40)
39
+ assert_equal(:Annot, annot[:Type])
40
+ assert_equal(:Square, annot[:Subtype])
41
+ assert_equal([10, 20, 40, 60], annot[:Rect])
42
+ assert_equal(annot, @page[:Annots].first)
43
+ end
44
+ end
45
+
46
+ describe "create_ellipse" do
47
+ it "creates an appropriate circle annotation object" do
48
+ annot = @annots.create(:ellipse, @page, 100, 100, a: 30, b: 40)
49
+ assert_equal(:Annot, annot[:Type])
50
+ assert_equal(:Circle, annot[:Subtype])
51
+ assert_equal([70, 60, 130, 140], annot[:Rect])
52
+ assert_equal(annot, @page[:Annots].first)
53
+ end
54
+ end
55
+
56
+ describe "create_polyline" do
57
+ it "creates an appropriate polyline annotation object" do
58
+ annot = @annots.create(:polyline, @page, 10, 10, 20, 15)
59
+ assert_equal(:Annot, annot[:Type])
60
+ assert_equal(:PolyLine, annot[:Subtype])
61
+ assert_equal([10, 10, 20, 15], annot.vertices)
62
+ assert_equal(annot, @page[:Annots].first)
63
+ end
64
+ end
65
+
66
+ describe "create_polygon" do
67
+ it "creates an appropriate polygon annotation object" do
68
+ annot = @annots.create(:polygon, @page, 10, 10, 20, 15)
69
+ assert_equal(:Annot, annot[:Type])
70
+ assert_equal(:Polygon, annot[:Subtype])
71
+ assert_equal([10, 10, 20, 15], annot.vertices)
72
+ assert_equal(annot, @page[:Annots].first)
73
+ end
74
+ end
33
75
  end
@@ -110,11 +110,12 @@ describe HexaPDF::Document::Layout::CellArgumentCollector do
110
110
  assert_equal({key: :value, a: :c, e: :f}, @args.retrieve_arguments_for(5, 6))
111
111
  end
112
112
 
113
- it "deep merges the :cell keys" do
114
- @args[] = {cell: {a: :b, c: :d}}
113
+ it "deep merges the :cell keys in order of definition" do
115
114
  @args[3..7] = {cell: {a: :y, e: :f}}
115
+ @args[] = {cell: {a: :b, c: :d}}
116
116
  @args[5, 6] = {cell: {a: :z}}
117
117
  assert_equal({cell: {a: :z, c: :d, e: :f}}, @args.retrieve_arguments_for(5, 6))
118
+ assert_equal({cell: {a: :b, c: :d}}, @args.retrieve_arguments_for(1, 2))
118
119
  end
119
120
  end
120
121
  end
@@ -146,6 +147,16 @@ describe HexaPDF::Document::Layout do
146
147
  end
147
148
  end
148
149
 
150
+ describe "style?" do
151
+ it "returns true if a given style is defined" do
152
+ assert(@layout.style?(:base))
153
+ end
154
+
155
+ it "returns false if a given style is not defined" do
156
+ refute(@layout.style?(:unknown))
157
+ end
158
+ end
159
+
149
160
  describe "styles" do
150
161
  it "returns the existing styles" do
151
162
  @layout.style(:test, font_size: 20)
@@ -162,21 +173,36 @@ describe HexaPDF::Document::Layout do
162
173
  end
163
174
  end
164
175
 
165
- describe "private retrieve_style" do
176
+ describe "resolve_font" do
166
177
  it "resolves a font name to a font wrapper" do
167
- style = @layout.send(:retrieve_style, {font: 'Helvetica'})
178
+ style = @layout.style(:other, font: 'Helvetica')
179
+ @layout.resolve_font(style)
168
180
  assert_kind_of(HexaPDF::Font::Type1Wrapper, style.font)
169
181
  end
170
182
 
183
+ it "uses the font_bold property when resolving a font name to a font wrapper" do
184
+ style = @layout.style(:other, font: 'Helvetica', font_bold: true)
185
+ @layout.resolve_font(style)
186
+ assert_equal('Helvetica-Bold', style.font.wrapped_font.font_name)
187
+ end
188
+
189
+ it "uses the font_italic property when resolving a font name to a font wrapper" do
190
+ style = @layout.style(:other, font: 'Helvetica', font_italic: true)
191
+ @layout.resolve_font(style)
192
+ assert_equal('Helvetica-Oblique', style.font.wrapped_font.font_name)
193
+ end
194
+
171
195
  it "sets the :base style's font if no font is set" do
172
196
  @layout.style(:base, font: 'Helvetica')
173
- style = @layout.send(:retrieve_style, {})
174
- assert_equal('Helvetica', style.font.wrapped_font.font_name)
197
+ style = @layout.style(:other, base: nil, font_italic: true)
198
+ @layout.resolve_font(style)
199
+ assert_equal('Helvetica-Oblique', style.font.wrapped_font.font_name)
175
200
  end
176
201
 
177
202
  it "sets the font specified in the config option font.default as fallback" do
178
- style = @layout.send(:retrieve_style, {})
179
- assert_equal('Times-Roman', style.font.wrapped_font.font_name)
203
+ style = @layout.style(:other, base: nil, font_italic: true)
204
+ @layout.resolve_font(style)
205
+ assert_equal('Times-Italic', style.font.wrapped_font.font_name)
180
206
  end
181
207
  end
182
208
 
@@ -203,7 +229,8 @@ describe HexaPDF::Document::Layout do
203
229
 
204
230
  describe "box" do
205
231
  it "creates the request box" do
206
- box = @layout.box(:column, columns: 3, gaps: 20, width: 15, height: 30, style: {font_size: 10},
232
+ box = @layout.box(:column, columns: 3, width: 15, height: 30,
233
+ style: {font_size: 10, box_options: {gaps: 20}},
207
234
  properties: {key: :value})
208
235
  assert_equal(15, box.width)
209
236
  assert_equal(30, box.height)
@@ -431,9 +458,10 @@ describe HexaPDF::Document::Layout do
431
458
 
432
459
  describe "table_box" do
433
460
  it "creates a table box" do
434
- box = @layout.table_box([['m']], column_widths: [100], header: proc { [['a']] },
461
+ box = @layout.table_box([['m']], header: proc { [['a']] },
435
462
  footer: proc { [['b']] }, cell_style: {background_color: "red"},
436
463
  width: 100, height: 300, style: {background_color: "blue"},
464
+ box_options: {column_widths: [100]},
437
465
  properties: {key: :value}, border: {width: 1})
438
466
  assert_equal(100, box.width)
439
467
  assert_equal(300, box.height)
@@ -13,6 +13,17 @@ describe HexaPDF::Document::Metadata do
13
13
 
14
14
  it "parses the info dictionary on creation" do
15
15
  assert_equal('Title', @metadata.title)
16
+
17
+ time = Time.now
18
+ @doc.trailer.info[:ModDate] = ''
19
+ assert_nil(HexaPDF::Document::Metadata.new(@doc).modification_date)
20
+ @doc.trailer.info[:ModDate] = time
21
+ assert_equal(time, HexaPDF::Document::Metadata.new(@doc).modification_date)
22
+ @doc.trailer.info[:CreationDate] = ''
23
+ assert_nil(HexaPDF::Document::Metadata.new(@doc).creation_date)
24
+ @doc.trailer.info[:CreationDate] = time
25
+ assert_equal(time, HexaPDF::Document::Metadata.new(@doc).creation_date)
26
+
16
27
  @doc.trailer.info[:Trapped] = :Unknown
17
28
  assert_nil(HexaPDF::Document::Metadata.new(@doc).trapped)
18
29
  @doc.trailer.info[:Trapped] = :True
@@ -213,6 +224,7 @@ describe HexaPDF::Document::Metadata do
213
224
  title.language = 'de'
214
225
  @metadata.title(['Title', title])
215
226
  @metadata.author(['Author 1', 'Author 2'])
227
+ @metadata.creation_date('')
216
228
  @metadata.register_property_type('dc', 'other', 'URI')
217
229
  @metadata.property('dc', 'other', 'https://test.org/example')
218
230
  @metadata.property('pdfaid', 'part', 3)
@@ -243,7 +255,7 @@ describe HexaPDF::Document::Metadata do
243
255
  </rdf:Description>
244
256
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
245
257
  <xmp:CreatorTool>Creator</xmp:CreatorTool>
246
- <xmp:CreateDate>#{@metadata.send(:xmp_date, @time)}</xmp:CreateDate>
258
+ <xmp:CreateDate></xmp:CreateDate>
247
259
  <xmp:ModifyDate>#{@metadata.send(:xmp_date, @time)}</xmp:ModifyDate>
248
260
  </rdf:Description>
249
261
  <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
@@ -188,10 +188,11 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
188
188
  refute(dict.value.key?(:Perms))
189
189
  crypt_filter.call(dict, 4, :AESV2, 16)
190
190
 
191
- dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
191
+ dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes, owner_password: 'hexapdf')
192
192
  assert_equal(32, dict[:UE].length)
193
193
  assert_equal(32, dict[:OE].length)
194
194
  assert_equal(16, dict[:Perms].length)
195
+ assert(@handler.send(:owner_password_valid?, 'hexapdf'))
195
196
  crypt_filter.call(dict, 6, :AESV3, 32)
196
197
  end
197
198
 
@@ -27,6 +27,16 @@ describe HexaPDF::Font::TrueType::Subsetter do
27
27
  assert_equal(value, @subsetter.subset_glyph_id(5))
28
28
  end
29
29
 
30
+ it "doesn't use certain subset glyph IDs for performance reasons" do
31
+ 1.upto(93) {|i| @subsetter.use_glyph(i) }
32
+ # glyph 0, 93 used glyph, 4 special glyphs
33
+ assert_equal(1 + 93 + 4, @subsetter.instance_variable_get(:@glyph_map).size)
34
+ 1.upto(12) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
35
+ 13.upto(38) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
36
+ 39.upto(88) {|i| assert_equal(i + 3, @subsetter.subset_glyph_id(i), "id=#{i}") }
37
+ 89.upto(93) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
38
+ end
39
+
30
40
  it "creates the subset font file" do
31
41
  gid = @font[:cmap].preferred_table[0x41]
32
42
  @subsetter.use_glyph(gid)
@@ -5,6 +5,14 @@ require 'hexapdf/document'
5
5
  require 'hexapdf/layout/box'
6
6
 
7
7
  describe HexaPDF::Layout::Box::FitResult do
8
+ it "allows setting the status to failure" do
9
+ result = HexaPDF::Layout::Box::FitResult.new(nil)
10
+ result.overflow!
11
+ refute(result.failure?)
12
+ result.failure!
13
+ assert(result.failure?)
14
+ end
15
+
8
16
  it "shows the box's mask area on #draw when using debug output" do
9
17
  doc = HexaPDF::Document.new(config: {'debug' => true})
10
18
  canvas = doc.pages.add.canvas