hexapdf 0.43.0 → 0.45.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 +36 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +13 -11
- data/lib/hexapdf/composer.rb +23 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/layout.rb +15 -3
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/layout/box.rb +180 -66
- data/lib/hexapdf/layout/box_fitter.rb +1 -0
- data/lib/hexapdf/layout/column_box.rb +18 -28
- data/lib/hexapdf/layout/container_box.rb +6 -6
- data/lib/hexapdf/layout/frame.rb +13 -94
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/list_box.rb +13 -31
- data/lib/hexapdf/layout/style.rb +8 -4
- data/lib/hexapdf/layout/table_box.rb +55 -58
- data/lib/hexapdf/layout/text_box.rb +84 -71
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -2
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_layout.rb +16 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +93 -37
- data/test/hexapdf/layout/test_box_fitter.rb +7 -0
- 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 +7 -46
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +26 -27
- data/test/hexapdf/layout/test_table_box.rb +47 -54
- data/test/hexapdf/layout/test_text_box.rb +83 -83
- data/test/hexapdf/test_composer.rb +20 -5
- data/test/hexapdf/test_parser.rb +8 -0
- data/test/hexapdf/test_serializer.rb +1 -0
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- metadata +2 -2
@@ -42,7 +42,46 @@ module HexaPDF
|
|
42
42
|
# A TextBox is used for drawing text, either inside a rectangular box or by flowing it around
|
43
43
|
# objects of a Frame.
|
44
44
|
#
|
45
|
+
# The standard usage is through the helper methods Document::Layout#text and
|
46
|
+
# Document::Layout#formatted_text.
|
47
|
+
#
|
45
48
|
# This class uses TextLayouter behind the scenes to do the hard work.
|
49
|
+
#
|
50
|
+
# == Used Box Properties
|
51
|
+
#
|
52
|
+
# The spacing after the last line can be controlled via the style property +last_line_gap+. Also
|
53
|
+
# see TextLayouter#style for other style properties taken into account.
|
54
|
+
#
|
55
|
+
# == Limitations
|
56
|
+
#
|
57
|
+
# When setting the style property 'position' to +:flow+, padding and border to the left and
|
58
|
+
# right as well as a predefined fixed width are not respected and the result will look wrong.
|
59
|
+
#
|
60
|
+
# == Examples
|
61
|
+
#
|
62
|
+
# Showing some text:
|
63
|
+
#
|
64
|
+
# #>pdf-composer
|
65
|
+
# composer.box(:text, items: layout.text_fragments("This is some text."))
|
66
|
+
# # Or easier with the provided convenience method
|
67
|
+
# composer.text("This is also some text")
|
68
|
+
#
|
69
|
+
# It is possible to flow the text around other objects by using the style property
|
70
|
+
# 'position' with the value +:flow+:
|
71
|
+
#
|
72
|
+
# #>pdf-composer
|
73
|
+
# composer.box(:base, width: 30, height: 30,
|
74
|
+
# style: {margin: 5, position: :float, background_color: "hp-blue-light"})
|
75
|
+
# composer.text("This is some text. " * 20, position: :flow)
|
76
|
+
#
|
77
|
+
# While top and bottom padding and border can be used with flow positioning, left and right
|
78
|
+
# padding and border are not supported and the result will look wrong:
|
79
|
+
#
|
80
|
+
# #>pdf-composer
|
81
|
+
# composer.box(:base, width: 30, height: 30,
|
82
|
+
# style: {margin: 5, position: :float, background_color: "hp-blue-light"})
|
83
|
+
# composer.text("This is some text. " * 20, padding: 10, position: :flow,
|
84
|
+
# text_align: :justify)
|
46
85
|
class TextBox < Box
|
47
86
|
|
48
87
|
# Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
|
@@ -67,94 +106,68 @@ module HexaPDF
|
|
67
106
|
true
|
68
107
|
end
|
69
108
|
|
109
|
+
# :nodoc:
|
110
|
+
def draw(canvas, x, y)
|
111
|
+
super(canvas, x + @x_offset, y)
|
112
|
+
end
|
113
|
+
|
114
|
+
# :nodoc:
|
115
|
+
def empty?
|
116
|
+
super && (!@result || @result.lines.empty?)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
70
121
|
# Fits the text box into the Frame.
|
71
122
|
#
|
72
123
|
# Depending on the 'position' style property, the text is either fit into the current region
|
73
124
|
# of the frame using +available_width+ and +available_height+, or fit to the shape of the
|
74
125
|
# frame starting from the top (when 'position' is set to :flow).
|
75
|
-
|
76
|
-
# The spacing after the last line can be controlled via the style property +last_line_gap+.
|
77
|
-
#
|
78
|
-
# Also see TextLayouter#style for other style properties taken into account.
|
79
|
-
def fit(available_width, available_height, frame)
|
80
|
-
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
81
|
-
(@initial_height > 0 && @initial_height > available_height)
|
82
|
-
|
126
|
+
def fit_content(_available_width, _available_height, frame)
|
83
127
|
frame = frame.child_frame(box: self)
|
84
|
-
@
|
85
|
-
|
86
|
-
|
128
|
+
@x_offset = 0
|
129
|
+
|
130
|
+
if style.position == :flow
|
131
|
+
height = (@initial_height > 0 ? @initial_height : frame.shape.bbox.height) - reserved_height
|
132
|
+
@result = @tl.fit(@items, frame.width_specification(reserved_height_top), height,
|
87
133
|
apply_first_text_indent: !split_box?, frame: frame)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
else
|
106
|
-
@result.lines.max_by(&:width)&.width || 0
|
107
|
-
end
|
108
|
-
@height += if @initial_height > 0 || style.text_valign == :center || style.text_valign == :bottom
|
109
|
-
height
|
110
|
-
else
|
111
|
-
@result.height
|
112
|
-
end
|
113
|
-
if style.last_line_gap && @result.lines.last
|
114
|
-
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
134
|
+
min_x = +Float::INFINITY
|
135
|
+
max_x = -Float::INFINITY
|
136
|
+
@result.lines.each do |line|
|
137
|
+
min_x = [min_x, line.x_offset].min
|
138
|
+
max_x = [max_x, line.x_offset + line.width].max
|
139
|
+
end
|
140
|
+
@width = (min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0) + reserved_width
|
141
|
+
@height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
|
142
|
+
else
|
143
|
+
@result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
|
144
|
+
apply_first_text_indent: !split_box?, frame: frame)
|
145
|
+
if style.text_align == :left && @initial_width == 0
|
146
|
+
@width = (@result.lines.max_by(&:width)&.width || 0) + reserved_width
|
147
|
+
end
|
148
|
+
if style.text_valign == :top && @initial_height == 0
|
149
|
+
@height = @result.height + reserved_height
|
150
|
+
end
|
115
151
|
end
|
116
152
|
|
117
|
-
@result.
|
118
|
-
(@result.
|
119
|
-
end
|
120
|
-
|
121
|
-
# Splits the text box into two boxes if necessary and possible.
|
122
|
-
def split(available_width, available_height, frame)
|
123
|
-
fit(available_width, available_height, frame) unless @result
|
124
|
-
|
125
|
-
if style.position != :flow && (float_compare(@width, available_width) > 0 ||
|
126
|
-
float_compare(@height, available_height) > 0)
|
127
|
-
[nil, self]
|
128
|
-
elsif @result.remaining_items.empty?
|
129
|
-
[self]
|
130
|
-
elsif @result.lines.empty?
|
131
|
-
[nil, self]
|
132
|
-
else
|
133
|
-
[self, create_box_for_remaining_items]
|
153
|
+
if style.last_line_gap && @result.lines.last && @initial_height == 0
|
154
|
+
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
134
155
|
end
|
135
|
-
end
|
136
156
|
|
137
|
-
|
138
|
-
|
139
|
-
|
157
|
+
if @result.status == :success
|
158
|
+
fit_result.success!
|
159
|
+
elsif @result.status == :height && !@result.lines.empty?
|
160
|
+
fit_result.overflow!
|
161
|
+
end
|
140
162
|
end
|
141
163
|
|
142
|
-
#
|
143
|
-
def
|
144
|
-
|
164
|
+
# Splits the text box into two.
|
165
|
+
def split_content
|
166
|
+
[self, create_box_for_remaining_items]
|
145
167
|
end
|
146
168
|
|
147
|
-
private
|
148
|
-
|
149
169
|
# Draws the text into the box.
|
150
170
|
def draw_content(canvas, x, y)
|
151
|
-
return unless @result
|
152
|
-
|
153
|
-
if @result.status == :height && @initial_height > 0 && style.overflow == :error
|
154
|
-
raise HexaPDF::Error, "Text doesn't fit into box with limited height and " \
|
155
|
-
"style property overflow is set to :error"
|
156
|
-
end
|
157
|
-
|
158
171
|
return if @result.lines.empty?
|
159
172
|
@result.draw(canvas, x - @x_offset, y + content_height)
|
160
173
|
end
|
@@ -52,7 +52,7 @@ module HexaPDF
|
|
52
52
|
# The items of a text fragment may be frozen to indicate that the fragment is potentially used
|
53
53
|
# multiple times.
|
54
54
|
#
|
55
|
-
# The rectangle with the bottom
|
55
|
+
# The rectangle with the bottom-left corner (#x_min, #y_min) and the top-right corner (#x_max,
|
56
56
|
# #y_max) describes the minimum bounding box of the whole text fragment and is usually *not*
|
57
57
|
# equal to the box (0, 0)-(#width, #height).
|
58
58
|
class TextFragment
|
@@ -218,7 +218,10 @@ module HexaPDF
|
|
218
218
|
|
219
219
|
# Breaks are detected at: space, tab, zero-width-space, non-breaking space, hyphen,
|
220
220
|
# soft-hypen and any valid Unicode newline separator
|
221
|
-
|
221
|
+
BREAK_CHARS = {}
|
222
|
+
" \u{A}\u{B}\u{C}\u{D}\u{85}\u{2028}\u{2029}\t\u{200B}\u{00AD}\u{00A0}-".each_char do |c|
|
223
|
+
BREAK_CHARS[c] = true
|
224
|
+
end
|
222
225
|
|
223
226
|
# Breaks the items (an array of InlineBox and TextFragment objects) into atomic pieces
|
224
227
|
# wrapped by Box, Glue or Penalty items, and returns those as an array.
|
@@ -235,7 +238,7 @@ module HexaPDF
|
|
235
238
|
# Collect characters and kerning values until break character is encountered
|
236
239
|
box_items = []
|
237
240
|
while (glyph = item.items[i]) &&
|
238
|
-
(glyph.kind_of?(Numeric) || !
|
241
|
+
(glyph.kind_of?(Numeric) || !BREAK_CHARS.key?(glyph.str))
|
239
242
|
box_items << glyph
|
240
243
|
i += 1
|
241
244
|
end
|
@@ -428,9 +431,7 @@ module HexaPDF
|
|
428
431
|
end
|
429
432
|
|
430
433
|
line = create_unjustified_line
|
431
|
-
last_line_used = true
|
432
|
-
last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
|
433
|
-
|
434
|
+
last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
|
434
435
|
item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
|
435
436
|
end
|
436
437
|
|
@@ -500,9 +501,7 @@ module HexaPDF
|
|
500
501
|
end
|
501
502
|
|
502
503
|
line = create_unjustified_line
|
503
|
-
last_line_used = true
|
504
|
-
last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
|
505
|
-
|
504
|
+
last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
|
506
505
|
item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
|
507
506
|
end
|
508
507
|
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -365,11 +365,14 @@ module HexaPDF
|
|
365
365
|
# Need to iterate through the whole lines array in case there are multiple %%EOF to try
|
366
366
|
eof_index = 0
|
367
367
|
while (eof_index = lines[0..(eof_index - 1)].rindex {|l| l.strip == '%%EOF' })
|
368
|
-
if lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
368
|
+
if eof_index > 0 && lines[eof_index - 1].strip =~ /\Astartxref\s(\d+)\z/
|
369
369
|
startxref_offset = $1.to_i
|
370
370
|
startxref_mangled = true
|
371
371
|
break # we found it even if it the syntax is not entirely correct
|
372
|
-
elsif eof_index < 2
|
372
|
+
elsif eof_index < 2
|
373
|
+
startxref_missing = true
|
374
|
+
break
|
375
|
+
elsif lines[eof_index - 2].strip != "startxref"
|
373
376
|
startxref_missing = true
|
374
377
|
else
|
375
378
|
startxref_offset = lines[eof_index - 1].to_i
|
data/lib/hexapdf/rectangle.rb
CHANGED
@@ -48,8 +48,8 @@ module HexaPDF
|
|
48
48
|
#
|
49
49
|
# [left, bottom, right, top]
|
50
50
|
#
|
51
|
-
# where +left+ is the bottom
|
52
|
-
# is the top
|
51
|
+
# where +left+ is the bottom-left x-coordinate, +bottom+ is the bottom-left y-coordinate, +right+
|
52
|
+
# is the top-right x-coordinate and +top+ is the top-right y-coordinate.
|
53
53
|
#
|
54
54
|
# See: PDF2.0 s7.9.5
|
55
55
|
class Rectangle < HexaPDF::PDFArray
|
@@ -119,8 +119,8 @@ module HexaPDF
|
|
119
119
|
#:nodoc:
|
120
120
|
RECTANGLE_ERROR_MSG = "A PDF rectangle structure must contain an array of four numbers"
|
121
121
|
|
122
|
-
# Ensures that the value is an array containing four numbers that specify the bottom
|
123
|
-
# top
|
122
|
+
# Ensures that the value is an array containing four numbers that specify the bottom-left and
|
123
|
+
# top-right corners.
|
124
124
|
def after_data_change
|
125
125
|
super
|
126
126
|
unless value.size == 4 && all? {|v| v.kind_of?(Numeric) }
|
@@ -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)
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -159,8 +159,8 @@ module HexaPDF
|
|
159
159
|
# retained without the need for parsing its contents.
|
160
160
|
#
|
161
161
|
# If the bounding box of the form XObject doesn't have its origin at (0, 0), the canvas origin
|
162
|
-
# is translated into the bottom
|
163
|
-
# canvas. This means that the canvas' origin is always at the bottom
|
162
|
+
# is translated into the bottom-left corner so that this detail doesn't matter when using the
|
163
|
+
# canvas. This means that the canvas' origin is always at the bottom-left corner of the
|
164
164
|
# bounding box.
|
165
165
|
#
|
166
166
|
# *Note* that a canvas can only be retrieved for initially empty form XObjects!
|
@@ -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
@@ -47,21 +47,20 @@ describe HexaPDF::Content::CanvasComposer do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "splits the box if possible" do
|
50
|
-
@composer.draw_box(create_box(width:
|
51
|
-
box = create_box(
|
52
|
-
box.define_singleton_method(:
|
53
|
-
|
54
|
-
end
|
50
|
+
@composer.draw_box(create_box(width: 300, height: 300, style: {position: :float}))
|
51
|
+
box = create_box(style: {mask_mode: :box})
|
52
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
53
|
+
box.define_singleton_method(:split_content) { [box, HexaPDF::Layout::Box.new(height: 100) {}] }
|
55
54
|
@composer.draw_box(box)
|
56
55
|
assert_operators(@composer.canvas.contents,
|
57
56
|
[[:save_graphics_state],
|
58
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0,
|
57
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 541.889764]],
|
59
58
|
[:restore_graphics_state],
|
60
59
|
[:save_graphics_state],
|
61
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
60
|
+
[:concatenate_matrix, [1, 0, 0, 1, 300, 0]],
|
62
61
|
[:restore_graphics_state],
|
63
62
|
[:save_graphics_state],
|
64
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
63
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 441.889764]],
|
65
64
|
[:restore_graphics_state]])
|
66
65
|
end
|
67
66
|
|
@@ -77,6 +76,12 @@ describe HexaPDF::Content::CanvasComposer do
|
|
77
76
|
[:restore_graphics_state]])
|
78
77
|
end
|
79
78
|
|
79
|
+
it "handles truncated boxes correctly" do
|
80
|
+
box = create_box(height: 400, style: {overflow: :truncate})
|
81
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
82
|
+
assert_same(box, @composer.draw_box(box))
|
83
|
+
end
|
84
|
+
|
80
85
|
it "returns the last drawn box" do
|
81
86
|
box = create_box(height: 400)
|
82
87
|
assert_same(box, @composer.draw_box(box))
|
@@ -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
|
@@ -141,6 +141,22 @@ describe HexaPDF::Document::Layout do
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
describe "styles" do
|
145
|
+
it "returns the existing styles" do
|
146
|
+
@layout.style(:test, font_size: 20)
|
147
|
+
assert_equal([:base, :test], @layout.styles.keys)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "sets multiple styles at once" do
|
151
|
+
styles = @layout.styles(
|
152
|
+
test: {font_size: 20},
|
153
|
+
test2: {font_size: 30},
|
154
|
+
)
|
155
|
+
assert_same(styles, @layout.styles)
|
156
|
+
assert_equal([:base, :test, :test2], @layout.styles.keys)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
144
160
|
describe "inline_box" do
|
145
161
|
it "takes a box as argument" do
|
146
162
|
box = HexaPDF::Layout::Box.create(width: 10, height: 10)
|
@@ -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'
|