hexapdf 0.42.0 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/Rakefile +1 -1
- data/examples/030-pdfa.rb +1 -0
- data/lib/hexapdf/composer.rb +1 -0
- data/lib/hexapdf/dictionary.rb +3 -3
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/document.rb +14 -1
- data/lib/hexapdf/encryption.rb +17 -0
- data/lib/hexapdf/layout/box.rb +161 -61
- data/lib/hexapdf/layout/box_fitter.rb +4 -3
- data/lib/hexapdf/layout/column_box.rb +23 -25
- data/lib/hexapdf/layout/container_box.rb +3 -3
- data/lib/hexapdf/layout/frame.rb +13 -95
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/line.rb +4 -0
- data/lib/hexapdf/layout/list_box.rb +12 -20
- data/lib/hexapdf/layout/style.rb +5 -1
- data/lib/hexapdf/layout/table_box.rb +48 -55
- data/lib/hexapdf/layout/text_box.rb +38 -39
- data/lib/hexapdf/parser.rb +23 -17
- data/lib/hexapdf/type/acro_form/form.rb +78 -27
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +82 -37
- data/test/hexapdf/layout/test_box_fitter.rb +10 -3
- data/test/hexapdf/layout/test_column_box.rb +7 -13
- data/test/hexapdf/layout/test_container_box.rb +1 -1
- data/test/hexapdf/layout/test_frame.rb +0 -48
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +25 -26
- data/test/hexapdf/layout/test_table_box.rb +39 -53
- data/test/hexapdf/layout/test_text_box.rb +65 -67
- data/test/hexapdf/test_composer.rb +6 -0
- data/test/hexapdf/test_dictionary.rb +6 -4
- data/test/hexapdf/test_parser.rb +20 -0
- data/test/hexapdf/type/acro_form/test_form.rb +63 -2
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- metadata +2 -2
@@ -43,6 +43,11 @@ module HexaPDF
|
|
43
43
|
# objects of a Frame.
|
44
44
|
#
|
45
45
|
# This class uses TextLayouter behind the scenes to do the hard work.
|
46
|
+
#
|
47
|
+
# == Used Box Properties
|
48
|
+
#
|
49
|
+
# The spacing after the last line can be controlled via the style property +last_line_gap+. Also
|
50
|
+
# see TextLayouter#style for other style properties taken into account.
|
46
51
|
class TextBox < Box
|
47
52
|
|
48
53
|
# Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
|
@@ -52,6 +57,7 @@ module HexaPDF
|
|
52
57
|
@tl = TextLayouter.new(style)
|
53
58
|
@items = items
|
54
59
|
@result = nil
|
60
|
+
@x_offset = 0
|
55
61
|
end
|
56
62
|
|
57
63
|
# Returns the text that will be drawn.
|
@@ -66,21 +72,27 @@ module HexaPDF
|
|
66
72
|
true
|
67
73
|
end
|
68
74
|
|
75
|
+
# :nodoc:
|
76
|
+
def draw(canvas, x, y)
|
77
|
+
super(canvas, x + @x_offset, y)
|
78
|
+
end
|
79
|
+
|
80
|
+
# :nodoc:
|
81
|
+
def empty?
|
82
|
+
super && (!@result || @result.lines.empty?)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
69
87
|
# Fits the text box into the Frame.
|
70
88
|
#
|
71
89
|
# Depending on the 'position' style property, the text is either fit into the current region
|
72
90
|
# of the frame using +available_width+ and +available_height+, or fit to the shape of the
|
73
91
|
# frame starting from the top (when 'position' is set to :flow).
|
74
|
-
|
75
|
-
# The spacing after the last line can be controlled via the style property +last_line_gap+.
|
76
|
-
#
|
77
|
-
# Also see TextLayouter#style for other style properties taken into account.
|
78
|
-
def fit(available_width, available_height, frame)
|
79
|
-
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
80
|
-
(@initial_height > 0 && @initial_height > available_height)
|
81
|
-
|
92
|
+
def fit_content(available_width, available_height, frame)
|
82
93
|
frame = frame.child_frame(box: self)
|
83
|
-
@width = @height = 0
|
94
|
+
@width = @x_offset = @height = 0
|
95
|
+
|
84
96
|
@result = if style.position == :flow
|
85
97
|
@tl.fit(@items, frame.width_specification, frame.shape.bbox.height,
|
86
98
|
apply_first_text_indent: !split_box?, frame: frame)
|
@@ -91,8 +103,17 @@ module HexaPDF
|
|
91
103
|
height = (@initial_height > 0 ? @initial_height : available_height) - @height
|
92
104
|
@tl.fit(@items, width, height, apply_first_text_indent: !split_box?, frame: frame)
|
93
105
|
end
|
106
|
+
|
94
107
|
@width += if @initial_width > 0 || style.text_align == :center || style.text_align == :right
|
95
108
|
width
|
109
|
+
elsif style.position == :flow
|
110
|
+
min_x = +Float::INFINITY
|
111
|
+
max_x = -Float::INFINITY
|
112
|
+
@result.lines.each do |line|
|
113
|
+
min_x = [min_x, line.x_offset].min
|
114
|
+
max_x = [max_x, line.x_offset + line.width].max
|
115
|
+
end
|
116
|
+
min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0
|
96
117
|
else
|
97
118
|
@result.lines.max_by(&:width)&.width || 0
|
98
119
|
end
|
@@ -105,44 +126,22 @@ module HexaPDF
|
|
105
126
|
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
106
127
|
end
|
107
128
|
|
108
|
-
@result.status == :success
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# Splits the text box into two boxes if necessary and possible.
|
113
|
-
def split(available_width, available_height, frame)
|
114
|
-
fit(available_width, available_height, frame) unless @result
|
115
|
-
|
116
|
-
if style.position != :flow && (float_compare(@width, available_width) > 0 ||
|
117
|
-
float_compare(@height, available_height) > 0)
|
118
|
-
[nil, self]
|
119
|
-
elsif @result.remaining_items.empty?
|
120
|
-
[self]
|
121
|
-
elsif @result.lines.empty?
|
122
|
-
[nil, self]
|
123
|
-
else
|
124
|
-
[self, create_box_for_remaining_items]
|
129
|
+
if @result.status == :success
|
130
|
+
fit_result.success!
|
131
|
+
elsif @result.status == :height && !@result.lines.empty?
|
132
|
+
fit_result.overflow!
|
125
133
|
end
|
126
134
|
end
|
127
135
|
|
128
|
-
#
|
129
|
-
def
|
130
|
-
|
136
|
+
# Splits the text box into two.
|
137
|
+
def split_content
|
138
|
+
[self, create_box_for_remaining_items]
|
131
139
|
end
|
132
140
|
|
133
|
-
private
|
134
|
-
|
135
141
|
# Draws the text into the box.
|
136
142
|
def draw_content(canvas, x, y)
|
137
|
-
return unless @result
|
138
|
-
|
139
|
-
if @result.status == :height && @initial_height > 0 && style.overflow == :error
|
140
|
-
raise HexaPDF::Error, "Text doesn't fit into box with limited height and " \
|
141
|
-
"style property overflow is set to :error"
|
142
|
-
end
|
143
|
-
|
144
143
|
return if @result.lines.empty?
|
145
|
-
@result.draw(canvas, x, y + content_height)
|
144
|
+
@result.draw(canvas, x - @x_offset, y + content_height)
|
146
145
|
end
|
147
146
|
|
148
147
|
# Creates a new TextBox instance for the items remaining after fitting the box.
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -362,29 +362,35 @@ module HexaPDF
|
|
362
362
|
pos = @io.pos
|
363
363
|
lines = @io.read(step_size + 40).split(/[\r\n]+/)
|
364
364
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
365
|
+
# Need to iterate through the whole lines array in case there are multiple %%EOF to try
|
366
|
+
eof_index = 0
|
367
|
+
while (eof_index = lines[0..(eof_index - 1)].rindex {|l| l.strip == '%%EOF' })
|
368
|
+
if eof_index > 0 && lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
369
|
+
startxref_offset = $1.to_i
|
370
|
+
startxref_mangled = true
|
371
|
+
break # we found it even if it the syntax is not entirely correct
|
372
|
+
elsif eof_index < 2
|
373
|
+
startxref_missing = true
|
374
|
+
break
|
375
|
+
elsif lines[eof_index - 2].strip != "startxref"
|
376
|
+
startxref_missing = true
|
377
|
+
else
|
378
|
+
startxref_offset = lines[eof_index - 1].to_i
|
379
|
+
break # we found it
|
380
|
+
end
|
377
381
|
end
|
382
|
+
eof_not_found ||= !eof_index
|
383
|
+
break if startxref_offset
|
378
384
|
end
|
379
385
|
|
380
|
-
if
|
381
|
-
maybe_raise("PDF file trailer with end-of-file marker not found", pos: pos,
|
382
|
-
force: !eof_index)
|
383
|
-
elsif startxref_mangled
|
386
|
+
if startxref_mangled
|
384
387
|
maybe_raise("PDF file trailer keyword startxref on same line as value", pos: pos)
|
385
388
|
elsif startxref_missing
|
386
389
|
maybe_raise("PDF file trailer is missing startxref keyword", pos: pos,
|
387
|
-
force:
|
390
|
+
force: !startxref_offset)
|
391
|
+
elsif eof_not_found
|
392
|
+
maybe_raise("PDF file trailer with end-of-file marker not found", pos: pos,
|
393
|
+
force: !startxref_offset)
|
388
394
|
end
|
389
395
|
|
390
396
|
@startxref_offset = startxref_offset
|
@@ -163,10 +163,21 @@ module HexaPDF
|
|
163
163
|
field
|
164
164
|
end
|
165
165
|
|
166
|
+
# Creates an untyped namespace field for creating hierarchies.
|
167
|
+
#
|
168
|
+
# Example:
|
169
|
+
#
|
170
|
+
# form.create_namespace_field('text')
|
171
|
+
# form.create_text_field('text.a1')
|
172
|
+
def create_namespace_field(name)
|
173
|
+
create_field(name)
|
174
|
+
end
|
175
|
+
|
166
176
|
# Creates a new text field with the given name and adds it to the form.
|
167
177
|
#
|
168
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
169
|
-
#
|
178
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
179
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
180
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
170
181
|
#
|
171
182
|
# The optional keyword arguments allow setting often used properties of the field:
|
172
183
|
#
|
@@ -202,8 +213,9 @@ module HexaPDF
|
|
202
213
|
|
203
214
|
# Creates a new multiline text field with the given name and adds it to the form.
|
204
215
|
#
|
205
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
206
|
-
#
|
216
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
217
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
218
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
207
219
|
#
|
208
220
|
# The optional keyword arguments allow setting often used properties of the field, see
|
209
221
|
# #create_text_field for details.
|
@@ -221,8 +233,9 @@ module HexaPDF
|
|
221
233
|
# The +max_chars+ argument defines the maximum number of characters the comb text field can
|
222
234
|
# accommodate.
|
223
235
|
#
|
224
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
225
|
-
#
|
236
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
237
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
238
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
226
239
|
#
|
227
240
|
# The optional keyword arguments allow setting often used properties of the field, see
|
228
241
|
# #create_text_field for details.
|
@@ -238,8 +251,9 @@ module HexaPDF
|
|
238
251
|
|
239
252
|
# Creates a new file select field with the given name and adds it to the form.
|
240
253
|
#
|
241
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
242
|
-
#
|
254
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
255
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
256
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
243
257
|
#
|
244
258
|
# The optional keyword arguments allow setting often used properties of the field, see
|
245
259
|
# #create_text_field for details.
|
@@ -254,8 +268,9 @@ module HexaPDF
|
|
254
268
|
|
255
269
|
# Creates a new password field with the given name and adds it to the form.
|
256
270
|
#
|
257
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
258
|
-
#
|
271
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
272
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
273
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
259
274
|
#
|
260
275
|
# The optional keyword arguments allow setting often used properties of the field, see
|
261
276
|
# #create_text_field for details.
|
@@ -270,8 +285,9 @@ module HexaPDF
|
|
270
285
|
|
271
286
|
# Creates a new check box with the given name and adds it to the form.
|
272
287
|
#
|
273
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
274
|
-
#
|
288
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
289
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
290
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
275
291
|
#
|
276
292
|
# Before a field value other than +false+ can be assigned to the check box, a widget needs
|
277
293
|
# to be created.
|
@@ -281,8 +297,9 @@ module HexaPDF
|
|
281
297
|
|
282
298
|
# Creates a radio button with the given name and adds it to the form.
|
283
299
|
#
|
284
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
285
|
-
#
|
300
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
301
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
302
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
286
303
|
#
|
287
304
|
# Before a field value other than +nil+ can be assigned to the radio button, at least one
|
288
305
|
# widget needs to be created.
|
@@ -292,8 +309,9 @@ module HexaPDF
|
|
292
309
|
|
293
310
|
# Creates a combo box with the given name and adds it to the form.
|
294
311
|
#
|
295
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
296
|
-
#
|
312
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
313
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
314
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
297
315
|
#
|
298
316
|
# The optional keyword arguments allow setting often used properties of the field:
|
299
317
|
#
|
@@ -319,8 +337,9 @@ module HexaPDF
|
|
319
337
|
|
320
338
|
# Creates a list box with the given name and adds it to the form.
|
321
339
|
#
|
322
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
323
|
-
#
|
340
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
341
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
342
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
324
343
|
#
|
325
344
|
# The optional keyword arguments allow setting often used properties of the field:
|
326
345
|
#
|
@@ -345,10 +364,38 @@ module HexaPDF
|
|
345
364
|
|
346
365
|
# Creates a signature field with the given name and adds it to the form.
|
347
366
|
#
|
348
|
-
# The +name+ may contain dots to signify a field hierarchy. If
|
349
|
-
#
|
367
|
+
# The +name+ may contain dots to signify a field hierarchy. If the parent fields don't
|
368
|
+
# already exist, they are created as pure namespace fields (see #create_namespace_field). If
|
369
|
+
# the +name+ doesn't contain dots, a top-level field is created.
|
350
370
|
def create_signature_field(name)
|
351
|
-
create_field(name, :Sig)
|
371
|
+
create_field(name, :Sig)
|
372
|
+
end
|
373
|
+
|
374
|
+
# :call-seq:
|
375
|
+
# form.delete_field(name)
|
376
|
+
# form.delete_field(field)
|
377
|
+
#
|
378
|
+
# Deletes the field specified by the given name or via the given field object.
|
379
|
+
#
|
380
|
+
# If the field is a signature field, the associated signature dictionary is also deleted.
|
381
|
+
def delete_field(name_or_field)
|
382
|
+
field = (name_or_field.kind_of?(String) ? field_by_name(name_or_field) : name_or_field)
|
383
|
+
document.delete(field[:V]) if field.field_type == :Sig
|
384
|
+
|
385
|
+
to_delete = field.each_widget(direct_only: false).to_a
|
386
|
+
document.pages.each do |page|
|
387
|
+
next unless page.key?(:Annots)
|
388
|
+
page_annots = page[:Annots].to_a - to_delete
|
389
|
+
page[:Annots].value.replace(page_annots)
|
390
|
+
end
|
391
|
+
to_delete.each {|widget| document.delete(widget) }
|
392
|
+
|
393
|
+
if field[:Parent]
|
394
|
+
field[:Parent][:Kids].delete(field)
|
395
|
+
else
|
396
|
+
self[:Fields].delete(field)
|
397
|
+
end
|
398
|
+
document.delete(field)
|
352
399
|
end
|
353
400
|
|
354
401
|
# Fills form fields with the values from the given +data+ hash.
|
@@ -485,23 +532,27 @@ module HexaPDF
|
|
485
532
|
|
486
533
|
private
|
487
534
|
|
488
|
-
# Creates a new field with the full name +name+ and the field type +type+.
|
489
|
-
def create_field(name, type)
|
535
|
+
# Creates a new field with the full name +name+ and the optional field type +type+.
|
536
|
+
def create_field(name, type = nil)
|
490
537
|
parent_name, _, name = name.rpartition('.')
|
491
538
|
parent_field = parent_name.empty? ? nil : field_by_name(parent_name)
|
492
539
|
if !parent_name.empty? && !parent_field
|
493
|
-
|
540
|
+
parent_field = create_namespace_field(parent_name)
|
494
541
|
end
|
495
542
|
|
496
|
-
field =
|
497
|
-
|
543
|
+
field = if type
|
544
|
+
document.add({FT: type, T: name, Parent: parent_field},
|
545
|
+
type: :XXAcroFormField, subtype: type)
|
546
|
+
else
|
547
|
+
document.add({T: name, Parent: parent_field}, type: :XXAcroFormField)
|
548
|
+
end
|
498
549
|
if parent_field
|
499
550
|
(parent_field[:Kids] ||= []) << field
|
500
551
|
else
|
501
552
|
(self[:Fields] ||= []) << field
|
502
553
|
end
|
503
554
|
|
504
|
-
yield(field)
|
555
|
+
yield(field) if block_given?
|
505
556
|
|
506
557
|
field
|
507
558
|
end
|
@@ -158,11 +158,11 @@ module HexaPDF
|
|
158
158
|
end
|
159
159
|
|
160
160
|
# :call-seq:
|
161
|
-
# file_spec.embed(filename, name: File.basename(filename), register: true) -> ef_stream
|
162
|
-
# file_spec.embed(io, name:, register: true) -> ef_stream
|
161
|
+
# file_spec.embed(filename, name: File.basename(filename), mime_type: nil, register: true) -> ef_stream
|
162
|
+
# file_spec.embed(io, name:, mime_type: nil, register: true) -> ef_stream
|
163
163
|
#
|
164
|
-
# Embeds the given file or IO stream into the PDF file, sets the path
|
165
|
-
# the created stream object.
|
164
|
+
# Embeds the given file or IO stream into the PDF file, sets the path and MIME type
|
165
|
+
# accordingly and returns the created stream object.
|
166
166
|
#
|
167
167
|
# If a file is given, the +name+ option defaults to the basename of the file. However, if an
|
168
168
|
# IO object is given, the +name+ argument is mandatory.
|
@@ -177,13 +177,16 @@ module HexaPDF
|
|
177
177
|
# name::
|
178
178
|
# The name that should be used as path value and when registering.
|
179
179
|
#
|
180
|
+
# mime_type::
|
181
|
+
# Optionally specifies the MIME type of the file.
|
182
|
+
#
|
180
183
|
# register::
|
181
184
|
# Specifies whether the embedded file will be added to the EmbeddedFiles name tree under
|
182
185
|
# the +name+. If the name is already taken, it's value is overwritten.
|
183
186
|
#
|
184
187
|
# The file has to be available until the PDF document gets written because reading and
|
185
188
|
# writing is done lazily.
|
186
|
-
def embed(file_or_io, name: nil, register: true)
|
189
|
+
def embed(file_or_io, name: nil, mime_type: nil, register: true)
|
187
190
|
name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
|
188
191
|
if name.nil?
|
189
192
|
raise ArgumentError, "The name argument is mandatory when given an IO object"
|
@@ -194,6 +197,7 @@ module HexaPDF
|
|
194
197
|
|
195
198
|
self[:EF] ||= {}
|
196
199
|
ef_stream = self[:EF][:UF] = self[:EF][:F] = document.add({Type: :EmbeddedFile})
|
200
|
+
ef_stream[:Subtype] = mime_type.to_sym if mime_type
|
197
201
|
stat = if file_or_io.kind_of?(String)
|
198
202
|
File.stat(file_or_io)
|
199
203
|
elsif file_or_io.respond_to?(:stat)
|
@@ -51,7 +51,7 @@ module HexaPDF
|
|
51
51
|
|
52
52
|
define_type :ExtGState
|
53
53
|
|
54
|
-
define_field :Type, type: Symbol,
|
54
|
+
define_field :Type, type: Symbol, default: type
|
55
55
|
define_field :LW, type: Numeric, version: "1.3"
|
56
56
|
define_field :LC, type: Integer, version: "1.3"
|
57
57
|
define_field :LJ, type: Integer, version: "1.3"
|
data/lib/hexapdf/version.rb
CHANGED
@@ -43,6 +43,11 @@ describe HexaPDF::Document::Files do
|
|
43
43
|
assert_equal('Some file', spec[:Desc])
|
44
44
|
end
|
45
45
|
|
46
|
+
it "optionally sets the MIME type of an embedded file" do
|
47
|
+
spec = @doc.files.add(@file.path, mime_type: 'application/pdf')
|
48
|
+
assert_equal(:'application/pdf', spec.embedded_file_stream[:Subtype])
|
49
|
+
end
|
50
|
+
|
46
51
|
it "requires the name argument when given an IO object" do
|
47
52
|
assert_raises(ArgumentError) { @doc.files.add(StringIO.new) }
|
48
53
|
end
|
@@ -187,6 +187,27 @@ describe HexaPDF::Document::Metadata do
|
|
187
187
|
assert_equal(metadata, @doc.catalog[:Metadata].stream.sub(/(?<=id=")\w+/, ''))
|
188
188
|
end
|
189
189
|
|
190
|
+
it "writes the custom metadata" do
|
191
|
+
@metadata.delete
|
192
|
+
@metadata.custom_metadata("<rdf:Description>Test</rdf:Description>")
|
193
|
+
@metadata.custom_metadata("<rdf:Description>Test2</rdf:Description>")
|
194
|
+
@doc.write(StringIO.new, update_fields: false)
|
195
|
+
metadata = <<~XMP
|
196
|
+
<?xpacket begin="" id=""?>
|
197
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
198
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
199
|
+
<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
|
200
|
+
<pdf:Producer>HexaPDF version #{HexaPDF::VERSION}</pdf:Producer>
|
201
|
+
</rdf:Description>
|
202
|
+
<rdf:Description>Test</rdf:Description>
|
203
|
+
<rdf:Description>Test2</rdf:Description>
|
204
|
+
</rdf:RDF>
|
205
|
+
</x:xmpmeta>
|
206
|
+
<?xpacket end="r"?>
|
207
|
+
XMP
|
208
|
+
assert_equal(metadata, @doc.catalog[:Metadata].stream.sub(/(?<=id=")\w+/, ''))
|
209
|
+
end
|
210
|
+
|
190
211
|
it "writes the XMP metadata" do
|
191
212
|
title = HexaPDF::Document::Metadata::LocalizedString.new('Der Titel')
|
192
213
|
title.language = 'de'
|