hexapdf 0.13.0 → 0.14.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 +51 -0
- data/examples/019-acro_form.rb +41 -4
- data/lib/hexapdf/cli/split.rb +74 -14
- data/lib/hexapdf/document.rb +10 -4
- data/lib/hexapdf/layout/text_layouter.rb +2 -2
- data/lib/hexapdf/object.rb +22 -0
- data/lib/hexapdf/parser.rb +23 -1
- data/lib/hexapdf/pdf_array.rb +2 -2
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +127 -27
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
- data/lib/hexapdf/type/acro_form/choice_field.rb +64 -10
- data/lib/hexapdf/type/acro_form/form.rb +133 -10
- data/lib/hexapdf/type/acro_form/text_field.rb +68 -3
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/font.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/form.rb +4 -1
- data/lib/hexapdf/type/page.rb +5 -5
- data/lib/hexapdf/utils/object_hash.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +9 -1
- data/test/hexapdf/test_document.rb +14 -6
- data/test/hexapdf/test_object.rb +27 -0
- data/test/hexapdf/test_parser.rb +46 -0
- data/test/hexapdf/test_pdf_array.rb +1 -1
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +286 -34
- data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
- data/test/hexapdf/type/acro_form/test_form.rb +83 -11
- data/test/hexapdf/type/acro_form/test_text_field.rb +75 -1
- data/test/hexapdf/type/test_form.rb +7 -0
- data/test/hexapdf/utils/test_object_hash.rb +5 -0
- data/test/test_helper.rb +2 -0
- metadata +4 -3
@@ -58,7 +58,7 @@ module HexaPDF
|
|
58
58
|
|
59
59
|
# Returns the CID font of this type 0 font.
|
60
60
|
def descendant_font
|
61
|
-
|
61
|
+
cache(:descendant_font) do
|
62
62
|
document.wrap(self[:DescendantFonts][0])
|
63
63
|
end
|
64
64
|
end
|
@@ -116,7 +116,7 @@ module HexaPDF
|
|
116
116
|
#
|
117
117
|
# Note that the CMap is cached internally when accessed the first time.
|
118
118
|
def cmap
|
119
|
-
|
119
|
+
cache(:cmap) do
|
120
120
|
val = self[:Encoding]
|
121
121
|
if val.kind_of?(Symbol)
|
122
122
|
HexaPDF::Font::CMap.for_name(val.to_s)
|
@@ -135,7 +135,7 @@ module HexaPDF
|
|
135
135
|
#
|
136
136
|
# See: PDF1.7 s9.10.2
|
137
137
|
def ucs2_cmap
|
138
|
-
|
138
|
+
cache(:ucs2_cmap) do
|
139
139
|
encoding = self[:Encoding]
|
140
140
|
system_info = descendant_font[:CIDSystemInfo]
|
141
141
|
registry = system_info[:Registry]
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -94,9 +94,12 @@ module HexaPDF
|
|
94
94
|
|
95
95
|
# Replaces the contents of the form XObject with the given string.
|
96
96
|
#
|
97
|
+
# This also clears the cache to avoid returning invalid objects.
|
98
|
+
#
|
97
99
|
# Note: This is the same as #stream= but here for interface compatibility with Page.
|
98
100
|
def contents=(data)
|
99
101
|
self.stream = data
|
102
|
+
clear_cache
|
100
103
|
end
|
101
104
|
|
102
105
|
# Returns the resource dictionary which is automatically created if it doesn't exist.
|
@@ -134,7 +137,7 @@ module HexaPDF
|
|
134
137
|
#
|
135
138
|
# *Note* that a canvas can only be retrieved for initially empty form XObjects!
|
136
139
|
def canvas
|
137
|
-
|
140
|
+
cache(:canvas) do
|
138
141
|
unless stream.empty?
|
139
142
|
raise HexaPDF::Error, "Cannot create a canvas for a form XObjects with contents"
|
140
143
|
end
|
data/lib/hexapdf/type/page.rb
CHANGED
@@ -383,7 +383,7 @@ module HexaPDF
|
|
383
383
|
raise ArgumentError, "Invalid value for 'type', expected: :page, :underlay or :overlay"
|
384
384
|
end
|
385
385
|
cache_key = "#{type}_canvas".intern
|
386
|
-
return
|
386
|
+
return cache(cache_key) if cached?(cache_key)
|
387
387
|
|
388
388
|
if type == :page && key?(:Contents)
|
389
389
|
raise HexaPDF::Error, "Cannot get the canvas for a page with contents"
|
@@ -400,14 +400,14 @@ module HexaPDF
|
|
400
400
|
|
401
401
|
contents = self[:Contents]
|
402
402
|
if contents.nil?
|
403
|
-
page_canvas =
|
403
|
+
page_canvas = cache(:page_canvas, create_canvas.call)
|
404
404
|
self[:Contents] = document.add({Filter: :FlateDecode},
|
405
405
|
stream: page_canvas.stream_data)
|
406
406
|
end
|
407
407
|
|
408
408
|
if type == :overlay || type == :underlay
|
409
|
-
underlay_canvas =
|
410
|
-
overlay_canvas =
|
409
|
+
underlay_canvas = cache(:underlay_canvas, create_canvas.call)
|
410
|
+
overlay_canvas = cache(:overlay_canvas, create_canvas.call)
|
411
411
|
|
412
412
|
stream = HexaPDF::StreamData.new do
|
413
413
|
Fiber.yield(" q ")
|
@@ -432,7 +432,7 @@ module HexaPDF
|
|
432
432
|
self[:Contents] = [underlay, *self[:Contents], overlay]
|
433
433
|
end
|
434
434
|
|
435
|
-
|
435
|
+
cache(cache_key)
|
436
436
|
end
|
437
437
|
|
438
438
|
# Creates a Form XObject from the page's dictionary and contents for the given PDF document.
|
data/lib/hexapdf/version.rb
CHANGED
@@ -591,25 +591,33 @@ describe HexaPDF::Layout::TextLayouter do
|
|
591
591
|
|
592
592
|
describe "horizontal alignment" do
|
593
593
|
before do
|
594
|
-
@items = boxes(*[[20, 20]] * 4)
|
594
|
+
@items = boxes(*[[20, 20]] * 4) + [glue(10), penalty(-5000, boxes(0).first.item)]
|
595
595
|
end
|
596
596
|
|
597
597
|
it "aligns the contents to the left" do
|
598
598
|
@style.align = :left
|
599
599
|
result = @layouter.fit(@items, 100, 100)
|
600
600
|
assert_equal(0, result.lines[0].x_offset)
|
601
|
+
assert_equal(80, result.lines[0].width)
|
602
|
+
result = @layouter.fit(@items, proc { 100 }, 100)
|
603
|
+
assert_equal(0, result.lines[0].x_offset)
|
604
|
+
assert_equal(80, result.lines[0].width)
|
601
605
|
end
|
602
606
|
|
603
607
|
it "aligns the contents to the center" do
|
604
608
|
@style.align = :center
|
605
609
|
result = @layouter.fit(@items, 100, 100)
|
606
610
|
assert_equal(10, result.lines[0].x_offset)
|
611
|
+
result = @layouter.fit(@items, proc { 100 }, 100)
|
612
|
+
assert_equal(10, result.lines[0].x_offset)
|
607
613
|
end
|
608
614
|
|
609
615
|
it "aligns the contents to the right" do
|
610
616
|
@style.align = :right
|
611
617
|
result = @layouter.fit(@items, 100, 100)
|
612
618
|
assert_equal(20, result.lines[0].x_offset)
|
619
|
+
result = @layouter.fit(@items, proc { 100 }, 100)
|
620
|
+
assert_equal(20, result.lines[0].x_offset)
|
613
621
|
end
|
614
622
|
end
|
615
623
|
|
@@ -609,16 +609,24 @@ describe HexaPDF::Document do
|
|
609
609
|
|
610
610
|
describe "caching interface" do
|
611
611
|
it "allows setting and retrieving values" do
|
612
|
-
assert_equal(:test, @doc.cache(:a, :b, :test))
|
613
|
-
assert_equal(:test, @doc.cache(:a, :b
|
614
|
-
assert_equal(:
|
612
|
+
assert_equal(:test, @doc.cache(:a, :b, :test) { :notused })
|
613
|
+
assert_equal(:test, @doc.cache(:a, :b) { :other })
|
614
|
+
assert_equal(:test, @doc.cache(:a, :b))
|
615
|
+
assert_nil(@doc.cache(:a, :c, nil))
|
616
|
+
assert_nil(@doc.cache(:a, :c) { :other })
|
617
|
+
assert_nil(@doc.cache(:a, :c))
|
615
618
|
assert(@doc.cached?(:a, :b))
|
616
619
|
assert(@doc.cached?(:a, :c))
|
617
620
|
end
|
618
621
|
|
622
|
+
it "allows updating a value" do
|
623
|
+
@doc.cache(:a, :b) { :test }
|
624
|
+
assert_equal(:new, @doc.cache(:a, :b, update: true) { :new })
|
625
|
+
end
|
626
|
+
|
619
627
|
it "allows clearing cached values" do
|
620
|
-
@doc.cache(:a, :b
|
621
|
-
@doc.cache(:b, :c
|
628
|
+
@doc.cache(:a, :b) { :c }
|
629
|
+
@doc.cache(:b, :c) { :d }
|
622
630
|
@doc.clear_cache(:a)
|
623
631
|
refute(@doc.cached?(:a, :b))
|
624
632
|
assert(@doc.cached?(:b, :c))
|
@@ -626,7 +634,7 @@ describe HexaPDF::Document do
|
|
626
634
|
refute(@doc.cached?(:a, :c))
|
627
635
|
end
|
628
636
|
|
629
|
-
it "fails if no cached value exists and
|
637
|
+
it "fails if no cached value exists and no block is given" do
|
630
638
|
assert_raises(LocalJumpError) { @doc.cache(:a, :b) }
|
631
639
|
end
|
632
640
|
end
|
data/test/hexapdf/test_object.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/object'
|
5
5
|
require 'hexapdf/reference'
|
6
|
+
require 'hexapdf/document'
|
6
7
|
|
7
8
|
describe HexaPDF::Object do
|
8
9
|
describe "class.deep_copy" do
|
@@ -199,6 +200,32 @@ describe HexaPDF::Object do
|
|
199
200
|
end
|
200
201
|
end
|
201
202
|
|
203
|
+
describe "caching" do
|
204
|
+
before do
|
205
|
+
@obj = HexaPDF::Object.new({}, document: HexaPDF::Document.new)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "can set and return a cached value" do
|
209
|
+
assert_equal(:value, @obj.cache(:data, :value))
|
210
|
+
assert_equal(:value, @obj.cache(:data, :other))
|
211
|
+
assert_equal(:value, @obj.cache(:block) { :value })
|
212
|
+
assert_equal(:other, @obj.cache(:data, :other, update: true))
|
213
|
+
end
|
214
|
+
|
215
|
+
it "can check for the existence of a cached value" do
|
216
|
+
refute(@obj.cached?(:data))
|
217
|
+
@obj.cache(:data, :value)
|
218
|
+
assert(@obj.cached?(:data))
|
219
|
+
end
|
220
|
+
|
221
|
+
it "can clear all cached values" do
|
222
|
+
@obj.cache(:data, :value)
|
223
|
+
assert(@obj.cached?(:data))
|
224
|
+
@obj.clear_cache
|
225
|
+
refute(@obj.cached?(:data))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
202
229
|
describe "validation" do
|
203
230
|
before do
|
204
231
|
@doc = Object.new
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -386,6 +386,41 @@ describe HexaPDF::Parser do
|
|
386
386
|
assert_match(/dictionary/, exp.message)
|
387
387
|
end
|
388
388
|
|
389
|
+
describe "invalid numbering of main xref section" do
|
390
|
+
it "handles the xref if the numbering is off by N" do
|
391
|
+
create_parser(" 1 0 obj 1 endobj\n" \
|
392
|
+
"xref\n1 2\n0000000000 65535 f \n0000000001 00000 n \ntrailer\n<<>>\n")
|
393
|
+
section, _trailer = @parser.parse_xref_section_and_trailer(17)
|
394
|
+
assert_equal(HexaPDF::XRefSection.in_use_entry(1, 0, 1), section[1])
|
395
|
+
end
|
396
|
+
|
397
|
+
it "fails if the first entry is not the one for oid=0" do
|
398
|
+
create_parser(" 1 0 obj 1 endobj\n" \
|
399
|
+
"xref\n1 2\n0000000000 00005 f \n0000000001 00000 n \ntrailer\n<<>>\n")
|
400
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(17) }
|
401
|
+
assert_match(/Main.*invalid numbering/i, exp.message)
|
402
|
+
|
403
|
+
create_parser(" 1 0 obj 1 endobj\n" \
|
404
|
+
"xref\n1 2\n0000000001 00000 n \n0000000001 00000 n \ntrailer\n<<>>\n")
|
405
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(17) }
|
406
|
+
assert_match(/Main.*invalid numbering/i, exp.message)
|
407
|
+
end
|
408
|
+
|
409
|
+
it "fails if the tested entry position is invalid" do
|
410
|
+
create_parser(" 1 0 obj 1 endobj\n" \
|
411
|
+
"xref\n1 2\n0000000000 65535 f \n0000000005 00000 n \ntrailer\n<<>>\n")
|
412
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(17) }
|
413
|
+
assert_match(/Main.*invalid numbering/i, exp.message)
|
414
|
+
end
|
415
|
+
|
416
|
+
it "fails if the tested entry position's oid doesn't match the corrected entry oid" do
|
417
|
+
create_parser(" 2 0 obj 1 endobj\n" \
|
418
|
+
"xref\n1 2\n0000000000 65535 f \n0000000001 00000 n \ntrailer\n<<>>\n")
|
419
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(17) }
|
420
|
+
assert_match(/Main.*invalid numbering/i, exp.message)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
389
424
|
describe "with strict parsing" do
|
390
425
|
before do
|
391
426
|
@document.config['parser.on_correctable_error'] = proc { true }
|
@@ -408,6 +443,12 @@ describe HexaPDF::Parser do
|
|
408
443
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
|
409
444
|
assert_match(/invalid.*cross-reference entry/i, exp.message)
|
410
445
|
end
|
446
|
+
|
447
|
+
it "fails if the main cross-reference section has invalid numbering" do
|
448
|
+
create_parser("xref\n1 1\n0000000001 00000 n \ntrailer\n<<>>\n")
|
449
|
+
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
|
450
|
+
assert_match(/Main.*invalid numbering/i, exp.message)
|
451
|
+
end
|
411
452
|
end
|
412
453
|
end
|
413
454
|
|
@@ -454,6 +495,11 @@ describe HexaPDF::Parser do
|
|
454
495
|
assert_equal(5, @parser.load_object(@xref).value)
|
455
496
|
end
|
456
497
|
|
498
|
+
it "handles cases where the line contains an invalid string that exceeds the read buffer" do
|
499
|
+
create_parser("(1" << "(abc" * 32188 << "\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
|
500
|
+
assert_equal(6, @parser.load_object(@xref).value)
|
501
|
+
end
|
502
|
+
|
457
503
|
it "ignores invalid objects" do
|
458
504
|
create_parser("1 x obj\n5\nendobj\n1 0 xobj\n6\nendobj\n1 0 obj 4\nendobj\ntrailer\n<</Size 1>>")
|
459
505
|
assert_equal(4, @parser.load_object(@xref).value)
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
43
|
+
<</Producer(HexaPDF version 0.14.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.
|
75
|
+
<</Producer(HexaPDF version 0.14.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -25,14 +25,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
25
25
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
26
26
|
end
|
27
27
|
|
28
|
-
it "fails for unsupported choice fields" do
|
29
|
-
@field = @doc.wrap(@field, type: :XXAcroFormField, subtype: :Ch)
|
30
|
-
@field[:FT] = :Ch
|
31
|
-
@field.initialize_as_list_box
|
32
|
-
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
33
|
-
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
34
|
-
end
|
35
|
-
|
36
28
|
it "fails for unsupported field types" do
|
37
29
|
@field[:FT] = :Unknown
|
38
30
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
@@ -121,6 +113,24 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
121
113
|
[:curve_to, [8.236068, 7.249543, 9.0, 8.572712, 9.0, 10.0]],
|
122
114
|
[:stroke_path], [:restore_graphics_state]])
|
123
115
|
end
|
116
|
+
|
117
|
+
it "handles the special case of a comb field" do
|
118
|
+
@field = @doc.add({FT: :Tx, MaxLen: 4}, type: :XXAcroFormField, subtype: :Tx)
|
119
|
+
@field.initialize_as_comb_text_field
|
120
|
+
@widget = @field.create_widget(@page, Rect: [0, 0, 10, 20])
|
121
|
+
@xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
|
122
|
+
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
123
|
+
@widget.border_style(width: 2)
|
124
|
+
execute
|
125
|
+
assert_operators(@xform.stream,
|
126
|
+
[[:save_graphics_state],
|
127
|
+
[:set_line_width, [2]],
|
128
|
+
[:append_rectangle, [1, 1, 8, 18]],
|
129
|
+
[:move_to, [2.5, 2]], [:line_to, [2.5, 20.0]],
|
130
|
+
[:move_to, [5.0, 2]], [:line_to, [5.0, 20.0]],
|
131
|
+
[:move_to, [7.5, 2]], [:line_to, [7.5, 20.0]],
|
132
|
+
[:stroke_path], [:restore_graphics_state]])
|
133
|
+
end
|
124
134
|
end
|
125
135
|
|
126
136
|
describe "draw_marker" do
|
@@ -342,7 +352,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
342
352
|
end
|
343
353
|
end
|
344
354
|
|
345
|
-
describe "text
|
355
|
+
describe "text fields" do
|
346
356
|
before do
|
347
357
|
@form.set_default_appearance_string
|
348
358
|
@field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
|
@@ -392,35 +402,58 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
392
402
|
assert_equal(@doc.acro_form.default_resources[:Font][:F1], form[:Resources][:Font][:F1])
|
393
403
|
end
|
394
404
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
@field.field_value = ''
|
401
|
-
end
|
405
|
+
it "re-uses the existing form XObject" do
|
406
|
+
@field[:V] = 'test'
|
407
|
+
@generator.create_appearances
|
408
|
+
form = @widget[:AP][:N]
|
409
|
+
form[:key] = :value
|
402
410
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
end
|
411
|
+
@field[:V] = 'test1'
|
412
|
+
@generator.create_appearances
|
413
|
+
assert_same(form, @widget[:AP][:N])
|
414
|
+
refute(form.key?(:key))
|
415
|
+
assert_match(/test1/, form.contents)
|
416
|
+
end
|
410
417
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
418
|
+
describe "font size calculation" do
|
419
|
+
before do
|
420
|
+
@widget[:Rect].height = 20
|
421
|
+
@widget[:Rect].width = 100
|
422
|
+
@field.field_value = ''
|
423
|
+
end
|
424
|
+
|
425
|
+
it "uses the non-zero font size" do
|
426
|
+
@field.set_default_appearance_string(font_size: 10)
|
427
|
+
@generator.create_appearances
|
428
|
+
assert_operators(@widget[:AP][:N].stream,
|
429
|
+
[:set_font_and_size, [:F1, 10]],
|
430
|
+
range: 5)
|
431
|
+
end
|
432
|
+
|
433
|
+
it "calculates the font size based on the rectangle height and border width" do
|
434
|
+
@generator.create_appearances
|
435
|
+
assert_operators(@widget[:AP][:N].stream,
|
436
|
+
[:set_font_and_size, [:F1, 12.923875]],
|
437
|
+
range: 5)
|
438
|
+
@widget.border_style(width: 2, color: :transparent)
|
439
|
+
@generator.create_appearances
|
440
|
+
assert_operators(@widget[:AP][:N].stream,
|
441
|
+
[:set_font_and_size, [:F1, 11.487889]],
|
442
|
+
range: 5)
|
443
|
+
end
|
444
|
+
|
445
|
+
it " in case of mulitline auto-sizing" do
|
446
|
+
@field.initialize_as_multiline_text_field
|
447
|
+
@field[:V] = 'a'
|
448
|
+
@field.set_default_appearance_string(font_size: 0)
|
449
|
+
@generator.create_appearances
|
450
|
+
assert_operators(@widget[:AP][:N].stream,
|
451
|
+
[:set_font_and_size, [:F1, 12]],
|
452
|
+
range: 6)
|
422
453
|
end
|
454
|
+
end
|
423
455
|
|
456
|
+
describe "single line text fields" do
|
424
457
|
describe "quadding e.g. text alignment" do
|
425
458
|
before do
|
426
459
|
@field.field_value = 'Test'
|
@@ -480,16 +513,235 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
480
513
|
end
|
481
514
|
end
|
482
515
|
|
516
|
+
describe "multiline text fields" do
|
517
|
+
before do
|
518
|
+
@field.set_default_appearance_string(font_size: 10)
|
519
|
+
@field.initialize_as_multiline_text_field
|
520
|
+
@widget[:Rect].height = 30
|
521
|
+
@widget[:Rect].width = 100
|
522
|
+
end
|
523
|
+
|
524
|
+
describe "quadding e.g. text alignment" do
|
525
|
+
before do
|
526
|
+
@field[:V] = "Test\nValue"
|
527
|
+
end
|
528
|
+
|
529
|
+
it "works for left aligned text" do
|
530
|
+
@field.text_alignment(:left)
|
531
|
+
@generator.create_appearances
|
532
|
+
assert_operators(@widget[:AP][:N].stream,
|
533
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
|
534
|
+
range: 9)
|
535
|
+
end
|
536
|
+
|
537
|
+
it "works for right aligned text" do
|
538
|
+
@field.text_alignment(:right)
|
539
|
+
@generator.create_appearances
|
540
|
+
assert_operators(@widget[:AP][:N].stream,
|
541
|
+
[:set_text_matrix, [1, 0, 0, 1, 78.55, 16.195]],
|
542
|
+
range: 9)
|
543
|
+
end
|
544
|
+
|
545
|
+
it "works for center aligned text" do
|
546
|
+
@field.text_alignment(:center)
|
547
|
+
@generator.create_appearances
|
548
|
+
assert_operators(@widget[:AP][:N].stream,
|
549
|
+
[:set_text_matrix, [1, 0, 0, 1, 40.275, 16.195]],
|
550
|
+
range: 9)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
it "creates the /N appearance stream according to the set string" do
|
555
|
+
@field.field_value = "Test\nValue"
|
556
|
+
@generator.create_appearances
|
557
|
+
assert_operators(@widget[:AP][:N].stream,
|
558
|
+
[[:begin_marked_content, [:Tx]],
|
559
|
+
[:save_graphics_state],
|
560
|
+
[:append_rectangle, [1, 1, 98, 28]],
|
561
|
+
[:clip_path_non_zero],
|
562
|
+
[:end_path],
|
563
|
+
[:save_graphics_state],
|
564
|
+
[:set_leading, [11.5625]],
|
565
|
+
[:set_font_and_size, [:F1, 10]],
|
566
|
+
[:begin_text],
|
567
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
|
568
|
+
[:show_text, ['Test']],
|
569
|
+
[:move_text_next_line],
|
570
|
+
[:show_text, ['Value']],
|
571
|
+
[:end_text],
|
572
|
+
[:restore_graphics_state],
|
573
|
+
[:restore_graphics_state],
|
574
|
+
[:end_marked_content]])
|
575
|
+
|
576
|
+
@field.field_value = "Test\nTest\nTest"
|
577
|
+
@field.set_default_appearance_string(font_size: 0)
|
578
|
+
@generator.create_appearances
|
579
|
+
assert_operators(@widget[:AP][:N].stream,
|
580
|
+
[[:begin_marked_content, [:Tx]],
|
581
|
+
[:save_graphics_state],
|
582
|
+
[:append_rectangle, [1, 1, 98, 28]],
|
583
|
+
[:clip_path_non_zero],
|
584
|
+
[:end_path],
|
585
|
+
[:save_graphics_state],
|
586
|
+
[:set_leading, [9.25]],
|
587
|
+
[:set_font_and_size, [:F1, 8]],
|
588
|
+
[:begin_text],
|
589
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 18.556]],
|
590
|
+
[:show_text, ['Test']],
|
591
|
+
[:move_text_next_line],
|
592
|
+
[:show_text, ['Test']],
|
593
|
+
[:move_text_next_line],
|
594
|
+
[:show_text, ['Test']],
|
595
|
+
[:end_text],
|
596
|
+
[:restore_graphics_state],
|
597
|
+
[:restore_graphics_state],
|
598
|
+
[:end_marked_content]],
|
599
|
+
)
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
describe "comb text fields" do
|
604
|
+
before do
|
605
|
+
@field.set_default_appearance_string(font_size: 10)
|
606
|
+
@field.initialize_as_comb_text_field
|
607
|
+
@field[:MaxLen] = 10
|
608
|
+
@widget[:Rect].height = 20
|
609
|
+
@widget[:Rect].width = 100
|
610
|
+
end
|
611
|
+
|
612
|
+
describe "quadding e.g. text alignment" do
|
613
|
+
before do
|
614
|
+
@field[:V] = 'Test'
|
615
|
+
end
|
616
|
+
|
617
|
+
it "works for left aligned text" do
|
618
|
+
@field.text_alignment(:left)
|
619
|
+
@generator.create_appearances
|
620
|
+
assert_operators(@widget[:AP][:N].stream,
|
621
|
+
[:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
|
622
|
+
range: 7)
|
623
|
+
end
|
624
|
+
|
625
|
+
it "works for right aligned text" do
|
626
|
+
@field.text_alignment(:right)
|
627
|
+
@generator.create_appearances
|
628
|
+
assert_operators(@widget[:AP][:N].stream,
|
629
|
+
[:set_text_matrix, [1, 0, 0, 1, 62.945, 6.41]],
|
630
|
+
range: 7)
|
631
|
+
end
|
632
|
+
|
633
|
+
it "works for center aligned text" do
|
634
|
+
@field.text_alignment(:center)
|
635
|
+
@generator.create_appearances
|
636
|
+
assert_operators(@widget[:AP][:N].stream,
|
637
|
+
[:set_text_matrix, [1, 0, 0, 1, 32.945, 6.41]],
|
638
|
+
range: 7)
|
639
|
+
end
|
640
|
+
|
641
|
+
it "handles centering like Adobe, e.g. shift left, when text cannot be completely centered" do
|
642
|
+
@field.field_value = 'Hello'
|
643
|
+
@field.text_alignment(:center)
|
644
|
+
@generator.create_appearances
|
645
|
+
assert_operators(@widget[:AP][:N].stream,
|
646
|
+
[:set_text_matrix, [1, 0, 0, 1, 22.39, 6.41]],
|
647
|
+
range: 7)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
it "creates the /N appearance stream according to the set string" do
|
652
|
+
@field.field_value = 'Text'
|
653
|
+
@generator.create_appearances
|
654
|
+
assert_operators(@widget[:AP][:N].stream,
|
655
|
+
[[:begin_marked_content, [:Tx]],
|
656
|
+
[:save_graphics_state],
|
657
|
+
[:append_rectangle, [1, 1, 98, 18]],
|
658
|
+
[:clip_path_non_zero],
|
659
|
+
[:end_path],
|
660
|
+
[:set_font_and_size, [:F1, 10]],
|
661
|
+
[:begin_text],
|
662
|
+
[:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
|
663
|
+
[:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
|
664
|
+
[:end_text],
|
665
|
+
[:restore_graphics_state],
|
666
|
+
[:end_marked_content]])
|
667
|
+
end
|
668
|
+
|
669
|
+
it "fails if the /MaxLen key is not set" do
|
670
|
+
@field.delete(:MaxLen)
|
671
|
+
@field[:V] = 't'
|
672
|
+
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
483
676
|
describe "choice fields" do
|
484
677
|
it "works for combo boxes by using the text appearance method" do
|
485
678
|
@form.set_default_appearance_string
|
486
679
|
field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
|
487
680
|
field.initialize_as_combo_box
|
681
|
+
field.flag(:edit)
|
682
|
+
field.field_value = 'Test'
|
488
683
|
widget = field.create_widget(@page, Rect: [0, 0, 0, 0])
|
489
684
|
generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(widget)
|
490
685
|
generator.create_appearances
|
491
686
|
assert_kind_of(HexaPDF::Type::Form, widget[:AP][:N])
|
492
687
|
end
|
688
|
+
|
689
|
+
describe "list boxes" do
|
690
|
+
before do
|
691
|
+
@field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
|
692
|
+
@field.initialize_as_list_box
|
693
|
+
@field.flag(:multi_select)
|
694
|
+
@field.option_items = ['a', 'b', 'c']
|
695
|
+
@widget = @field.create_widget(@page, Rect: [0, 0, 90, 36])
|
696
|
+
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
697
|
+
end
|
698
|
+
|
699
|
+
it "uses a fixed font size for list box items if auto-sizing is used" do
|
700
|
+
@field.set_default_appearance_string(font_size: 0)
|
701
|
+
@generator.create_appearances
|
702
|
+
assert_operators(@widget[:AP][:N].stream,
|
703
|
+
[:set_font_and_size, [:F1, 12]],
|
704
|
+
range: 8)
|
705
|
+
end
|
706
|
+
|
707
|
+
it "uses the set values instead of the ones from /I if in conflict" do
|
708
|
+
@field[:I] = [0, 1]
|
709
|
+
@field[:V] = ['b']
|
710
|
+
@generator.create_appearances
|
711
|
+
assert_operators(@widget[:AP][:N].stream,
|
712
|
+
[[:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
|
713
|
+
[:append_rectangle, [1, 7.25, 88, 13.875]],
|
714
|
+
[:fill_path_non_zero]],
|
715
|
+
range: 5..7)
|
716
|
+
end
|
717
|
+
|
718
|
+
it "creates the /N appearance stream" do
|
719
|
+
@field[:I] = [1, 2]
|
720
|
+
@field[:V] = ['b', 'c']
|
721
|
+
@generator.create_appearances
|
722
|
+
assert_operators(@widget[:AP][:N].stream,
|
723
|
+
[[:begin_marked_content, [:Tx]],
|
724
|
+
[:save_graphics_state],
|
725
|
+
[:append_rectangle, [1, 1, 88, 34]],
|
726
|
+
[:clip_path_non_zero], [:end_path],
|
727
|
+
[:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
|
728
|
+
[:append_rectangle, [1, 7.25, 88, 13.875]],
|
729
|
+
[:append_rectangle, [1, -6.625, 88, 13.875]],
|
730
|
+
[:fill_path_non_zero],
|
731
|
+
[:save_graphics_state],
|
732
|
+
[:set_leading, [13.875]],
|
733
|
+
[:set_font_and_size, [:F1, 12]],
|
734
|
+
[:set_device_gray_non_stroking_color, [0.0]],
|
735
|
+
[:begin_text],
|
736
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
|
737
|
+
[:show_text, ["a"]],
|
738
|
+
[:move_text_next_line],
|
739
|
+
[:show_text, ["b"]],
|
740
|
+
[:end_text],
|
741
|
+
[:restore_graphics_state], [:restore_graphics_state],
|
742
|
+
[:end_marked_content]])
|
743
|
+
end
|
744
|
+
end
|
493
745
|
end
|
494
746
|
|
495
747
|
describe "font resolution in case the referenced font is not usable" do
|