hexapdf 0.17.3 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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