hexapdf 0.5.0 → 0.6.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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -2
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +1 -1
  5. data/VERSION +1 -1
  6. data/examples/boxes.rb +68 -0
  7. data/examples/graphics.rb +12 -12
  8. data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
  9. data/examples/text_layouter_inline_boxes.rb +66 -0
  10. data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
  11. data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
  12. data/examples/text_layouter_styling.rb +125 -0
  13. data/examples/truetype.rb +5 -7
  14. data/lib/hexapdf/cli/command.rb +1 -0
  15. data/lib/hexapdf/configuration.rb +170 -106
  16. data/lib/hexapdf/content/canvas.rb +41 -36
  17. data/lib/hexapdf/content/graphics_state.rb +15 -0
  18. data/lib/hexapdf/content/operator.rb +1 -1
  19. data/lib/hexapdf/dictionary.rb +20 -8
  20. data/lib/hexapdf/dictionary_fields.rb +8 -6
  21. data/lib/hexapdf/document.rb +25 -26
  22. data/lib/hexapdf/document/fonts.rb +4 -4
  23. data/lib/hexapdf/document/images.rb +2 -2
  24. data/lib/hexapdf/document/pages.rb +16 -16
  25. data/lib/hexapdf/encryption/security_handler.rb +41 -9
  26. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  27. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  28. data/lib/hexapdf/filter/predictor.rb +7 -1
  29. data/lib/hexapdf/font/true_type/font.rb +20 -0
  30. data/lib/hexapdf/font/type1/font.rb +23 -0
  31. data/lib/hexapdf/font_loader.rb +1 -0
  32. data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
  33. data/lib/hexapdf/font_loader/from_file.rb +65 -0
  34. data/lib/hexapdf/image_loader/png.rb +2 -2
  35. data/lib/hexapdf/layout.rb +3 -2
  36. data/lib/hexapdf/layout/box.rb +146 -0
  37. data/lib/hexapdf/layout/inline_box.rb +40 -31
  38. data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
  39. data/lib/hexapdf/layout/style.rb +630 -41
  40. data/lib/hexapdf/layout/text_fragment.rb +80 -12
  41. data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
  42. data/lib/hexapdf/number_tree_node.rb +1 -1
  43. data/lib/hexapdf/parser.rb +4 -1
  44. data/lib/hexapdf/revisions.rb +11 -4
  45. data/lib/hexapdf/stream.rb +8 -9
  46. data/lib/hexapdf/tokenizer.rb +5 -3
  47. data/lib/hexapdf/type.rb +3 -0
  48. data/lib/hexapdf/type/action.rb +56 -0
  49. data/lib/hexapdf/type/actions.rb +52 -0
  50. data/lib/hexapdf/type/actions/go_to.rb +52 -0
  51. data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
  52. data/lib/hexapdf/type/actions/launch.rb +73 -0
  53. data/lib/hexapdf/type/actions/uri.rb +65 -0
  54. data/lib/hexapdf/type/annotation.rb +85 -0
  55. data/lib/hexapdf/type/annotations.rb +51 -0
  56. data/lib/hexapdf/type/annotations/link.rb +70 -0
  57. data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
  58. data/lib/hexapdf/type/annotations/text.rb +81 -0
  59. data/lib/hexapdf/type/catalog.rb +3 -1
  60. data/lib/hexapdf/type/embedded_file.rb +6 -11
  61. data/lib/hexapdf/type/file_specification.rb +4 -6
  62. data/lib/hexapdf/type/font.rb +3 -1
  63. data/lib/hexapdf/type/font_descriptor.rb +18 -16
  64. data/lib/hexapdf/type/form.rb +3 -1
  65. data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
  66. data/lib/hexapdf/type/image.rb +4 -2
  67. data/lib/hexapdf/type/info.rb +2 -5
  68. data/lib/hexapdf/type/names.rb +2 -5
  69. data/lib/hexapdf/type/object_stream.rb +2 -1
  70. data/lib/hexapdf/type/page.rb +14 -1
  71. data/lib/hexapdf/type/page_tree_node.rb +9 -6
  72. data/lib/hexapdf/type/resources.rb +2 -5
  73. data/lib/hexapdf/type/trailer.rb +2 -5
  74. data/lib/hexapdf/type/viewer_preferences.rb +2 -5
  75. data/lib/hexapdf/type/xref_stream.rb +3 -1
  76. data/lib/hexapdf/version.rb +1 -1
  77. data/test/hexapdf/common_tokenizer_tests.rb +3 -1
  78. data/test/hexapdf/content/test_canvas.rb +29 -3
  79. data/test/hexapdf/content/test_graphics_state.rb +11 -0
  80. data/test/hexapdf/content/test_operator.rb +3 -2
  81. data/test/hexapdf/document/test_fonts.rb +8 -8
  82. data/test/hexapdf/document/test_images.rb +4 -12
  83. data/test/hexapdf/document/test_pages.rb +7 -7
  84. data/test/hexapdf/encryption/test_security_handler.rb +1 -5
  85. data/test/hexapdf/filter/test_predictor.rb +40 -12
  86. data/test/hexapdf/font/true_type/test_font.rb +16 -0
  87. data/test/hexapdf/font/type1/test_font.rb +30 -0
  88. data/test/hexapdf/font_loader/test_from_file.rb +29 -0
  89. data/test/hexapdf/font_loader/test_standard14.rb +4 -3
  90. data/test/hexapdf/layout/test_box.rb +104 -0
  91. data/test/hexapdf/layout/test_inline_box.rb +24 -10
  92. data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
  93. data/test/hexapdf/layout/test_style.rb +519 -31
  94. data/test/hexapdf/layout/test_text_fragment.rb +136 -15
  95. data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
  96. data/test/hexapdf/layout/test_text_shaper.rb +1 -1
  97. data/test/hexapdf/test_configuration.rb +12 -6
  98. data/test/hexapdf/test_dictionary.rb +27 -2
  99. data/test/hexapdf/test_dictionary_fields.rb +10 -1
  100. data/test/hexapdf/test_document.rb +14 -13
  101. data/test/hexapdf/test_parser.rb +12 -0
  102. data/test/hexapdf/test_revisions.rb +34 -0
  103. data/test/hexapdf/test_stream.rb +1 -1
  104. data/test/hexapdf/test_type.rb +18 -0
  105. data/test/hexapdf/test_writer.rb +2 -2
  106. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  107. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  108. data/test/hexapdf/type/annotations/test_link.rb +19 -0
  109. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  110. data/test/hexapdf/type/annotations/test_text.rb +38 -0
  111. data/test/hexapdf/type/test_annotation.rb +38 -0
  112. data/test/hexapdf/type/test_file_specification.rb +0 -7
  113. data/test/hexapdf/type/test_info.rb +0 -5
  114. data/test/hexapdf/type/test_page.rb +14 -0
  115. data/test/hexapdf/type/test_page_tree_node.rb +4 -1
  116. data/test/hexapdf/type/test_trailer.rb +0 -4
  117. data/test/test_helper.rb +6 -3
  118. metadata +36 -15
  119. data/examples/text_box_inline_boxes.rb +0 -56
  120. data/examples/text_box_styling.rb +0 -72
  121. data/test/hexapdf/type/test_embedded_file.rb +0 -16
  122. data/test/hexapdf/type/test_names.rb +0 -9
@@ -21,7 +21,7 @@ describe HexaPDF::Layout::TextShaper do
21
21
 
22
22
  describe "Type1 font features" do
23
23
  before do
24
- @font = @doc.fonts.load("Times", custom_encoding: true)
24
+ @font = @doc.fonts.add("Times", custom_encoding: true)
25
25
  end
26
26
 
27
27
  it "handles ligatures" do
@@ -30,13 +30,19 @@ describe HexaPDF::Configuration do
30
30
 
31
31
  it "can create a new config object by merging another one or a hash" do
32
32
  @config['hash'] = {'test' => :test, 'other' => :other}
33
+ @config['array'] = [5, 6]
33
34
  config = @config.merge('test' => :other)
34
35
  assert_equal(:other, config['test'])
35
36
 
36
37
  config['hash']['test'] = :other
37
- config = @config.merge(config)
38
- assert_equal(:other, config['hash']['test'])
39
- assert_equal(:other, config['hash']['other'])
38
+ config1 = @config.merge(config)
39
+ assert_equal(:other, config1['hash']['test'])
40
+ assert_equal(:other, config1['hash']['other'])
41
+
42
+ config2 = @config.merge(config)
43
+ config2['array'].unshift(4)
44
+ assert_equal([4, 5, 6], config2['array'])
45
+ assert_equal([5, 6], config['array'])
40
46
  end
41
47
 
42
48
  describe "constantize" do
@@ -51,9 +57,9 @@ describe HexaPDF::Configuration do
51
57
  end
52
58
 
53
59
  it "returns a constant for a nested option" do
54
- @config['test'] = {'test' => 'HexaPDF', 'const' => HexaPDF}
55
- assert_equal(HexaPDF, @config.constantize('test', 'test'))
56
- assert_equal(HexaPDF, @config.constantize('test', 'const'))
60
+ @config['test'] = {'test' => ['HexaPDF'], 'const' => {'const' => HexaPDF}}
61
+ assert_equal(HexaPDF, @config.constantize('test', 'test', 0))
62
+ assert_equal(HexaPDF, @config.constantize('test', 'const', 'const'))
57
63
 
58
64
  @config['test'] = ['HexaPDF', HexaPDF]
59
65
  assert_equal(HexaPDF, @config.constantize('test', 0))
@@ -69,6 +69,16 @@ describe HexaPDF::Dictionary do
69
69
  refute(HexaPDF::Dictionary.field(:Test))
70
70
  assert_equal([], HexaPDF::Dictionary.each_field.to_a)
71
71
  end
72
+
73
+ it "allows defining a static type" do
74
+ @test_class.define_type(:MyClass)
75
+ assert_equal(:MyClass, @test_class.type)
76
+ assert_equal(:MyClass, @test_class.new({}).type)
77
+ end
78
+
79
+ it "::type returns nil if no static type has been defined" do
80
+ assert_nil(@test_class.type)
81
+ end
72
82
  end
73
83
 
74
84
  describe "after_data_change" do
@@ -166,6 +176,21 @@ describe HexaPDF::Dictionary do
166
176
  end
167
177
  end
168
178
 
179
+ describe "key?" do
180
+ it "returns false for unset keys" do
181
+ refute(@dict.key?(:UnsetKey))
182
+ end
183
+
184
+ it "returns false for keys with a nil value" do
185
+ @dict[:NilKey] = nil
186
+ refute(@dict.key?(:NilKey))
187
+ end
188
+
189
+ it "returns true for keys with a non-nil value" do
190
+ assert(@dict.key?(:Other))
191
+ end
192
+ end
193
+
169
194
  describe "validate_fields" do
170
195
  before do
171
196
  @test_class.define_field(:Inherited, type: [Array, Symbol], required: true, indirect: false)
@@ -260,9 +285,9 @@ describe HexaPDF::Dictionary do
260
285
  end
261
286
  end
262
287
 
263
- describe "to_hash" do
288
+ describe "to_h" do
264
289
  it "returns a shallow copy of the value" do
265
- obj = @dict.to_hash
290
+ obj = @dict.to_h
266
291
  refute_equal(obj.object_id, @dict.value.object_id)
267
292
  assert_equal(obj, @dict.value)
268
293
  end
@@ -154,13 +154,22 @@ describe HexaPDF::DictionaryFields do
154
154
  end
155
155
 
156
156
  it "allows conversion from a string" do
157
- refute(@field.convert?({}))
157
+ assert(@field.convert?("test"))
158
158
 
159
159
  @doc = Minitest::Mock.new
160
160
  @doc.expect(:wrap, :data, [{F: 'test'}, {type: HexaPDF::Type::FileSpecification}])
161
161
  @field.convert('test', @doc)
162
162
  @doc.verify
163
163
  end
164
+
165
+ it "allows conversion from a hash/dictionary" do
166
+ assert(@field.convert?({}))
167
+
168
+ @doc = Minitest::Mock.new
169
+ @doc.expect(:wrap, :data, [{F: 'test'}, {type: HexaPDF::Type::FileSpecification}])
170
+ @field.convert({F: 'test'}, @doc)
171
+ @doc.verify
172
+ end
164
173
  end
165
174
 
166
175
  describe "RectangleConverter" do
@@ -264,15 +264,18 @@ EOF
264
264
 
265
265
  describe "wrap" do
266
266
  before do
267
- @myclass = Class.new(HexaPDF::Object)
268
- @myclass2 = Class.new(HexaPDF::Object)
267
+ @myclass = Class.new(HexaPDF::Dictionary)
268
+ @myclass.define_type(:MyClass)
269
+ @myclass2 = Class.new(HexaPDF::Dictionary)
269
270
  HexaPDF::GlobalConfiguration['object.type_map'][:MyClass] = @myclass
270
- HexaPDF::GlobalConfiguration['object.subtype_map'][:TheSecond] = @myclass2
271
+ HexaPDF::GlobalConfiguration['object.subtype_map'][nil][:Global] = @myclass2
272
+ HexaPDF::GlobalConfiguration['object.subtype_map'][:MyClass] = {TheSecond: @myclass2}
271
273
  end
272
274
 
273
275
  after do
274
276
  HexaPDF::GlobalConfiguration['object.type_map'].delete(:MyClass)
275
- HexaPDF::GlobalConfiguration['object.subtype_map'].delete(:TheSecond)
277
+ HexaPDF::GlobalConfiguration['object.subtype_map'][nil].delete(:Global)
278
+ HexaPDF::GlobalConfiguration['object.subtype_map'][:MyClass].delete(:TheSecond)
276
279
  end
277
280
 
278
281
  it "uses a suitable default type if no special type is specified" do
@@ -324,16 +327,18 @@ EOF
324
327
 
325
328
  it "uses the type/subtype information in the hash that should be wrapped" do
326
329
  assert_kind_of(@myclass, @doc.wrap(Type: :MyClass))
327
- assert_kind_of(@myclass2, @doc.wrap(Subtype: :TheSecond))
328
- assert_kind_of(@myclass2, @doc.wrap(Type: :MyClass, Subtype: :TheSecond))
330
+ refute_kind_of(@myclass2, @doc.wrap(Subtype: :TheSecond))
331
+ assert_kind_of(@myclass2, @doc.wrap(Subtype: :Global))
332
+ assert_kind_of(@myclass2, @doc.wrap(Type: :MyClass, S: :TheSecond))
329
333
  assert_kind_of(@myclass, @doc.wrap(Type: :MyClass, Subtype: :TheThird))
330
334
  end
331
335
 
332
336
  it "respects the given type/subtype arguments" do
333
337
  assert_kind_of(@myclass, @doc.wrap({Type: :Other}, type: :MyClass))
334
- assert_kind_of(@myclass2, @doc.wrap({Subtype: :Other}, subtype: :TheSecond))
338
+ assert_kind_of(@myclass2, @doc.wrap({Subtype: :Other}, subtype: :Global))
335
339
  assert_kind_of(@myclass2, @doc.wrap({Type: :Other, Subtype: :Other},
336
340
  type: :MyClass, subtype: :TheSecond))
341
+ assert_kind_of(@myclass2, @doc.wrap({Subtype: :TheSecond}, type: @myclass))
337
342
  end
338
343
 
339
344
  it "directly uses a class given via the type argument" do
@@ -529,12 +534,8 @@ EOF
529
534
  end
530
535
 
531
536
  describe "task" do
532
- after do
533
- HexaPDF::GlobalConfiguration['task.map'].delete(:test)
534
- end
535
-
536
537
  it "executes the given task with options" do
537
- HexaPDF::GlobalConfiguration['task.map'][:test] = lambda do |doc, arg1:|
538
+ @doc.config['task.map'][:test] = lambda do |doc, arg1:|
538
539
  assert_equal(doc, @doc)
539
540
  assert_equal(:arg1, arg1)
540
541
  end
@@ -542,7 +543,7 @@ EOF
542
543
  end
543
544
 
544
545
  it "executes the given task with a block" do
545
- HexaPDF::GlobalConfiguration['task.map'][:test] = lambda do |doc, **, &block|
546
+ @doc.config['task.map'][:test] = lambda do |doc, **, &block|
546
547
  assert_equal(doc, @doc)
547
548
  block.call('inside')
548
549
  end
@@ -284,6 +284,12 @@ EOF
284
284
  assert_equal(HexaPDF::XRefSection.free_entry(1, 65536), section[1])
285
285
  end
286
286
 
287
+ it "handles xref with missing whitespace at end" do
288
+ create_parser("xref\n0 2\n0000000000 00000 n\n0000000000 65536 n\ntrailer\n<<>>\n")
289
+ section, _trailer = @parser.parse_xref_section_and_trailer(0)
290
+ assert_equal(HexaPDF::XRefSection.free_entry(1, 65536), section[1])
291
+ end
292
+
287
293
  it "fails if the xref keyword is missing/mangled" do
288
294
  create_parser("xTEf\n0 d\n0000000000 00000 n \ntrailer\n<< >>\n")
289
295
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
@@ -351,6 +357,12 @@ EOF
351
357
  assert_match(/invalid.*cross-reference entry/i, exp.message)
352
358
  end
353
359
 
360
+ it "parse_xref_section_and_trailer fails if trailing second whitespace is missing" do
361
+ create_parser("xref\n0 1\n0000000000 00000 n\ntrailer\n<<>>\n")
362
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_xref_section_and_trailer(0) }
363
+ assert_match(/invalid.*cross-reference subsection entry/i, exp.message)
364
+ end
365
+
354
366
  it "parse_indirect_object fails if an empty indirect object is found" do
355
367
  create_parser("1 0 obj\nendobj")
356
368
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.parse_indirect_object }
@@ -123,4 +123,38 @@ EOF
123
123
  assert_equal(400, @revisions[2].object(2).value)
124
124
  end
125
125
  end
126
+
127
+ it "handles invalid PDFs that have a loop via the xref /Prev or /XRefStm entries" do
128
+ io = StringIO.new(<<EOF)
129
+ %PDF-1.7
130
+ 1 0 obj
131
+ 10
132
+ endobj
133
+
134
+ xref
135
+ 0 2
136
+ 0000000000 65535 f
137
+ 0000000009 00000 n
138
+ trailer
139
+ << /Size 2 /Prev 148>>
140
+ startxref
141
+ 28
142
+ %%EOF
143
+
144
+ 2 0 obj
145
+ 300
146
+ endobj
147
+
148
+ xref
149
+ 2 1
150
+ 0000000301 00000 n
151
+ trailer
152
+ << /Size 3 /Prev 28 /XRefStm 148>>
153
+ startxref
154
+ 148
155
+ %%EOF
156
+ EOF
157
+ doc = HexaPDF::Document.new(io: io)
158
+ assert_equal(2, doc.revisions.count)
159
+ end
126
160
  end
@@ -132,7 +132,7 @@ describe HexaPDF::Stream do
132
132
  end
133
133
 
134
134
  def encoded_data(str, encoders = [])
135
- map = HexaPDF::GlobalConfiguration['filter.map']
135
+ map = @document.config['filter.map']
136
136
  tmp = feeder(str)
137
137
  encoders.each {|e| tmp = ::Object.const_get(map[e]).encoder(tmp)}
138
138
  collector(tmp)
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/type'
5
+
6
+ describe HexaPDF::Type do
7
+ it "all autoload files have no syntax error" do
8
+ HexaPDF::Type.constants.each do |const|
9
+ HexaPDF::Type.const_get(const) # no assert needed here
10
+ end
11
+ HexaPDF::Type::Actions.constants.each do |const|
12
+ HexaPDF::Type::Actions.const_get(const) # no assert needed here
13
+ end
14
+ HexaPDF::Type::Annotations.constants.each do |const|
15
+ HexaPDF::Type::Annotations.const_get(const) # no assert needed here
16
+ end
17
+ end
18
+ end
@@ -40,7 +40,7 @@ startxref
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.5.0)>>
43
+ <</Producer(HexaPDF version 0.6.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ startxref
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.5.0)>>
75
+ <</Producer(HexaPDF version 0.6.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/actions/launch'
6
+
7
+ describe HexaPDF::Type::Actions::Launch do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @action = HexaPDF::Type::Actions::Launch.new({}, document: @doc)
11
+ end
12
+
13
+ describe "validation" do
14
+ it "needs a launch target" do
15
+ refute(@action.validate)
16
+
17
+ @action.value = {F: {}}
18
+ assert(@action.validate)
19
+
20
+ @action.value = {Win: {F: "test.exe"}}
21
+ assert(@action.validate)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/actions/uri'
6
+
7
+ describe HexaPDF::Type::Actions::URI do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @action = HexaPDF::Type::Actions::URI.new({}, document: @doc)
11
+ end
12
+
13
+ describe "validation" do
14
+ it "URI needs to be ASCII only" do
15
+ refute(@action.validate)
16
+
17
+ @action[:URI] = "hellö"
18
+ refute(@action.validate(auto_correct: false))
19
+ assert(@action.validate(auto_correct: true))
20
+ assert_equal("hell%C3%B6", @action[:URI])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/link'
6
+
7
+ describe HexaPDF::Type::Annotations::Link do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @annot = HexaPDF::Type::Annotations::Link.new({Rect: [0, 0, 1, 1]}, document: @doc)
11
+ end
12
+
13
+ describe "validation" do
14
+ it "checks for valid /H value" do
15
+ @annot[:H] = :invalid
16
+ refute(@annot.validate {|msg| assert_match(/contains invalid value/, msg)})
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/markup_annotation'
6
+
7
+ describe HexaPDF::Type::Annotations::MarkupAnnotation do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @annot = HexaPDF::Type::Annotations::MarkupAnnotation.new({Subtype: :Text, Rect: [0, 0, 1, 1]},
11
+ document: @doc)
12
+ end
13
+
14
+ describe "validation" do
15
+ it "needs IRT set if RT is set" do
16
+ assert(@annot.validate)
17
+
18
+ @annot[:RT] = :R
19
+ refute(@annot.validate)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/text'
6
+
7
+ describe HexaPDF::Type::Annotations::Text do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @doc.version = '1.5'
11
+ @annot = HexaPDF::Type::Annotations::Text.new({Rect: [0, 0, 1, 1]}, document: @doc)
12
+ end
13
+
14
+ describe "validation" do
15
+ it "checks for correct /StateModel values" do
16
+ @annot[:StateModel] = 'Invalid'
17
+ refute(@annot.validate {|msg| assert_match(/contains invalid value/, msg)})
18
+ end
19
+
20
+ it "automatically sets /StateModel based on the /State entry if possible" do
21
+ @annot[:State] = 'Marked'
22
+ assert(@annot.validate)
23
+ assert_equal('Marked', @annot[:StateModel])
24
+
25
+ @annot.delete(:StateModel)
26
+ @annot[:State] = 'Unknown'
27
+ refute(@annot.validate {|msg| assert_match(/StateModel required/, msg)})
28
+ end
29
+
30
+ it "checks whether /State and /StateModel match" do
31
+ @annot[:State] = 'Marked'
32
+ @annot[:StateModel] = 'Marked'
33
+ assert(@annot.validate)
34
+ @annot[:StateModel] = 'Review'
35
+ refute(@annot.validate {|msg| assert_match(/\/State and \/StateModel don't agree/, msg)})
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotation'
6
+
7
+ describe HexaPDF::Type::Annotation do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @annot = @doc.add(Type: :Annotation, F: 0b100011)
11
+ end
12
+
13
+ describe "flags" do
14
+ it "returns all flags" do
15
+ assert_equal([:invisible, :hidden, :no_view], @annot.flags)
16
+ end
17
+ end
18
+
19
+ describe "flagged?" do
20
+ it "returns true if the given flag is set" do
21
+ assert(@annot.flagged?(:hidden))
22
+ refute(@annot.flagged?(:locked))
23
+ end
24
+
25
+ it "raises an error if an unknown flag name is provided" do
26
+ assert_raises(ArgumentError) { @annot.flagged?(:unknown) }
27
+ end
28
+ end
29
+
30
+ describe "flag" do
31
+ it "sets the given flag bits" do
32
+ @annot.flag(:locked)
33
+ assert_equal([:invisible, :hidden, :no_view, :locked], @annot.flags)
34
+ @annot.flag(:locked, clear_existing: true)
35
+ assert_equal([:locked], @annot.flags)
36
+ end
37
+ end
38
+ end