hexapdf 0.43.0 → 0.45.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 +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'
|