hexapdf 0.17.3 → 0.18.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15efadb34e0a5dc93ec7659d02cb95a19400a38fe7ccffcbc2438fa6e6f51b8f
4
- data.tar.gz: 29ddd43180432e648c107ae40c2618415f0ead8bf6e6fcbe46bc1f0557eae271
3
+ metadata.gz: d95ce1575c017f44b2c0f96e7e5a927b8c4f8c3adf6aa0f3a7dc983c5dfa77a8
4
+ data.tar.gz: e49a23655e5ce4f4ded50c5ac0c90d7892c41bd526dbdfe72ad85cca4891098b
5
5
  SHA512:
6
- metadata.gz: 0a3668dd377838665e75b6c8325efbe237e379823deb060acdf5c7be26d799ab4eee65e94a75b50e9ffc27c81693cb0158765402d9d6f4ddbacc3c1b3be531d4
7
- data.tar.gz: 759a4a692997d50022f26fd6e5fbf06859a93a00b5d371286a084ffbcb98d3571d72eeb1d3c8d733b4b8917d195c236f7dac3654e4fddae8cb9c2cf7b2f9ab42
6
+ metadata.gz: 37e3b09a059bb7875c797f50f60642b080f20a081fa2540f74837ac51a8cb9c1aa5b93e6502dd9f242d178f68ea654c0619b2e55d4cb934e36badca4a5057d1c
7
+ data.tar.gz: cd9fc830b8b4f5387478d7e6c32260672a9332bb2faf3aa7afcb4d71bf7c39481616f7f33d731229a6099e44dac84bbecd651c3618593f6c7ba872b9e958a3cb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## 0.18.0 - 2021-11-04
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Content::ColorSpace::serialize_device_color] for serialization of
6
+ device colors in parts other than the canvas
7
+ * [HexaPDF::Type::AcroForm::VariableTextField::create_appearance_string] for
8
+ centralized creation of appearance strings
9
+ * [HexaPDF::Object.make_direct] for making objects and all parts of them direct
10
+ instead of indirect
11
+
12
+ ### Changed
13
+
14
+ * [HexaPDF::Type::AcroForm::VariableTextField::parse_appearance_string] to also
15
+ return the font color
16
+ * [HexaPDF::Type::AcroForm::VariableTextField#set_default_appearance_string] to
17
+ allow specifying the font color
18
+ * [HexaPDF::Type::AcroForm::Form] methods to support new variable text field
19
+ methods
20
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator] to support the set font color
21
+ when creating text field appearances
22
+
23
+ ### Fixed
24
+
25
+ * Writing of existing, encrypted PDF files where parts of the encryption
26
+ dictionary are indirect objects
27
+ * [HexaPDF::Content::GraphicObject::EndpointArc] to correctly determine the
28
+ start and end points
29
+ * [HexaPDF::Dictionary#perform_validation] to correctly handle objects that
30
+ should not be indirect objects
31
+
32
+
1
33
  ## 0.17.3 - 2021-10-31
2
34
 
3
35
  ### Fixed
@@ -35,7 +35,9 @@
35
35
  #++
36
36
 
37
37
  require 'hexapdf/error'
38
+ require 'hexapdf/content'
38
39
  require 'hexapdf/configuration'
40
+ require 'hexapdf/serializer'
39
41
 
40
42
  module HexaPDF
41
43
  module Content
@@ -304,6 +306,23 @@ module HexaPDF
304
306
  GlobalConfiguration.constantize('color_space.map', for_components(spec)).new.color(*spec)
305
307
  end
306
308
 
309
+ # Serializes the given device color into the form expected by PDF content streams.
310
+ #
311
+ # The +type+ argument can either be :stroke to serialize as stroke color operator or :fill as
312
+ # fill color operator.
313
+ def self.serialize_device_color(color, type: :fill)
314
+ operator = case color.color_space.family
315
+ when :DeviceRGB then :rg
316
+ when :DeviceGray then :g
317
+ when :DeviceCMYK then :k
318
+ else
319
+ raise ArgumentError, "Device color object expected, got #{color.class}"
320
+ end
321
+ operator = operator.upcase if type == :stroke
322
+ Content::Operator::DEFAULT_OPERATORS[operator].
323
+ serialize(HexaPDF::Serializer.new, *color.components)
324
+ end
325
+
307
326
  # Returns a device color object for the given components array without applying value
308
327
  # normalization.
309
328
  def self.prenormalized_device_color(components)
@@ -51,8 +51,8 @@ module HexaPDF
51
51
  # Examples:
52
52
  #
53
53
  # #>pdf-center
54
- # arc = canvas.graphic_object(:arc, a: 100, b: 50).stroke
55
- # arc.draw(canvas).stroke # or: canvas.draw(arc).stroke
54
+ # arc = canvas.graphic_object(:arc, a: 100, b: 50, end_angle: 150)
55
+ # canvas.draw(arc).stroke
56
56
  #
57
57
  # See: ELL - https://spaceroots.org/documents/ellipse/elliptical-arc.pdf
58
58
  class Arc
@@ -49,8 +49,8 @@ module HexaPDF
49
49
  # Examples:
50
50
  #
51
51
  # #>pdf-center
52
- # arc = canvas.graphic_object(:endpoint_arc, x: 50, y: 20, a: 30, b: 10).stroke
53
- # arc.move_to(0, 0).draw(canvas).stroke # or: canvas.draw(arc).stroke
52
+ # arc = canvas.graphic_object(:endpoint_arc, x: 50, y: 20, a: 30, b: 10)
53
+ # canvas.move_to(0, 0).draw(arc).stroke
54
54
  #
55
55
  # See: GraphicObject::Arc, ARC - https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
56
56
  class EndpointArc
@@ -251,10 +251,10 @@ module HexaPDF
251
251
  cy = sin_theta * cxp + cos_theta * cyp + (y1 + y2) / 2.0
252
252
 
253
253
  # F.6.5.5
254
- start_angle = compute_angle_to_x_axis((x1p - cxp) / rx, (y1p - cyp) / ry)
254
+ start_angle = compute_angle_to_x_axis((x1p - cxp), (y1p - cyp)) % 360
255
255
 
256
256
  # F.6.5.6 (modified bc we just need the end angle)
257
- end_angle = compute_angle_to_x_axis((-x1p - cxp) / rx, (-y1p - cyp) / ry)
257
+ end_angle = compute_angle_to_x_axis((-x1p - cxp), (-y1p - cyp)) % 360
258
258
 
259
259
  {cx: cx, cy: cy, a: rx, b: ry, start_angle: start_angle, end_angle: end_angle,
260
260
  inclination: @inclination, clockwise: @clockwise}
@@ -41,20 +41,45 @@ module HexaPDF
41
41
  # This graphic object represents a solid elliptical arc, i.e. an arc that has an inner and
42
42
  # an outer set of a/b values.
43
43
  #
44
+ # This graphic object is registered under the :solid_arc key for use with the
45
+ # HexaPDF::Content::Canvas class.
46
+ #
44
47
  # Thus it can be used to create
45
48
  #
46
- # * an (elliptical) disk (when the inner a/b are zero and the difference between start and
49
+ # * an (*elliptical*) *disk* (when the inner a/b are zero and the difference between start and
47
50
  # end angles is greater than or equal to 360),
48
51
  #
49
- # * an (elliptical) sector (when the inner a/b are zero and the difference between start
52
+ # #>pdf-center
53
+ # canvas.fill_color("red").
54
+ # draw(:solid_arc, outer_a: 80, outer_b: 50, end_angle: 360).
55
+ # fill_stroke
56
+ #
57
+ # * an (*elliptical*) *sector* (when the inner a/b are zero and the difference between start
50
58
  # and end angles is less than 360),
51
59
  #
52
- # * an (elliptical) annulus (when the inner a/b are nonzero and the difference between
60
+ # #>pdf-center
61
+ # canvas.fill_color("red").
62
+ # draw(:solid_arc, outer_a: 80, outer_b: 50, start_angle: 20, end_angle: 230).
63
+ # fill_stroke
64
+ #
65
+ # * an (*elliptical*) *annulus* (when the inner a/b are nonzero and the difference between
53
66
  # start and end angles is greater than or equal to 360), and
54
67
  #
55
- # * an (elliptical) annular sector (when the inner a/b are nonzero and the difference
68
+ # #>pdf-center
69
+ # canvas.fill_color("red").
70
+ # draw(:solid_arc, outer_a: 80, outer_b: 50, inner_a: 70, inner_b: 30,
71
+ # end_angle: 360).
72
+ # fill_stroke
73
+ #
74
+ # * an (*elliptical*) *annular sector* (when the inner a/b are nonzero and the difference
56
75
  # between start and end angles is less than 360)
57
76
  #
77
+ # #>pdf-center
78
+ # canvas.fill_color("red").
79
+ # draw(:solid_arc, outer_a: 80, outer_b: 50, inner_a: 70, inner_b: 30,
80
+ # start_angle: 20, end_angle: 230).
81
+ # fill_stroke
82
+ #
58
83
  # See: Arc
59
84
  class SolidArc
60
85
 
@@ -66,30 +91,106 @@ module HexaPDF
66
91
  end
67
92
 
68
93
  # x-coordinate of center point
94
+ #
95
+ # Examples:
96
+ #
97
+ # #>pdf-center
98
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
99
+ # inner_a: 20, inner_b: 10)
100
+ # canvas.draw(solid_arc).stroke
101
+ # canvas.stroke_color("red").draw(solid_arc, cx: 50).stroke
69
102
  attr_reader :cx
70
103
 
71
104
  # y-coordinate of center point
105
+ #
106
+ # Examples:
107
+ #
108
+ # #>pdf-center
109
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
110
+ # inner_a: 20, inner_b: 10)
111
+ # canvas.draw(solid_arc).stroke
112
+ # canvas.stroke_color("red").draw(solid_arc, cy: 50).stroke
72
113
  attr_reader :cy
73
114
 
74
- # Length of inner semi-major axis
115
+ # Length of inner semi-major axis which (without altering the #inclination) is parallel to
116
+ # the x-axis
117
+ #
118
+ # Examples:
119
+ #
120
+ # #>pdf-center
121
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
122
+ # inner_a: 20, inner_b: 10)
123
+ # canvas.draw(solid_arc).stroke
124
+ # canvas.stroke_color("red").draw(solid_arc, inner_a: 5).stroke
75
125
  attr_reader :inner_a
76
126
 
77
- # Length of inner semi-minor axis
127
+ # Length of inner semi-minor axis which (without altering the #inclination) is parallel to the
128
+ # y-axis
129
+ #
130
+ # Examples:
131
+ #
132
+ # #>pdf-center
133
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
134
+ # inner_a: 20, inner_b: 10)
135
+ # canvas.draw(solid_arc).stroke
136
+ # canvas.stroke_color("red").draw(solid_arc, inner_b: 20).stroke
78
137
  attr_reader :inner_b
79
138
 
80
- # Length of outer semi-major axis
139
+ # Length of outer semi-major axis which (without altering the #inclination) is parallel to
140
+ # the x-axis
141
+ #
142
+ # Examples:
143
+ #
144
+ # #>pdf-center
145
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
146
+ # inner_a: 20, inner_b: 10)
147
+ # canvas.draw(solid_arc).stroke
148
+ # canvas.stroke_color("red").draw(solid_arc, outer_a: 45).stroke
81
149
  attr_reader :outer_a
82
150
 
83
- # Length of outer semi-minor axis
151
+ # Length of outer semi-minor axis which (without altering the #inclination) is parallel to the
152
+ # y-axis
153
+ #
154
+ # Examples:
155
+ #
156
+ # #>pdf-center
157
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
158
+ # inner_a: 20, inner_b: 10)
159
+ # canvas.draw(solid_arc).stroke
160
+ # canvas.stroke_color("red").draw(solid_arc, outer_b: 40).stroke
84
161
  attr_reader :outer_b
85
162
 
86
- # Start angle in degrees
163
+ # Start angle of the solid arc in degrees
164
+ #
165
+ # Examples:
166
+ #
167
+ # #>pdf-center
168
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
169
+ # inner_a: 20, inner_b: 10)
170
+ # canvas.draw(solid_arc).stroke
171
+ # canvas.stroke_color("red").draw(solid_arc, start_angle: 60).stroke
87
172
  attr_reader :start_angle
88
173
 
89
- # End angle in degrees
174
+ # End angle of the solid arc in degrees
175
+ #
176
+ # Examples:
177
+ #
178
+ # #>pdf-center
179
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
180
+ # inner_a: 20, inner_b: 10)
181
+ # canvas.draw(solid_arc).stroke
182
+ # canvas.stroke_color("red").draw(solid_arc, end_angle: 120).stroke
90
183
  attr_reader :end_angle
91
184
 
92
185
  # Inclination in degrees of semi-major axis in respect to x-axis
186
+ #
187
+ # Examples:
188
+ #
189
+ # #>pdf-center
190
+ # solid_arc = canvas.graphic_object(:solid_arc, outer_a: 30, outer_b: 20,
191
+ # inner_a: 20, inner_b: 10)
192
+ # canvas.draw(solid_arc).stroke
193
+ # canvas.stroke_color("red").draw(solid_arc, inclination: 40).stroke
93
194
  attr_reader :inclination
94
195
 
95
196
  # Creates a solid arc with default values (a unit disk at the origin).
@@ -318,8 +318,8 @@ module HexaPDF
318
318
  value[name] = document.add(obj)
319
319
  elsif !field.indirect && obj.kind_of?(HexaPDF::Object) && obj.indirect?
320
320
  yield("Field #{name} needs to be a direct object", true)
321
- document.delete(obj)
322
321
  value[name] = obj.value
322
+ document.delete(obj)
323
323
  end
324
324
  end
325
325
  end
@@ -213,7 +213,9 @@ module HexaPDF
213
213
  end
214
214
 
215
215
  handler = handler.new(document)
216
- document.trailer[:Encrypt] = handler.set_up_decryption(dict, **options)
216
+ dict = document.trailer[:Encrypt] = handler.set_up_decryption(dict, **options)
217
+ HexaPDF::Object.make_direct(dict.value)
218
+ document.revisions.current.update(dict)
217
219
  document.revisions.each do |r|
218
220
  loader = r.loader
219
221
  r.loader = lambda do |xref_entry|
@@ -141,6 +141,24 @@ module HexaPDF
141
141
  end
142
142
  end
143
143
 
144
+ # Makes sure that the object itself as well as all nested values are direct objects.
145
+ #
146
+ # If an indirect object is found, it is turned into a direct object and the indirect object is
147
+ # deleted from the document.
148
+ def self.make_direct(object)
149
+ if object.kind_of?(HexaPDF::Object) && object.indirect?
150
+ object_to_delete = object
151
+ object = object.value
152
+ object_to_delete.document.delete(object_to_delete)
153
+ end
154
+ if object.kind_of?(Hash)
155
+ object.transform_values! {|val| make_direct(val) }
156
+ elsif object.kind_of?(Array)
157
+ object.map! {|val| make_direct(val) }
158
+ end
159
+ object
160
+ end
161
+
144
162
  # The wrapped HexaPDF::PDFData value.
145
163
  #
146
164
  # This attribute is not part of the public API!
@@ -35,6 +35,8 @@
35
35
  #++
36
36
 
37
37
  require 'time'
38
+ require 'hexapdf/object'
39
+ require 'hexapdf/stream'
38
40
  require 'hexapdf/tokenizer'
39
41
  require 'hexapdf/filter'
40
42
  require 'hexapdf/utils/lru_cache'
@@ -227,8 +227,8 @@ module HexaPDF
227
227
  # Note: Rich text fields are currently not supported!
228
228
  def create_text_appearances
229
229
  default_resources = @document.acro_form.default_resources
230
- font, font_size = retrieve_font_information(default_resources)
231
- style = HexaPDF::Layout::Style.new(font: font)
230
+ font, font_size, font_color = retrieve_font_information(default_resources)
231
+ style = HexaPDF::Layout::Style.new(font: font, fill_color: font_color)
232
232
  border_style = @widget.border_style
233
233
  padding = [1, border_style.width].max
234
234
 
@@ -482,7 +482,7 @@ module HexaPDF
482
482
 
483
483
  # Returns the font wrapper and font size to be used for a variable text field.
484
484
  def retrieve_font_information(resources)
485
- font_name, font_size = @field.parse_default_appearance_string
485
+ font_name, font_size, font_color = @field.parse_default_appearance_string
486
486
  font_object = resources.font(font_name) rescue nil
487
487
  font = font_object&.font_wrapper
488
488
  unless font
@@ -498,7 +498,7 @@ module HexaPDF
498
498
  raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
499
499
  end
500
500
  end
501
- [font, font_size]
501
+ [font, font_size, font_color]
502
502
  end
503
503
 
504
504
  # Calculates the font size for text fields based on the font and font size of the default
@@ -157,8 +157,8 @@ module HexaPDF
157
157
  # The optional keyword arguments allow setting often used properties of the field:
158
158
  #
159
159
  # +font+::
160
- # The font that should be used for the text of the field. If +font_size+ or
161
- # +font_options+ is specified but +font+ isn't, the font Helvetica is used.
160
+ # The font that should be used for the text of the field. If +font_size+, +font_options+
161
+ # or +font_color+ is specified but +font+ isn't, the font Helvetica is used.
162
162
  #
163
163
  # If no font is set on the text field, the default font properties of the AcroForm form
164
164
  # are used. Note that field specific or form specific font properties have to be set.
@@ -169,15 +169,20 @@ module HexaPDF
169
169
  # A hash with font options like :variant that should be used.
170
170
  #
171
171
  # +font_size+::
172
- # The font size that should be used. If +font+ or +font_options+ is specified but
173
- # +font_size+ isn't, font size defaults to 0 (= auto-sizing).
172
+ # The font size that should be used. If +font+, +font_options+ or +font_color+ is
173
+ # specified but +font_size+ isn't, font size defaults to 0 (= auto-sizing).
174
+ #
175
+ # +font_color+::
176
+ # The font color that should be used. If +font+, +font_options+ or +font_size+ is
177
+ # specified but +font_color+ isn't, font color defaults to 0 (i.e. black).
174
178
  #
175
179
  # +align+::
176
180
  # The alignment of the text, either :left, :center or :right.
177
- def create_text_field(name, font: nil, font_options: nil, font_size: nil, align: nil)
181
+ def create_text_field(name, font: nil, font_options: nil, font_size: nil, font_color: nil,
182
+ align: nil)
178
183
  create_field(name, :Tx) do |field|
179
184
  apply_variable_text_properties(field, font: font, font_options: font_options,
180
- font_size: font_size, align: align)
185
+ font_size: font_size, font_color: font_color, align: align)
181
186
  end
182
187
  end
183
188
 
@@ -189,11 +194,11 @@ module HexaPDF
189
194
  # The optional keyword arguments allow setting often used properties of the field, see
190
195
  # #create_text_field for details.
191
196
  def create_multiline_text_field(name, font: nil, font_options: nil, font_size: nil,
192
- align: nil)
197
+ font_color: nil, align: nil)
193
198
  create_field(name, :Tx) do |field|
194
199
  field.initialize_as_multiline_text_field
195
200
  apply_variable_text_properties(field, font: font, font_options: font_options,
196
- font_size: font_size, align: align)
201
+ font_size: font_size, font_color: font_color, align: align)
197
202
  end
198
203
  end
199
204
 
@@ -208,11 +213,11 @@ module HexaPDF
208
213
  # The optional keyword arguments allow setting often used properties of the field, see
209
214
  # #create_text_field for details.
210
215
  def create_comb_text_field(name, max_chars:, font: nil, font_options: nil, font_size: nil,
211
- align: nil)
216
+ font_color: nil, align: nil)
212
217
  create_field(name, :Tx) do |field|
213
218
  field.initialize_as_comb_text_field
214
219
  apply_variable_text_properties(field, font: font, font_options: font_options,
215
- font_size: font_size, align: align)
220
+ font_size: font_size, font_color: font_color, align: align)
216
221
  field[:MaxLen] = max_chars
217
222
  end
218
223
  end
@@ -224,11 +229,12 @@ module HexaPDF
224
229
  #
225
230
  # The optional keyword arguments allow setting often used properties of the field, see
226
231
  # #create_text_field for details.
227
- def create_file_select_field(name, font: nil, font_options: nil, font_size: nil, align: nil)
232
+ def create_file_select_field(name, font: nil, font_options: nil, font_size: nil,
233
+ font_color: nil, align: nil)
228
234
  create_field(name, :Tx) do |field|
229
235
  field.initialize_as_file_select_field
230
236
  apply_variable_text_properties(field, font: font, font_options: font_options,
231
- font_size: font_size, align: align)
237
+ font_size: font_size, font_color: font_color, align: align)
232
238
  end
233
239
  end
234
240
 
@@ -239,11 +245,12 @@ module HexaPDF
239
245
  #
240
246
  # The optional keyword arguments allow setting often used properties of the field, see
241
247
  # #create_text_field for details.
242
- def create_password_field(name, font: nil, font_options: nil, font_size: nil, align: nil)
248
+ def create_password_field(name, font: nil, font_options: nil, font_size: nil,
249
+ font_color: nil, align: nil)
243
250
  create_field(name, :Tx) do |field|
244
251
  field.initialize_as_password_field
245
252
  apply_variable_text_properties(field, font: font, font_options: font_options,
246
- font_size: font_size, align: align)
253
+ font_size: font_size, font_color: font_color, align: align)
247
254
  end
248
255
  end
249
256
 
@@ -280,13 +287,13 @@ module HexaPDF
280
287
  # +font+, +font_options+, +font_size+ and +align+::
281
288
  # See #create_text_field
282
289
  def create_combo_box(name, option_items: nil, editable: nil, font: nil,
283
- font_options: nil, font_size: nil, align: nil)
290
+ font_options: nil, font_size: nil, font_color: nil, align: nil)
284
291
  create_field(name, :Ch) do |field|
285
292
  field.initialize_as_combo_box
286
293
  field.option_items = option_items if option_items
287
294
  field.flag(:edit) if editable
288
295
  apply_variable_text_properties(field, font: font, font_options: font_options,
289
- font_size: font_size, align: align)
296
+ font_size: font_size, font_color: font_color, align: align)
290
297
  end
291
298
  end
292
299
 
@@ -306,13 +313,13 @@ module HexaPDF
306
313
  # +font+, +font_options+, +font_size+ and +align+::
307
314
  # See #create_text_field.
308
315
  def create_list_box(name, option_items: nil, multi_select: nil, font: nil,
309
- font_options: nil, font_size: nil, align: nil)
316
+ font_options: nil, font_size: nil, font_color: nil, align: nil)
310
317
  create_field(name, :Ch) do |field|
311
318
  field.initialize_as_list_box
312
319
  field.option_items = option_items if option_items
313
320
  field.flag(:multi_select) if multi_select
314
321
  apply_variable_text_properties(field, font: font, font_options: font_options,
315
- font_size: font_size, align: align)
322
+ font_size: font_size, font_color: font_color, align: align)
316
323
  end
317
324
  end
318
325
 
@@ -322,13 +329,16 @@ module HexaPDF
322
329
  type: :XXResources)
323
330
  end
324
331
 
325
- # Sets the global default appearance string using the provided values.
332
+ # Sets the global default appearance string using the provided values or the default values
333
+ # which provide a sane default.
326
334
  #
327
- # The default argument values are a sane default. If +font_size+ is set to 0, the font size
328
- # is calculated using the height/width of the field.
329
- def set_default_appearance_string(font: 'Helvetica', font_size: 0)
330
- name = default_resources.add_font(document.fonts.add(font).pdf_object)
331
- self[:DA] = "0 g /#{name} #{font_size} Tf"
335
+ # See VariableTextField::create_appearance_string for information on the arguments.
336
+ def set_default_appearance_string(font: 'Helvetica', font_options: {}, font_size: 0,
337
+ font_color: 0)
338
+ self[:DA] = VariableTextField.create_appearance_string(document, font: font,
339
+ font_options: font_options,
340
+ font_size: font_size,
341
+ font_color: font_color)
332
342
  end
333
343
 
334
344
  # Sets the /NeedAppearances field to +true+.
@@ -420,11 +430,12 @@ module HexaPDF
420
430
 
421
431
  # Applies the given variable field properties to the field.
422
432
  def apply_variable_text_properties(field, font: nil, font_options: nil, font_size: nil,
423
- align: nil)
424
- if font || font_options || font_size
433
+ font_color: nil, align: nil)
434
+ if font || font_options || font_size || font_color
425
435
  field.set_default_appearance_string(font: font || 'Helvetica',
426
436
  font_options: font_options || {},
427
- font_size: font_size || 0)
437
+ font_size: font_size || 0,
438
+ font_color: font_color || 0)
428
439
  end
429
440
  field.text_alignment(align) if align
430
441
  end
@@ -437,7 +448,7 @@ module HexaPDF
437
448
  yield("When the field /DA is present, the field /DR must also be present")
438
449
  return
439
450
  end
440
- font_name, _ = VariableTextField.parse_appearance_string(da)
451
+ font_name, = VariableTextField.parse_appearance_string(da)
441
452
  if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
442
453
  yield("The font specified in /DA is not in the /DR resource dictionary")
443
454
  end
@@ -37,7 +37,7 @@
37
37
  require 'hexapdf/dictionary'
38
38
  require 'hexapdf/stream'
39
39
  require 'hexapdf/error'
40
- require 'hexapdf/content/parser'
40
+ require 'hexapdf/content'
41
41
 
42
42
  module HexaPDF
43
43
  module Type
@@ -61,14 +61,53 @@ module HexaPDF
61
61
 
62
62
  UNSET_ARG = ::Object.new # :nodoc:
63
63
 
64
- # Parses the given appearance string. If no block is given, the appearance string is
65
- # searched for font name and font size both of which are returend. Otherwise the block is
66
- # called with each found content stream operator and has to handle them themselves.
64
+ # Creates an AcroForm appearance string for the HexaPDF +document+ from the given arguments
65
+ # and returns it.
66
+ #
67
+ # +font+::
68
+ # The name of the font.
69
+ #
70
+ # +font_options+::
71
+ # Additional font options like :variant used when loading the font. See
72
+ # HexaPDF::Document::Fonts#add
73
+ #
74
+ # +font_size+::
75
+ # The font size. If this is set to 0, the font size is calculated using the height/width
76
+ # of the field.
77
+ #
78
+ # +font_color+::
79
+ # The font color. See HexaPDF::Content::ColorSpace.device_color_from_specification for
80
+ # allowed values.
81
+ def self.create_appearance_string(document, font: 'Helvetica', font_options: {},
82
+ font_size: 0, font_color: 0)
83
+ name = document.acro_form(create: true).default_resources.
84
+ add_font(document.fonts.add(font, **font_options).pdf_object)
85
+ font_color = HexaPDF::Content::ColorSpace.device_color_from_specification(font_color)
86
+ color_string = HexaPDF::Content::ColorSpace.serialize_device_color(font_color)
87
+ "#{color_string.chomp} /#{name} #{font_size} Tf"
88
+ end
89
+
90
+ # :call-seq:
91
+ # VariableTextField.parse_appearance_string(string) -> [font_name, font_size, font_color]
92
+ # VariableTextField.parse_appearance_string(string) {|obj, params| block } -> nil
93
+ #
94
+ # Parses the given appearance string.
95
+ #
96
+ # If no block is given, the appearance string is searched for font name, font size and font
97
+ # color all of which are returned. Otherwise the block is called with each found content
98
+ # stream operator and has to handle them itself.
67
99
  def self.parse_appearance_string(appearance_string, &block) # :yield: obj, params
68
- font_params = nil
69
- block ||= lambda {|obj, params| font_params = params.dup if obj == :Tf }
100
+ font_params = [nil, nil, nil]
101
+ block ||= lambda do |obj, params|
102
+ case obj
103
+ when :Tf
104
+ font_params[0, 2] = params
105
+ when :rg, :g, :k
106
+ font_params[2] = HexaPDF::Content::ColorSpace.prenormalized_device_color(params)
107
+ end
108
+ end
70
109
  HexaPDF::Content::Parser.parse(appearance_string.sub(/\/\//, '/'), &block)
71
- font_params
110
+ block_given? ? nil : font_params
72
111
  end
73
112
 
74
113
  # :call-seq:
@@ -99,21 +138,20 @@ module HexaPDF
99
138
  end
100
139
  end
101
140
 
102
- # Sets the default appearance string using the provided values.
103
- #
104
- # The default argument values are a sane default. If +font_size+ is set to 0, the font size
105
- # is calculated using the height/width of the field.
141
+ # Sets the default appearance string using the provided values or the default values which
142
+ # provide a sane default.
106
143
  #
107
- # Use the +font_options+ hash to provide font options like :variant, see
108
- # HexaPDF::Document::Fonts#add.
109
- def set_default_appearance_string(font: 'Helvetica', font_options: {}, font_size: 0)
110
- name = document.acro_form(create: true).default_resources.
111
- add_font(document.fonts.add(font, **font_options).pdf_object)
112
- self[:DA] = "0 g /#{name} #{font_size} Tf"
144
+ # See ::create_appearance_string for information on the arguments.
145
+ def set_default_appearance_string(font: 'Helvetica', font_options: {}, font_size: 0,
146
+ font_color: 0)
147
+ self[:DA] = self.class.create_appearance_string(document, font: font,
148
+ font_options: font_options,
149
+ font_size: font_size,
150
+ font_color: font_color)
113
151
  end
114
152
 
115
153
  # Parses the default appearance string and returns an array containing [font_name,
116
- # font_size].
154
+ # font_size, font_color].
117
155
  #
118
156
  # The default appearance string is taken from the field or, if not set, the default
119
157
  # appearance string of the form.
@@ -268,6 +268,7 @@ module HexaPDF
268
268
  style ||= (field.check_box? ? :check : :cicrle)
269
269
  size ||= 0
270
270
  color = Content::ColorSpace.device_color_from_specification(color || 0)
271
+ serialized_color = Content::ColorSpace.serialize_device_color(color)
271
272
 
272
273
  self[:MK] ||= {}
273
274
  self[:MK][:CA] = case style
@@ -281,13 +282,6 @@ module HexaPDF
281
282
  else
282
283
  raise ArgumentError, "Unknown value #{style} for argument 'style'"
283
284
  end
284
- operator = case color.color_space.family
285
- when :DeviceRGB then :rg
286
- when :DeviceGray then :g
287
- when :DeviceCMYK then :k
288
- end
289
- serialized_color = Content::Operator::DEFAULT_OPERATORS[operator].
290
- serialize(HexaPDF::Serializer.new, *color.components)
291
285
  self[:DA] = "/ZaDb #{size} Tf #{serialized_color}".strip
292
286
  else
293
287
  style = case self[:MK]&.[](:CA)
@@ -306,16 +300,10 @@ module HexaPDF
306
300
  end
307
301
  end
308
302
  size = 0
309
- color = [0]
303
+ color = HexaPDF::Content::ColorSpace.prenormalized_device_color([0])
310
304
  if (da = self[:DA] || field[:DA])
311
- HexaPDF::Type::AcroForm::VariableTextField.parse_appearance_string(da) do |obj, params|
312
- case obj
313
- when :rg, :g, :k then color = params.dup
314
- when :Tf then size = params[1]
315
- end
316
- end
305
+ _, size, color = HexaPDF::Type::AcroForm::VariableTextField.parse_appearance_string(da)
317
306
  end
318
- color = HexaPDF::Content::ColorSpace.prenormalized_device_color(color)
319
307
 
320
308
  MarkerStyle.new(style, size, color)
321
309
  end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.17.3'
40
+ VERSION = '0.18.0'
41
41
 
42
42
  end
@@ -55,21 +55,29 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
55
55
 
56
56
  it "draws the arc onto the canvas" do
57
57
  {
58
- [false, false] => {cx: 100, cy: 50, start_angle: 180, end_angle: 270, clockwise: false},
59
- [false, true] => {cx: 50, cy: 25, start_angle: 90, end_angle: 0, clockwise: true},
60
- [true, false] => {cx: 50, cy: 25, start_angle: 90, end_angle: 360, clockwise: false},
61
- [true, true] => {cx: 100, cy: 50, start_angle: 180, end_angle: -90, clockwise: true},
58
+ [false, false] => {cx: 100, cy: 50, a: 50, b: 25, start_angle: 180, end_angle: 270, clockwise: false},
59
+ [false, true] => {cx: 50, cy: 25, a: 50, b: 25, start_angle: 90, end_angle: 0, clockwise: true},
60
+ [true, false] => {cx: 50, cy: 25, a: 50, b: 25, start_angle: 90, end_angle: 360, clockwise: false},
61
+ [true, true] => {cx: 0, cy: 0, a: 40, b: 30, start_angle: 60, end_angle: 120},
62
62
  }.each do |(large_arc, clockwise), data|
63
63
  @page.delete(:Contents)
64
64
  canvas = @page.canvas
65
- canvas.draw(:arc, a: 50, b: 25, inclination: 0, **data)
65
+ arc = canvas.graphic_object(:arc, **data)
66
+ canvas.draw(arc)
66
67
  arc_data = @page.contents
67
68
 
68
69
  canvas.contents.clear
69
70
  assert(@page.contents.empty?)
70
- canvas.move_to(50.0, 50.0)
71
- canvas.draw(:endpoint_arc, x: 100, y: 25, a: 50, b: 25, inclination: 0,
72
- large_arc: large_arc, clockwise: clockwise)
71
+ canvas.move_to(*arc.start_point)
72
+ earc = canvas.graphic_object(:endpoint_arc, x: arc.end_point[0], y: arc.end_point[1],
73
+ a: data[:a], b: data[:b], inclination: data[:inclination] || 0,
74
+ large_arc: large_arc, clockwise: clockwise)
75
+ canvas.draw(earc)
76
+ narc = canvas.graphic_object(:arc, **earc.send(:compute_arc_values, *arc.start_point))
77
+ assert_in_delta(arc.start_point[0], narc.start_point[0], 0.0001)
78
+ assert_in_delta(arc.start_point[1], narc.start_point[1], 0.0001)
79
+ assert_in_delta(arc.end_point[0], narc.end_point[0], 0.0001)
80
+ assert_in_delta(arc.end_point[1], narc.end_point[1], 0.0001)
73
81
  assert_equal(arc_data, @page.contents)
74
82
  end
75
83
  end
@@ -99,6 +99,32 @@ describe HexaPDF::Content::ColorSpace do
99
99
  end
100
100
  end
101
101
 
102
+ describe "self.serialize_device_color" do
103
+ it "works for device gray colors" do
104
+ color = @class.device_color_from_specification(0.5)
105
+ assert_equal("0.5 g\n", @class.serialize_device_color(color))
106
+ assert_equal("0.5 G\n", @class.serialize_device_color(color, type: :stroke))
107
+ end
108
+
109
+ it "works for device RGB colors" do
110
+ color = @class.device_color_from_specification("red")
111
+ assert_equal("1.0 0.0 0.0 rg\n", @class.serialize_device_color(color))
112
+ assert_equal("1.0 0.0 0.0 RG\n", @class.serialize_device_color(color, type: :stroke))
113
+ end
114
+
115
+ it "works for device CMYK colors" do
116
+ color = @class.device_color_from_specification([100, 100, 100, 0])
117
+ assert_equal("1.0 1.0 1.0 0.0 k\n", @class.serialize_device_color(color))
118
+ assert_equal("1.0 1.0 1.0 0.0 K\n", @class.serialize_device_color(color, type: :stroke))
119
+ end
120
+
121
+ it "fails if no device color is provided" do
122
+ assert_raises(ArgumentError) do
123
+ @class.serialize_device_color(@class::Universal.new([]).default_color)
124
+ end
125
+ end
126
+ end
127
+
102
128
  it "returns a device color object for prenormalized color values" do
103
129
  assert_equal([5, 6, 7], @class.prenormalized_device_color([5, 6, 7]).components)
104
130
  end
@@ -107,9 +107,11 @@ describe HexaPDF::Encryption::SecurityHandler do
107
107
  end
108
108
 
109
109
  it "updates the trailer's /Encrypt entry to be wrapped by an encryption dictionary" do
110
- @document.trailer[:Encrypt] = {Filter: :Test, V: 1}
110
+ @document.trailer[:Encrypt] = {Filter: :Test,
111
+ V: HexaPDF::Object.new(1, oid: 1, document: @document)}
111
112
  HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
112
113
  assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, @document.trailer[:Encrypt])
114
+ assert_equal({Filter: :Test, V: 1}, @document.trailer[:Encrypt].value)
113
115
  end
114
116
 
115
117
  it "returns the frozen security handler" do
@@ -20,6 +20,7 @@ describe HexaPDF::Dictionary do
20
20
  end
21
21
 
22
22
  def delete(_obj)
23
+ _obj.data.value = nil
23
24
  end
24
25
 
25
26
  def wrap(obj, type:)
@@ -45,6 +45,34 @@ describe HexaPDF::Object do
45
45
  end
46
46
  end
47
47
 
48
+ describe "class.make_direct" do
49
+ before do
50
+ @doc = HexaPDF::Document.new
51
+ end
52
+
53
+ it "doesn't touch wrapped direct objects" do
54
+ obj = HexaPDF::Object.new(5)
55
+ assert_same(obj, HexaPDF::Object.make_direct(obj))
56
+ end
57
+
58
+ it "works for simple values" do
59
+ obj = HexaPDF::Object.new(5, oid: 1, document: @doc)
60
+ assert_same(5, HexaPDF::Object.make_direct(obj))
61
+ end
62
+
63
+ it "works for hashes" do
64
+ obj = HexaPDF::Dictionary.new({a: 5, b: HexaPDF::Object.new(:a, oid: 3, document: @doc)},
65
+ oid: 1, document: @doc)
66
+ assert_equal({a: 5, b: :a}, HexaPDF::Object.make_direct(obj))
67
+ end
68
+
69
+ it "works for arrays" do
70
+ obj = HexaPDF::PDFArray.new([:b, HexaPDF::Object.new(:a, oid: 3, document: @doc)],
71
+ oid: 1, document: @doc)
72
+ assert_equal([:b, :a], HexaPDF::Object.make_direct(obj))
73
+ end
74
+ end
75
+
48
76
  describe "initialize" do
49
77
  it "uses a simple value as is" do
50
78
  obj = HexaPDF::Object.new(5)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.17.3)>>
43
+ <</Producer(HexaPDF version 0.18.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.17.3)>>
75
+ <</Producer(HexaPDF version 0.18.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -499,6 +499,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
499
499
 
500
500
  it "creates the /N appearance stream according to the set string" do
501
501
  @field.field_value = 'Text'
502
+ @field.set_default_appearance_string(font_color: "red")
502
503
  @generator.create_appearances
503
504
  assert_operators(@widget[:AP][:N].stream,
504
505
  [[:begin_marked_content, [:Tx]],
@@ -507,6 +508,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
507
508
  [:clip_path_non_zero],
508
509
  [:end_path],
509
510
  [:set_font_and_size, [:F1, 6.641436]],
511
+ [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
510
512
  [:begin_text],
511
513
  [:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
512
514
  [:show_text, ["Text"]],
@@ -556,6 +558,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
556
558
 
557
559
  it "creates the /N appearance stream according to the set string" do
558
560
  @field.field_value = "Test\nValue"
561
+ @field.set_default_appearance_string(font_size: 10, font_color: "red")
559
562
  @generator.create_appearances
560
563
  assert_operators(@widget[:AP][:N].stream,
561
564
  [[:begin_marked_content, [:Tx]],
@@ -566,6 +569,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
566
569
  [:save_graphics_state],
567
570
  [:set_leading, [11.5625]],
568
571
  [:set_font_and_size, [:F1, 10]],
572
+ [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
569
573
  [:begin_text],
570
574
  [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
571
575
  [:show_text, ['Test']],
@@ -653,6 +657,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
653
657
 
654
658
  it "creates the /N appearance stream according to the set string" do
655
659
  @field.field_value = 'Text'
660
+ @field.set_default_appearance_string(font_size: 10, font_color: "red")
656
661
  @generator.create_appearances
657
662
  assert_operators(@widget[:AP][:N].stream,
658
663
  [[:begin_marked_content, [:Tx]],
@@ -661,6 +666,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
661
666
  [:clip_path_non_zero],
662
667
  [:end_path],
663
668
  [:set_font_and_size, [:F1, 10]],
669
+ [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
664
670
  [:begin_text],
665
671
  [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
666
672
  [:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
@@ -721,6 +727,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
721
727
  it "creates the /N appearance stream" do
722
728
  @field[:I] = [1, 2]
723
729
  @field[:V] = ['b', 'c']
730
+ @field.set_default_appearance_string(font_size: 12, font_color: "red")
724
731
  @generator.create_appearances
725
732
  assert_operators(@widget[:AP][:N].stream,
726
733
  [[:begin_marked_content, [:Tx]],
@@ -734,7 +741,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
734
741
  [:save_graphics_state],
735
742
  [:set_leading, [13.875]],
736
743
  [:set_font_and_size, [:F1, 12]],
737
- [:set_device_gray_non_stroking_color, [0.0]],
744
+ [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
738
745
  [:begin_text],
739
746
  [:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
740
747
  [:show_text, ["a"]],
@@ -8,6 +8,7 @@ describe HexaPDF::Type::AcroForm::Form do
8
8
  before do
9
9
  @doc = HexaPDF::Document.new
10
10
  @acro_form = @doc.add({Fields: []}, type: :XXAcroForm)
11
+ @doc.catalog[:AcroForm] = @acro_form
11
12
  end
12
13
 
13
14
  describe "signature flags" do
@@ -140,12 +141,13 @@ describe HexaPDF::Type::AcroForm::Form do
140
141
 
141
142
  def applies_variable_text_properties(method, **args)
142
143
  field = @acro_form.send(method, "field", **args, font: 'Times')
143
- font_name, font_size = field.parse_default_appearance_string
144
+ font_name, font_size, font_color = field.parse_default_appearance_string
144
145
  assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
145
146
  assert_equal(0, font_size)
147
+ assert_equal(HexaPDF::Content::ColorSpace::DeviceGray.new.color(0), font_color)
146
148
 
147
149
  field = @acro_form.send(method, "field", **args, font_options: {variant: :bold})
148
- font_name, font_size = field.parse_default_appearance_string
150
+ font_name, = field.parse_default_appearance_string
149
151
  assert_equal(:'Helvetica-Bold', @acro_form.default_resources.font(font_name)[:BaseFont])
150
152
 
151
153
  field = @acro_form.send(method, "field", **args, font_size: 10)
@@ -153,6 +155,10 @@ describe HexaPDF::Type::AcroForm::Form do
153
155
  assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
154
156
  assert_equal(10, font_size)
155
157
 
158
+ field = @acro_form.send(method, "field", **args, font_color: "red")
159
+ _, _, font_color = field.parse_default_appearance_string
160
+ assert_equal(HexaPDF::Content::ColorSpace::DeviceRGB.new.color(255, 0, 0), font_color)
161
+
156
162
  field = @acro_form.send(method, "field", **args, font: 'Courier', font_size: 10, align: :center)
157
163
  font_name, font_size = field.parse_default_appearance_string
158
164
  assert_equal(:Courier, @acro_form.default_resources.font(font_name)[:BaseFont])
@@ -229,16 +235,17 @@ describe HexaPDF::Type::AcroForm::Form do
229
235
  describe "set_default_appearance_string" do
230
236
  it "uses sane default values if no arguments are provided" do
231
237
  @acro_form.set_default_appearance_string
232
- assert_equal("0 g /F1 0 Tf", @acro_form[:DA])
238
+ assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
233
239
  font = @acro_form.default_resources.font(:F1)
234
240
  assert(font)
235
241
  assert_equal(:Helvetica, font[:BaseFont])
236
242
  end
237
243
 
238
- it "allows specifying the used font and font size" do
239
- @acro_form.set_default_appearance_string(font: 'Times', font_size: 10)
240
- assert_equal("0 g /F1 10 Tf", @acro_form[:DA])
241
- assert_equal(:'Times-Roman', @acro_form.default_resources.font(:F1)[:BaseFont])
244
+ it "allows specifying the used font, font size and font color" do
245
+ @acro_form.set_default_appearance_string(font: 'Times', font_options: {variant: :bold},
246
+ font_size: 10, font_color: "red")
247
+ assert_equal("1.0 0.0 0.0 rg /F1 10 Tf", @acro_form[:DA])
248
+ assert_equal(:'Times-Bold', @acro_form.default_resources.font(:F1)[:BaseFont])
242
249
  end
243
250
  end
244
251
 
@@ -333,7 +340,7 @@ describe HexaPDF::Type::AcroForm::Form do
333
340
 
334
341
  it "set the default appearance string, though optional, to a valid value to avoid problems" do
335
342
  assert(@acro_form.validate)
336
- assert_equal("0 g /F1 0 Tf", @acro_form[:DA])
343
+ assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
337
344
  end
338
345
 
339
346
  describe "automatically creates the terminal fields; appearances" do
@@ -31,7 +31,7 @@ describe HexaPDF::Type::AcroForm::VariableTextField do
31
31
  end
32
32
  end
33
33
 
34
- describe "set_default_appearance_string" do
34
+ describe "set_default_appearance_string / self.create_appearance_string" do
35
35
  it "creates the AcroForm object if it doesn't exist" do
36
36
  @doc.catalog.delete(:AcroForm)
37
37
  @field.set_default_appearance_string
@@ -40,7 +40,7 @@ describe HexaPDF::Type::AcroForm::VariableTextField do
40
40
 
41
41
  it "uses sane default values if no arguments are provided" do
42
42
  @field.set_default_appearance_string
43
- assert_equal("0 g /F1 0 Tf", @field[:DA])
43
+ assert_equal("0.0 g /F1 0 Tf", @field[:DA])
44
44
  font = @doc.acro_form.default_resources.font(:F1)
45
45
  assert(font)
46
46
  assert_equal(:Helvetica, font[:BaseFont])
@@ -48,36 +48,46 @@ describe HexaPDF::Type::AcroForm::VariableTextField do
48
48
 
49
49
  it "allows specifying the font" do
50
50
  @field.set_default_appearance_string(font: 'Times')
51
- assert_equal("0 g /F2 0 Tf", @field[:DA])
51
+ assert_equal("0.0 g /F2 0 Tf", @field[:DA])
52
52
  assert_equal(:'Times-Roman', @doc.acro_form.default_resources.font(:F2)[:BaseFont])
53
53
  end
54
54
 
55
55
  it "allows specifying the font options" do
56
56
  @field.set_default_appearance_string(font_options: {variant: :italic})
57
- assert_equal("0 g /F2 0 Tf", @field[:DA])
57
+ assert_equal("0.0 g /F2 0 Tf", @field[:DA])
58
58
  assert_equal(:'Helvetica-Oblique', @doc.acro_form.default_resources.font(:F2)[:BaseFont])
59
59
  end
60
60
 
61
61
  it "allows specifying the font size" do
62
62
  @field.set_default_appearance_string(font_size: 10)
63
- assert_equal("0 g /F1 10 Tf", @field[:DA])
63
+ assert_equal("0.0 g /F1 10 Tf", @field[:DA])
64
+ end
65
+
66
+ it "allows specifying the font color" do
67
+ @field.set_default_appearance_string(font_color: "red")
68
+ assert_equal("1.0 0.0 0.0 rg /F1 0 Tf", @field[:DA])
64
69
  end
65
70
  end
66
71
 
67
72
  describe "parse_default_appearance_string" do
73
+ before do
74
+ @color = HexaPDF::Content::ColorSpace.prenormalized_device_color([1])
75
+ end
76
+
68
77
  it "parses the default appearance string of the field" do
69
78
  @field[:DA] = "1 g //F1 20 Tf 5 w /F2 10 Tf"
70
- assert_equal([:F2, 10], @field.parse_default_appearance_string)
79
+ assert_equal([:F2, 10, @color], @field.parse_default_appearance_string)
71
80
  end
72
81
 
73
82
  it "uses the default appearance string of a parent field" do
74
83
  parent = @doc.add({DA: "/F1 15 Tf"}, type: :XXAcroFormField)
75
84
  @field[:Parent] = parent
76
- assert_equal([:F1, 15], @field.parse_default_appearance_string)
85
+ assert_equal([:F1, 15, nil], @field.parse_default_appearance_string)
77
86
  end
78
87
 
79
88
  it "uses the global default appearance string" do
80
- assert_equal([:F1, 0], @field.parse_default_appearance_string)
89
+ assert_equal([:F1, 0, HexaPDF::Content::ColorSpace.prenormalized_device_color([0])],
90
+ @field.parse_default_appearance_string)
81
91
  end
82
92
 
83
93
  it "fails if no /DA value is set" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.3
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-31 00:00:00.000000000 Z
11
+ date: 2021-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse