hexapdf 0.20.4 → 0.21.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 +27 -0
- data/README.md +5 -3
- data/Rakefile +10 -1
- data/examples/018-composer.rb +10 -10
- data/lib/hexapdf/cli/batch.rb +4 -6
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +59 -0
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/composer.rb +147 -53
- data/lib/hexapdf/configuration.rb +7 -3
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/color_space.rb +1 -1
- data/lib/hexapdf/content/operator.rb +7 -7
- data/lib/hexapdf/content/parser.rb +3 -3
- data/lib/hexapdf/content/processor.rb +9 -9
- data/lib/hexapdf/document/signatures.rb +5 -4
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/font/true_type/font.rb +7 -7
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type_wrapper.rb +9 -14
- data/lib/hexapdf/font/type1/font.rb +10 -12
- data/lib/hexapdf/font/type1_wrapper.rb +1 -2
- data/lib/hexapdf/layout/box.rb +12 -9
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/style.rb +28 -8
- data/lib/hexapdf/layout/text_fragment.rb +10 -9
- data/lib/hexapdf/parser.rb +5 -0
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +6 -4
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -2
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/resources.rb +4 -4
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
- data/lib/hexapdf/type/signature.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +3 -3
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +5 -5
- data/test/hexapdf/content/test_graphics_state.rb +1 -0
- data/test/hexapdf/content/test_operator.rb +2 -2
- data/test/hexapdf/content/test_processor.rb +1 -1
- data/test/hexapdf/encryption/test_standard_security_handler.rb +23 -29
- data/test/hexapdf/filter/test_predictor.rb +16 -20
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/common.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +1 -1
- data/test/hexapdf/image_loader/test_pdf.rb +6 -8
- data/test/hexapdf/image_loader/test_png.rb +2 -2
- data/test/hexapdf/layout/test_box.rb +11 -1
- data/test/hexapdf/layout/test_style.rb +23 -0
- data/test/hexapdf/layout/test_text_fragment.rb +21 -21
- data/test/hexapdf/test_composer.rb +115 -52
- data/test/hexapdf/test_dictionary.rb +2 -2
- data/test/hexapdf/test_document.rb +11 -9
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_parser.rb +13 -7
- data/test/hexapdf/test_serializer.rb +20 -22
- data/test/hexapdf/test_stream.rb +7 -9
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
- data/test/hexapdf/type/acro_form/test_choice_field.rb +1 -1
- data/test/hexapdf/type/signature/common.rb +1 -1
- data/test/hexapdf/type/test_font_type0.rb +1 -1
- data/test/hexapdf/type/test_font_type1.rb +7 -7
- data/test/hexapdf/type/test_image.rb +13 -17
- metadata +2 -2
@@ -60,9 +60,9 @@ module CommonTokenizerTests
|
|
60
60
|
'obj', 'endobj', 'f*', '*f', '{', '}',
|
61
61
|
"parentheses ( ) and (\nspecial \0053++characters\n (*!&}^% and so on).\n", '',
|
62
62
|
"Nov shmoz ka pop.", "\x90\x1F\xA3", "\x90\x1F\xA0",
|
63
|
-
:Name1, :ASomewhatLongerName, :
|
64
|
-
:
|
65
|
-
:
|
63
|
+
:Name1, :ASomewhatLongerName, :'A;Name_With-Various***Characters?',
|
64
|
+
:'1.2', :$$, :@pattern, :'.notdef', :'lime Green', :'paired()parentheses',
|
65
|
+
:'The_Key_of_F#_Minor', :AB, :"",
|
66
66
|
'[', 5, 6, :Name, ']', '[', 5, 6, :Name, ']',
|
67
67
|
'<<', :Name, 5, '>>'
|
68
68
|
].each {|t| t.force_encoding('BINARY') if t.respond_to?(:force_encoding) }
|
@@ -85,12 +85,12 @@ module CommonTokenizerTests
|
|
85
85
|
|
86
86
|
create_tokenizer("/Hößgang")
|
87
87
|
token = @tokenizer.next_token
|
88
|
-
assert_equal(:
|
88
|
+
assert_equal(:Hößgang, token)
|
89
89
|
assert_equal(Encoding::UTF_8, token.encoding)
|
90
90
|
|
91
91
|
create_tokenizer('/H#c3#b6#c3#9fgang')
|
92
92
|
token = @tokenizer.next_token
|
93
|
-
assert_equal(:
|
93
|
+
assert_equal(:Hößgang, token)
|
94
94
|
assert_equal(Encoding::UTF_8, token.encoding)
|
95
95
|
|
96
96
|
create_tokenizer('/H#E8lp')
|
@@ -391,7 +391,7 @@ describe_operator :CurveToNoSecondControlPoint, :y do
|
|
391
391
|
end
|
392
392
|
end
|
393
393
|
|
394
|
-
[:S, :s, :f, :F, 'f*'
|
394
|
+
[:S, :s, :f, :F, :'f*', :B, :'B*', :b, :'b*', :n].each do |sym|
|
395
395
|
describe_operator :EndPath, sym do
|
396
396
|
it "changes the graphics object to none" do
|
397
397
|
@processor.graphics_object = :path
|
@@ -401,7 +401,7 @@ end
|
|
401
401
|
end
|
402
402
|
end
|
403
403
|
|
404
|
-
[:W, 'W*'
|
404
|
+
[:W, :'W*'].each do |sym|
|
405
405
|
describe_operator :ClipPath, sym do
|
406
406
|
it "changes the graphics object to clipping_path for clip path operations" do
|
407
407
|
invoke
|
@@ -119,7 +119,7 @@ describe HexaPDF::Content::Processor do
|
|
119
119
|
@processor.process(:BT)
|
120
120
|
@processor.graphics_state.font = @font = @doc.add({Type: :Font, Subtype: :Type1,
|
121
121
|
Encoding: :WinAnsiEncoding,
|
122
|
-
BaseFont: :
|
122
|
+
BaseFont: :'Times-Roman'})
|
123
123
|
@processor.graphics_state.font_size = 10
|
124
124
|
@processor.graphics_state.text_rise = 10
|
125
125
|
@processor.graphics_state.character_spacing = 1
|
@@ -62,29 +62,25 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
62
62
|
test_files.each do |file|
|
63
63
|
basename = File.basename(file)
|
64
64
|
it "can decrypt, encrypt and decrypt the encrypted file #{basename} with the user password" do
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
flunk("Error processing #{basename}: #{e}")
|
76
|
-
end
|
65
|
+
doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
|
66
|
+
decryption_opts: {password: user_password})
|
67
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
68
|
+
|
69
|
+
out = StringIO.new(''.b)
|
70
|
+
HexaPDF::Writer.new(doc, out).write
|
71
|
+
doc = HexaPDF::Document.new(io: out, decryption_opts: {password: user_password})
|
72
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
73
|
+
rescue HexaPDF::EncryptionError => e
|
74
|
+
flunk("Error processing #{basename}: #{e}")
|
77
75
|
end
|
78
76
|
|
79
77
|
unless basename.start_with?("userpwd")
|
80
78
|
it "can decrypt the encrypted file #{basename} with the owner password" do
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
flunk("Error processing #{basename}: #{e}")
|
87
|
-
end
|
79
|
+
doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
|
80
|
+
decryption_opts: {password: owner_password})
|
81
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
82
|
+
rescue HexaPDF::EncryptionError => e
|
83
|
+
flunk("Error processing #{basename}: #{e}")
|
88
84
|
end
|
89
85
|
end
|
90
86
|
end
|
@@ -97,16 +93,14 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
97
93
|
it "can encrypt and then decrypt with all encryption variations" do
|
98
94
|
{arc4: [40, 48, 128], aes: [128, 256]}.each do |algorithm, key_lengths|
|
99
95
|
key_lengths.each do |key_length|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
flunk("Error using variation: #{algorithm} #{key_length} bits\n" << e.message)
|
109
|
-
end
|
96
|
+
doc = HexaPDF::Document.new
|
97
|
+
doc.encrypt(algorithm: algorithm, key_length: key_length)
|
98
|
+
sio = StringIO.new
|
99
|
+
doc.write(sio)
|
100
|
+
doc = HexaPDF::Document.new(io: sio)
|
101
|
+
assert_kind_of(Time, doc.trailer.info[:ModDate], "alg: #{algorithm} #{key_length} bits")
|
102
|
+
rescue HexaPDF::Error => e
|
103
|
+
flunk("Error using variation: #{algorithm} #{key_length} bits\n" << e.message)
|
110
104
|
end
|
111
105
|
end
|
112
106
|
end
|
@@ -106,17 +106,15 @@ describe HexaPDF::Filter::Predictor do
|
|
106
106
|
end
|
107
107
|
|
108
108
|
it "fails if the last row is missing data and 'filter.predictor.strict' is true " do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
collector(encoder)
|
116
|
-
end
|
117
|
-
ensure
|
118
|
-
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
109
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
|
110
|
+
assert_raises(HexaPDF::FilterError) do
|
111
|
+
data = @testcases['up']
|
112
|
+
encoder = @obj.png_execute(:encoder, feeder(data[:source][0..-2], 1), data[:Predictor],
|
113
|
+
data[:Colors], data[:BitsPerComponent], data[:Columns])
|
114
|
+
collector(encoder)
|
119
115
|
end
|
116
|
+
ensure
|
117
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
120
118
|
end
|
121
119
|
end
|
122
120
|
|
@@ -139,17 +137,15 @@ describe HexaPDF::Filter::Predictor do
|
|
139
137
|
end
|
140
138
|
|
141
139
|
it "fails if the last row is missing data and 'filter.predictor.strict' is true " do
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
collector(encoder)
|
149
|
-
end
|
150
|
-
ensure
|
151
|
-
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
140
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
|
141
|
+
assert_raises(HexaPDF::FilterError) do
|
142
|
+
data = @testcases['up']
|
143
|
+
encoder = @obj.png_execute(:decoder, feeder(data[:result][0..-2], 1), data[:Predictor],
|
144
|
+
data[:Colors], data[:BitsPerComponent], data[:Columns])
|
145
|
+
collector(encoder)
|
152
146
|
end
|
147
|
+
ensure
|
148
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
153
149
|
end
|
154
150
|
end
|
155
151
|
end
|
@@ -14,7 +14,7 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
14
14
|
|
15
15
|
it "can be used with an existing PDF object" do
|
16
16
|
font = @doc.add({Type: :Font, Subtype: :Type1, Encoding: {Differences: [65, :B]},
|
17
|
-
BaseFont: :
|
17
|
+
BaseFont: :'Times-Roman'})
|
18
18
|
wrapper = HexaPDF::Font::Type1Wrapper.new(@doc, FONT_TIMES, pdf_object: font)
|
19
19
|
assert_equal([:B, :E, :A, :S, :T], wrapper.decode_utf8("BEAST").map(&:name))
|
20
20
|
assert_equal("A", wrapper.encode(wrapper.glyph(:A)))
|
@@ -16,7 +16,7 @@ module TestHelper
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def create_table(name, data = nil, standalone: false)
|
19
|
-
font, entry =
|
19
|
+
font, entry = standalone ? set_up_stub_true_type_font(register_vars: false) : [@font, @entry]
|
20
20
|
if data
|
21
21
|
font.io.string = data
|
22
22
|
entry.length = font.io.length
|
@@ -11,7 +11,7 @@ describe HexaPDF::Font::TrueType::Table::Cmap do
|
|
11
11
|
[0, 1, 28],
|
12
12
|
[3, 1, 28 + f0.length],
|
13
13
|
[1, 0, 28],
|
14
|
-
].map {|a| a.pack('n2N') }.join
|
14
|
+
].map {|a| a.pack('n2N') }.join << f0 << [10, 22, 0, 0, 2, 10, 13].pack('nN2N2n2')
|
15
15
|
set_up_stub_true_type_font(data)
|
16
16
|
end
|
17
17
|
|
@@ -55,7 +55,7 @@ describe HexaPDF::Font::TrueType::Table::CmapSubtable do
|
|
55
55
|
|
56
56
|
it "works for format 2" do
|
57
57
|
f2 = ([0, 8] + [0] * 254).pack('n*') + \
|
58
|
-
[[0, 256, 0, 2 + 8], [0x33, 3, 5, 2 + 256 * 2]].map {|a| a.pack('n2s>n') }.join
|
58
|
+
[[0, 256, 0, 2 + 8], [0x33, 3, 5, 2 + 256 * 2]].map {|a| a.pack('n2s>n') }.join + \
|
59
59
|
((0..255).to_a + [35, 65534, 0]).pack('n*')
|
60
60
|
t = table([2, f2.length + 6, 0].pack('n3') << f2)
|
61
61
|
assert_nil(t[0x0132])
|
@@ -33,14 +33,12 @@ describe HexaPDF::ImageLoader::PDF do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "works for PDF files using a string object and use_stringio=false" do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
file.close if file.path == @pdf && !file.closed?
|
43
|
-
end
|
36
|
+
@doc.config['image_loader.pdf.use_stringio'] = false
|
37
|
+
form = @loader.load(@doc, @pdf)
|
38
|
+
assert_equal(:Form, form[:Subtype])
|
39
|
+
ensure
|
40
|
+
ObjectSpace.each_object(File) do |file|
|
41
|
+
file.close if file.path == @pdf && !file.closed?
|
44
42
|
end
|
45
43
|
end
|
46
44
|
end
|
@@ -30,11 +30,11 @@ describe HexaPDF::ImageLoader::PNG do
|
|
30
30
|
assert_equal(height, image[:Height])
|
31
31
|
assert_equal(bpc, image[:BitsPerComponent])
|
32
32
|
assert_equal(color_space, @doc.unwrap(image[:ColorSpace])) if color_space
|
33
|
-
data = stream.map {|row| [row.map {|i| i.to_s(2).rjust(bpc, '0') }.join
|
33
|
+
data = stream.map {|row| [row.map {|i| i.to_s(2).rjust(bpc, '0') }.join].pack('B*') }.join
|
34
34
|
assert_equal(data, image.stream)
|
35
35
|
end
|
36
36
|
|
37
|
-
#
|
37
|
+
# NOTE: colors and image data for comparisons were extracted using GIMP and its color tools
|
38
38
|
describe "load" do
|
39
39
|
before do
|
40
40
|
@greyscale_1bit_data = [[1, 1, 0, 0, 0],
|
@@ -17,11 +17,21 @@ describe HexaPDF::Layout::Box do
|
|
17
17
|
assert_same(block, box.instance_eval { @draw_block })
|
18
18
|
end
|
19
19
|
|
20
|
-
it "allows specifying style
|
20
|
+
it "allows specifying a style object" do
|
21
|
+
box = HexaPDF::Layout::Box.create(style: {background_color: 20})
|
22
|
+
assert_equal(20, box.style.background_color)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows specifying style properties" do
|
21
26
|
box = HexaPDF::Layout::Box.create(background_color: 20)
|
22
27
|
assert_equal(20, box.style.background_color)
|
23
28
|
end
|
24
29
|
|
30
|
+
it "applies the additional style properties to the style object" do
|
31
|
+
box = HexaPDF::Layout::Box.create(style: {background_color: 20}, background_color: 15)
|
32
|
+
assert_equal(15, box.style.background_color)
|
33
|
+
end
|
34
|
+
|
25
35
|
it "takes content width and height" do
|
26
36
|
box = HexaPDF::Layout::Box.create(width: 100, height: 200, content_box: true,
|
27
37
|
padding: [10, 8, 6, 4],
|
@@ -604,6 +604,26 @@ describe HexaPDF::Layout::Style do
|
|
604
604
|
end
|
605
605
|
end
|
606
606
|
|
607
|
+
describe "self.create" do
|
608
|
+
it "returns the provided style argument" do
|
609
|
+
assert_same(@style, HexaPDF::Layout::Style.create(@style))
|
610
|
+
end
|
611
|
+
|
612
|
+
it "creates a new Style object based on the passed hash" do
|
613
|
+
style = HexaPDF::Layout::Style.create(font_size: 10, fill_color: 'green')
|
614
|
+
assert_equal(10, style.font_size)
|
615
|
+
assert_equal('green', style.fill_color)
|
616
|
+
end
|
617
|
+
|
618
|
+
it "creates an empty Style object if nil is passed" do
|
619
|
+
assert_kind_of(HexaPDF::Layout::Style, HexaPDF::Layout::Style.create(nil))
|
620
|
+
end
|
621
|
+
|
622
|
+
it "raises an error if an invalid object is provided" do
|
623
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style.create(5) }
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
607
627
|
it "can assign values on initialization" do
|
608
628
|
style = HexaPDF::Layout::Style.new(font_size: 10)
|
609
629
|
assert_equal(10, style.font_size)
|
@@ -692,6 +712,9 @@ describe HexaPDF::Layout::Style do
|
|
692
712
|
|
693
713
|
@style.stroke_dash_pattern(5, 2)
|
694
714
|
assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
|
715
|
+
|
716
|
+
@style.line_spacing(1.2)
|
717
|
+
assert_equal([:proportional, 1.2], [@style.line_spacing.type, @style.line_spacing.value])
|
695
718
|
end
|
696
719
|
|
697
720
|
it "allows checking for valid values" do
|
@@ -20,7 +20,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe "create" do
|
23
|
-
it "creates a TextFragment from text and
|
23
|
+
it "creates a TextFragment from text and style" do
|
24
24
|
frag = HexaPDF::Layout::TextFragment.create("Tom", font: @font, font_size: 20,
|
25
25
|
font_features: {kern: true})
|
26
26
|
assert_equal(4, frag.items.length)
|
@@ -180,16 +180,16 @@ describe HexaPDF::Layout::TextFragment do
|
|
180
180
|
[:set_line_width, [5]],
|
181
181
|
[:set_line_cap_style, [1]],
|
182
182
|
[:set_line_dash_pattern, [[5], 0]]],
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
183
|
+
back: [[:end_text],
|
184
|
+
[:save_graphics_state],
|
185
|
+
[:set_device_gray_stroking_color, [0]],
|
186
|
+
[:set_line_width, [@fragment.style.calculated_underline_thickness]],
|
187
|
+
[:set_line_cap_style, [0]],
|
188
|
+
[:set_line_dash_pattern, [[], 0]],
|
189
|
+
[:move_to, [10, 15]],
|
190
|
+
[:line_to, [40.88, 15]],
|
191
|
+
[:stroke_path],
|
192
|
+
[:restore_graphics_state]])
|
193
193
|
end
|
194
194
|
|
195
195
|
it "draws the strikeout line" do
|
@@ -201,16 +201,16 @@ describe HexaPDF::Layout::TextFragment do
|
|
201
201
|
[:set_line_width, [5]],
|
202
202
|
[:set_line_cap_style, [1]],
|
203
203
|
[:set_line_dash_pattern, [[5], 0]]],
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
204
|
+
back: [[:end_text],
|
205
|
+
[:save_graphics_state],
|
206
|
+
[:set_device_gray_stroking_color, [0]],
|
207
|
+
[:set_line_width, [@fragment.style.calculated_strikeout_thickness]],
|
208
|
+
[:set_line_cap_style, [0]],
|
209
|
+
[:set_line_dash_pattern, [[], 0]],
|
210
|
+
[:move_to, [10, 21.01]],
|
211
|
+
[:line_to, [40.88, 21.01]],
|
212
|
+
[:stroke_path],
|
213
|
+
[:restore_graphics_state]])
|
214
214
|
end
|
215
215
|
end
|
216
216
|
|
@@ -19,7 +19,7 @@ describe HexaPDF::Composer do
|
|
19
19
|
assert_equal(36, @composer.frame.bottom)
|
20
20
|
assert_equal(523, @composer.frame.width)
|
21
21
|
assert_equal(770, @composer.frame.height)
|
22
|
-
|
22
|
+
assert_kind_of(HexaPDF::Layout::Style, @composer.style(:base))
|
23
23
|
end
|
24
24
|
|
25
25
|
it "allows the customization of the page size" do
|
@@ -86,83 +86,144 @@ describe HexaPDF::Composer do
|
|
86
86
|
assert_equal(806, @composer.y)
|
87
87
|
end
|
88
88
|
|
89
|
+
describe "style" do
|
90
|
+
it "creates a new style if it does not exist based on the base argument" do
|
91
|
+
@composer.style(:base, font_size: 20)
|
92
|
+
assert_equal(20, @composer.style(:newstyle, subscript: true).font_size)
|
93
|
+
refute( @composer.style(:base).subscript)
|
94
|
+
assert_equal(10, @composer.style(:another_new, base: nil).font_size)
|
95
|
+
assert(@composer.style(:yet_another_new, base: :newstyle).subscript)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns the named style" do
|
99
|
+
assert_kind_of(HexaPDF::Layout::Style, @composer.style(:base))
|
100
|
+
end
|
101
|
+
|
102
|
+
it "updates the style with the given properties" do
|
103
|
+
assert_equal(20, @composer.style(:base, font_size: 20).font_size)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
89
107
|
describe "text" do
|
90
|
-
|
91
|
-
|
92
|
-
@composer.define_singleton_method(:draw_box)
|
108
|
+
before do
|
109
|
+
test_self = self
|
110
|
+
@composer.define_singleton_method(:draw_box) do |arg|
|
111
|
+
test_self.instance_variable_set(:@box, arg)
|
112
|
+
end
|
113
|
+
end
|
93
114
|
|
115
|
+
it "creates a text box and draws it on the canvas" do
|
94
116
|
@composer.text("Test", width: 10, height: 15)
|
95
|
-
assert_equal(10, box.width)
|
96
|
-
assert_equal(15, box.height)
|
97
|
-
assert_same(@composer.document.fonts.add("Times"), box.style.font)
|
98
|
-
items = box.instance_variable_get(:@items)
|
117
|
+
assert_equal(10, @box.width)
|
118
|
+
assert_equal(15, @box.height)
|
119
|
+
assert_same(@composer.document.fonts.add("Times"), @box.style.font)
|
120
|
+
items = @box.instance_variable_get(:@items)
|
99
121
|
assert_equal(1, items.length)
|
100
|
-
assert_same(box.style, items.first.style)
|
122
|
+
assert_same(@box.style, items.first.style)
|
101
123
|
end
|
102
124
|
|
103
125
|
it "allows setting of a custom style" do
|
104
|
-
|
105
|
-
@composer.
|
106
|
-
|
107
|
-
@composer.
|
108
|
-
|
109
|
-
|
126
|
+
style = HexaPDF::Layout::Style.new(font_size: 20, font: ['Times', {variant: :bold}])
|
127
|
+
@composer.text("Test", style: style)
|
128
|
+
assert_same(@box.style, style)
|
129
|
+
assert_same(@composer.document.fonts.add("Times", variant: :bold), @box.style.font)
|
130
|
+
assert_equal(20, @box.style.font_size)
|
131
|
+
|
132
|
+
@composer.text("Test", style: {font_size: 20})
|
133
|
+
assert_equal(20, @box.style.font_size)
|
134
|
+
|
135
|
+
@composer.style(:named, font_size: 20)
|
136
|
+
@composer.text("Test", style: :named)
|
137
|
+
assert_equal(20, @box.style.font_size)
|
110
138
|
end
|
111
139
|
|
112
140
|
it "updates the used style with the provided options" do
|
113
|
-
|
114
|
-
@
|
141
|
+
@composer.text("Test", style: {subscript: true}, font_size: 20)
|
142
|
+
assert_equal(20, @box.style.font_size)
|
143
|
+
end
|
115
144
|
|
116
|
-
|
117
|
-
|
145
|
+
it "allows using a box style different from the text style" do
|
146
|
+
style = HexaPDF::Layout::Style.new(font_size: 20)
|
147
|
+
@composer.text("Test", box_style: style)
|
148
|
+
refute_same(@box.instance_variable_get(:@items).first.style, style)
|
149
|
+
assert_same(@box.style, style)
|
150
|
+
|
151
|
+
@composer.style(:named, font_size: 20)
|
152
|
+
@composer.text("Test", box_style: :named)
|
153
|
+
assert_equal(20, @box.style.font_size)
|
118
154
|
end
|
119
155
|
end
|
120
156
|
|
121
157
|
describe "formatted_text" do
|
122
|
-
|
123
|
-
|
124
|
-
@composer.define_singleton_method(:draw_box)
|
158
|
+
before do
|
159
|
+
test_self = self
|
160
|
+
@composer.define_singleton_method(:draw_box) do |arg|
|
161
|
+
test_self.instance_variable_set(:@box, arg)
|
162
|
+
end
|
163
|
+
end
|
125
164
|
|
165
|
+
it "creates a text box with the given text and draws it on the canvas" do
|
126
166
|
@composer.formatted_text(["Test"], width: 10, height: 15)
|
127
|
-
assert_equal(10, box.width)
|
128
|
-
assert_equal(15, box.height)
|
129
|
-
assert_equal(1, box.instance_variable_get(:@items).length)
|
167
|
+
assert_equal(10, @box.width)
|
168
|
+
assert_equal(15, @box.height)
|
169
|
+
assert_equal(1, @box.instance_variable_get(:@items).length)
|
130
170
|
end
|
131
171
|
|
132
|
-
it "a hash
|
133
|
-
|
134
|
-
@
|
172
|
+
it "allows using a hash with :text key instead of a simple string" do
|
173
|
+
@composer.formatted_text([{text: "Test"}])
|
174
|
+
items = @box.instance_variable_get(:@items)
|
175
|
+
assert_equal(4, items[0].items.length)
|
176
|
+
end
|
135
177
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
assert_equal(
|
140
|
-
assert_equal(:center, items.first.style.align)
|
141
|
-
assert_equal(10, box.style.font_size)
|
178
|
+
it "uses an empty string if the :text key for a hash is not specified" do
|
179
|
+
@composer.formatted_text([{font_size: "Test"}])
|
180
|
+
items = @box.instance_variable_get(:@items)
|
181
|
+
assert_equal(0, items[0].items.length)
|
142
182
|
end
|
143
183
|
|
144
|
-
it "
|
145
|
-
|
146
|
-
@
|
184
|
+
it "allows setting a custom base style for all parts" do
|
185
|
+
@composer.formatted_text(["Test", "other"], font_size: 20)
|
186
|
+
items = @box.instance_variable_get(:@items)
|
187
|
+
assert_equal(20, @box.style.font_size)
|
188
|
+
assert_equal(20, items[0].style.font_size)
|
189
|
+
assert_equal(20, items[1].style.font_size)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "allows using custom style properties for a single part" do
|
193
|
+
@composer.formatted_text([{text: "Test", font_size: 20}, "test"], align: :center)
|
194
|
+
items = @box.instance_variable_get(:@items)
|
195
|
+
assert_equal(10, @box.style.font_size)
|
147
196
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
assert_equal(
|
152
|
-
assert_equal(
|
153
|
-
assert_equal(:center, items.first.style.align)
|
197
|
+
assert_equal(20, items[0].style.font_size)
|
198
|
+
assert_equal(:center, items[0].style.align)
|
199
|
+
|
200
|
+
assert_equal(10, items[1].style.font_size)
|
201
|
+
assert_equal(:center, items[1].style.align)
|
154
202
|
end
|
155
203
|
|
156
|
-
it "a
|
157
|
-
|
158
|
-
|
204
|
+
it "allows using a custom style as basis for a single part" do
|
205
|
+
@composer.formatted_text([{text: "Test", style: {font_size: 20}, subscript: true}, "test"],
|
206
|
+
align: :center)
|
207
|
+
items = @box.instance_variable_get(:@items)
|
208
|
+
assert_equal(10, @box.style.font_size)
|
159
209
|
|
160
|
-
|
161
|
-
items
|
162
|
-
|
163
|
-
|
164
|
-
assert_equal(
|
210
|
+
assert_equal(20, items[0].style.font_size)
|
211
|
+
assert_equal(:left, items[0].style.align)
|
212
|
+
assert(items[0].style.subscript)
|
213
|
+
|
214
|
+
assert_equal(10, items[1].style.font_size)
|
215
|
+
assert_equal(:center, items[1].style.align)
|
216
|
+
refute(items[1].style.subscript)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "allows specifying a link to an URL via the :link key" do
|
220
|
+
@composer.formatted_text([{text: "Test", link: "URI"}, {link: "URI"}, "test"])
|
221
|
+
items = @box.instance_variable_get(:@items)
|
222
|
+
assert_equal(3, items.length)
|
223
|
+
assert_equal(4, items[0].items.length, "text should be Test")
|
224
|
+
assert_equal(3, items[1].items.length, "text should be URI")
|
165
225
|
assert_equal([:link, {uri: 'URI'}], items[0].style.overlays.instance_variable_get(:@layers)[0])
|
226
|
+
refute(items[2].style.overlays?)
|
166
227
|
end
|
167
228
|
end
|
168
229
|
|
@@ -172,9 +233,11 @@ describe HexaPDF::Composer do
|
|
172
233
|
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
173
234
|
image_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
|
174
235
|
|
175
|
-
@composer.image(image_path, width: 10, height: 15)
|
236
|
+
@composer.image(image_path, width: 10, height: 15, style: {font_size: 20}, subscript: true)
|
176
237
|
assert_equal(10, box.width)
|
177
238
|
assert_equal(15, box.height)
|
239
|
+
assert_equal(20, box.style.font_size)
|
240
|
+
assert(box.style.subscript)
|
178
241
|
assert_same(@composer.document.images.add(image_path), box.image)
|
179
242
|
end
|
180
243
|
end
|