hexapdf 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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