hexapdf 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +3 -3
  5. data/VERSION +1 -1
  6. data/examples/arc.rb +1 -1
  7. data/examples/graphics.rb +1 -1
  8. data/examples/hello_world.rb +1 -1
  9. data/examples/merging.rb +1 -1
  10. data/examples/show_char_bboxes.rb +1 -1
  11. data/examples/standard_pdf_fonts.rb +1 -1
  12. data/examples/truetype.rb +1 -1
  13. data/lib/hexapdf/cli.rb +14 -7
  14. data/lib/hexapdf/cli/extract.rb +1 -1
  15. data/lib/hexapdf/cli/info.rb +2 -2
  16. data/lib/hexapdf/cli/inspect.rb +4 -4
  17. data/lib/hexapdf/cli/modify.rb +151 -51
  18. data/lib/hexapdf/configuration.rb +1 -1
  19. data/lib/hexapdf/content/canvas.rb +1 -1
  20. data/lib/hexapdf/content/processor.rb +1 -1
  21. data/lib/hexapdf/dictionary.rb +6 -19
  22. data/lib/hexapdf/dictionary_fields.rb +1 -1
  23. data/lib/hexapdf/document.rb +23 -16
  24. data/lib/hexapdf/document/files.rb +130 -0
  25. data/lib/hexapdf/{font_utils.rb → document/fonts.rb} +40 -38
  26. data/lib/hexapdf/document/images.rb +117 -0
  27. data/lib/hexapdf/document/pages.rb +125 -0
  28. data/lib/hexapdf/encryption/aes.rb +1 -1
  29. data/lib/hexapdf/encryption/ruby_aes.rb +10 -10
  30. data/lib/hexapdf/encryption/standard_security_handler.rb +11 -8
  31. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  32. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -6
  33. data/lib/hexapdf/font/cmap/writer.rb +5 -7
  34. data/lib/hexapdf/font/true_type.rb +4 -1
  35. data/lib/hexapdf/font/true_type/font.rb +8 -16
  36. data/lib/hexapdf/font/true_type/table.rb +5 -16
  37. data/lib/hexapdf/font/true_type/table/cmap.rb +2 -7
  38. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +2 -6
  39. data/lib/hexapdf/font/true_type/table/directory.rb +0 -5
  40. data/lib/hexapdf/font/true_type/table/glyf.rb +3 -11
  41. data/lib/hexapdf/font/true_type/table/head.rb +0 -12
  42. data/lib/hexapdf/font/true_type/table/hhea.rb +0 -7
  43. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -5
  44. data/lib/hexapdf/font/true_type/table/loca.rb +0 -4
  45. data/lib/hexapdf/font/true_type/table/maxp.rb +0 -8
  46. data/lib/hexapdf/font/true_type/table/name.rb +3 -17
  47. data/lib/hexapdf/font/true_type/table/os2.rb +0 -14
  48. data/lib/hexapdf/font/true_type/table/post.rb +0 -8
  49. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  50. data/lib/hexapdf/font/type1.rb +2 -2
  51. data/lib/hexapdf/font/type1/font.rb +2 -1
  52. data/lib/hexapdf/font/type1/font_metrics.rb +10 -1
  53. data/lib/hexapdf/font/type1_wrapper.rb +2 -1
  54. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  55. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  56. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  57. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  58. data/lib/hexapdf/image_loader/png.rb +2 -2
  59. data/lib/hexapdf/object.rb +18 -5
  60. data/lib/hexapdf/rectangle.rb +8 -1
  61. data/lib/hexapdf/revisions.rb +4 -2
  62. data/lib/hexapdf/serializer.rb +3 -3
  63. data/lib/hexapdf/stream.rb +3 -2
  64. data/lib/hexapdf/task/dereference.rb +4 -5
  65. data/lib/hexapdf/task/optimize.rb +6 -3
  66. data/lib/hexapdf/tokenizer.rb +3 -3
  67. data/lib/hexapdf/type/file_specification.rb +2 -2
  68. data/lib/hexapdf/type/form.rb +19 -0
  69. data/lib/hexapdf/type/page.rb +21 -6
  70. data/lib/hexapdf/type/page_tree_node.rb +27 -34
  71. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  72. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  73. data/lib/hexapdf/version.rb +1 -1
  74. data/man/man1/hexapdf.1 +259 -187
  75. data/test/hexapdf/content/graphic_object/test_arc.rb +1 -1
  76. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +1 -1
  77. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +1 -1
  78. data/test/hexapdf/content/test_canvas.rb +1 -1
  79. data/test/hexapdf/document/test_files.rb +71 -0
  80. data/test/hexapdf/{test_font_utils.rb → document/test_fonts.rb} +1 -2
  81. data/test/hexapdf/document/test_images.rb +78 -0
  82. data/test/hexapdf/document/test_pages.rb +114 -0
  83. data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -5
  84. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/common.rb +0 -4
  86. data/test/hexapdf/font/true_type/table/test_cmap.rb +0 -6
  87. data/test/hexapdf/font/true_type/table/test_directory.rb +0 -5
  88. data/test/hexapdf/font/true_type/table/test_glyf.rb +5 -8
  89. data/test/hexapdf/font/true_type/table/test_head.rb +0 -20
  90. data/test/hexapdf/font/true_type/table/test_hhea.rb +0 -7
  91. data/test/hexapdf/font/true_type/table/test_hmtx.rb +2 -7
  92. data/test/hexapdf/font/true_type/table/test_loca.rb +4 -8
  93. data/test/hexapdf/font/true_type/table/test_maxp.rb +0 -7
  94. data/test/hexapdf/font/true_type/table/test_name.rb +0 -19
  95. data/test/hexapdf/font/true_type/table/test_os2.rb +0 -8
  96. data/test/hexapdf/font/true_type/table/test_post.rb +0 -13
  97. data/test/hexapdf/font/true_type/test_font.rb +14 -38
  98. data/test/hexapdf/font/true_type/test_table.rb +0 -9
  99. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  100. data/test/hexapdf/task/test_dereference.rb +5 -1
  101. data/test/hexapdf/task/test_optimize.rb +1 -1
  102. data/test/hexapdf/test_dictionary.rb +4 -0
  103. data/test/hexapdf/test_document.rb +0 -7
  104. data/test/hexapdf/test_importer.rb +4 -4
  105. data/test/hexapdf/test_object.rb +31 -9
  106. data/test/hexapdf/test_rectangle.rb +18 -0
  107. data/test/hexapdf/test_revisions.rb +7 -0
  108. data/test/hexapdf/test_serializer.rb +6 -0
  109. data/test/hexapdf/test_writer.rb +2 -2
  110. data/test/hexapdf/type/test_form.rb +12 -0
  111. data/test/hexapdf/type/test_page.rb +39 -20
  112. data/test/hexapdf/type/test_page_tree_node.rb +28 -21
  113. metadata +21 -9
  114. data/lib/hexapdf/document_utils.rb +0 -209
  115. data/test/hexapdf/test_document_utils.rb +0 -144
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'test_helper'
4
4
  require 'hexapdf/rectangle'
5
+ require 'hexapdf/document'
5
6
 
6
7
  describe HexaPDF::Rectangle do
7
8
  describe "after_data_change" do
@@ -33,4 +34,21 @@ describe HexaPDF::Rectangle do
33
34
  assert_equal(2, rect.width)
34
35
  assert_equal(4, rect.height)
35
36
  end
37
+
38
+ describe "validation" do
39
+ it "ensures that it is a correct PDF rectangle" do
40
+ doc = HexaPDF::Document.new
41
+ rect = HexaPDF::Rectangle.new([0, 1, 2, 3], document: doc)
42
+ assert(rect.validate)
43
+
44
+ rect.value.shift
45
+ refute(rect.validate)
46
+
47
+ rect.value.unshift(:A)
48
+ refute(rect.validate)
49
+
50
+ rect.data.value = :A
51
+ refute(rect.validate)
52
+ end
53
+ end
36
54
  end
@@ -82,6 +82,13 @@ EOF
82
82
  assert_equal(1, @revisions.each.to_a.size)
83
83
  assert_equal([10, 200], @revisions.current.each.to_a.sort.map(&:value))
84
84
  end
85
+
86
+ it "handles objects correctly that are in multiple revisions" do
87
+ @revisions.current.add(@revisions[0].object(1))
88
+ @revisions.merge
89
+ assert_equal(1, @revisions.each.to_a.size)
90
+ assert_equal([10, 200], @revisions.current.each.to_a.sort.map(&:value))
91
+ end
85
92
  end
86
93
 
87
94
  describe "initialize" do
@@ -25,6 +25,12 @@ describe HexaPDF::Serializer do
25
25
  assert_equal(result, @serializer.serialize(object))
26
26
  end
27
27
 
28
+ it "works correctly with unnamed classes and modules" do
29
+ klass = Class.new(String)
30
+ s = klass.new("test")
31
+ assert_serialized("(test)", s)
32
+ end
33
+
28
34
  it "serializes nil" do
29
35
  assert_serialized("null", nil)
30
36
  end
@@ -40,7 +40,7 @@ startxref
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.1.0)>>
43
+ <</Producer(HexaPDF version 0.2.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.1.0)>>
75
+ <</Producer(HexaPDF version 0.2.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -57,4 +57,16 @@ describe HexaPDF::Type::Form do
57
57
  assert_equal([[:set_line_width, [10]]], processor.recorded_ops)
58
58
  end
59
59
  end
60
+
61
+ describe "canvas" do
62
+ it "always returns the same Canvas instance" do
63
+ canvas = @form.canvas
64
+ assert_same(canvas, @form.canvas)
65
+ end
66
+
67
+ it "fails if the form XObject already has data" do
68
+ @form.stream = '10 w'
69
+ assert_raises(HexaPDF::Error) { @form.canvas }
70
+ end
71
+ end
60
72
  end
@@ -49,13 +49,11 @@ describe HexaPDF::Type::Page do
49
49
 
50
50
  @kid[:Rotate] = :kid_rotate
51
51
  assert_equal(:kid_rotate, @page[:Rotate])
52
- @kid.delete(:Rotate)
53
- assert_equal(0, @page[:Rotate])
54
52
  end
55
53
 
56
- it "fails if no parent node is associated" do
57
- page = @doc.add(Type: :Page)
58
- assert_raises(HexaPDF::InvalidPDFObjectError) { page[:Resources] }
54
+ it "returns nil or the default value if no value is set anywhere" do
55
+ assert_nil(@page[:MediaBox])
56
+ assert_equal(0, @page[:Rotate])
59
57
  end
60
58
  end
61
59
 
@@ -71,7 +69,7 @@ describe HexaPDF::Type::Page do
71
69
 
72
70
  describe "box" do
73
71
  before do
74
- @page = @doc.pages.add_page
72
+ @page = @doc.pages.add
75
73
  end
76
74
 
77
75
  it "returns the correct media box" do
@@ -106,13 +104,13 @@ describe HexaPDF::Type::Page do
106
104
 
107
105
  describe "contents" do
108
106
  it "returns the contents of a single content stream" do
109
- page = @doc.pages.add_page
107
+ page = @doc.pages.add
110
108
  page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
111
109
  assert_equal('q 10 w Q', page.contents)
112
110
  end
113
111
 
114
112
  it "returns the concatenated contents of multiple content stream" do
115
- page = @doc.pages.add_page
113
+ page = @doc.pages.add
116
114
  page[:Contents] = [@doc.wrap({}, stream: 'q 10'), @doc.wrap({}, stream: 'w Q')]
117
115
  assert_equal('q 10 w Q', page.contents)
118
116
  end
@@ -120,13 +118,13 @@ describe HexaPDF::Type::Page do
120
118
 
121
119
  describe "contents=" do
122
120
  it "creates a content stream if none already exist" do
123
- page = @doc.pages.add_page
121
+ page = @doc.pages.add
124
122
  page.contents = 'test'
125
123
  assert_equal('test', page[:Contents].stream)
126
124
  end
127
125
 
128
126
  it "reuses an existing content stream" do
129
- page = @doc.pages.add_page
127
+ page = @doc.pages.add
130
128
  page[:Contents] = content = @doc.wrap({}, stream: 'q 10 w Q')
131
129
  page.contents = 'test'
132
130
  assert_equal(content, page[:Contents])
@@ -134,7 +132,7 @@ describe HexaPDF::Type::Page do
134
132
  end
135
133
 
136
134
  it "reuses the first content stream and deletes the rest if more than one exist" do
137
- page = @doc.pages.add_page
135
+ page = @doc.pages.add
138
136
  page[:Contents] = [content = @doc.add({}, stream: 'q 10 w Q'), @doc.add({}, stream: 'q Q')]
139
137
  page.contents = 'test'
140
138
  assert_equal(content, page[:Contents])
@@ -144,33 +142,54 @@ describe HexaPDF::Type::Page do
144
142
 
145
143
  describe "resources" do
146
144
  it "creates the resource dictionary if it is not found" do
147
- page = @doc.add(Type: :Page, Parent: @doc.pages)
145
+ page = @doc.add(Type: :Page, Parent: @doc.pages.root)
148
146
  resources = page.resources
149
147
  assert_equal(:XXResources, resources.type)
150
148
  assert_equal({}, resources.value)
151
149
  end
152
150
 
153
151
  it "returns the already used resource dictionary" do
154
- @doc.pages[:Resources] = {Font: {F1: nil}}
155
- page = @doc.pages.add_page(@doc.add(Type: :Page))
152
+ @doc.pages.root[:Resources] = {Font: {F1: nil}}
153
+ page = @doc.pages.add(@doc.add(Type: :Page))
156
154
  resources = page.resources
157
155
  assert_equal(:XXResources, resources.type)
158
- assert_equal(@doc.pages[:Resources], resources)
156
+ assert_equal(@doc.pages.root[:Resources], resources)
159
157
  end
160
158
  end
161
159
 
162
160
  describe "process_contents" do
163
161
  it "parses the contents and processes it" do
164
- page = @doc.pages.add_page
162
+ page = @doc.pages.add
165
163
  page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
166
164
  assert_operators(page, [[:save_graphics_state], [:set_line_width, [10]],
167
165
  [:restore_graphics_state]])
168
166
  end
169
167
  end
170
168
 
169
+ describe "index" do
170
+ it "returns the index of the page in the page tree" do
171
+ kid1 = @doc.add(Type: :Pages, Parent: @doc.pages.root, Count: 4)
172
+ @doc.pages.root[:Kids] << kid1
173
+
174
+ kid11 = @doc.add(Type: :Pages, Parent: kid1)
175
+ page1 = kid11.add_page
176
+ kid1[:Kids] << kid11
177
+ page2 = kid1.add_page
178
+ kid12 = @doc.add(Type: :Pages, Parent: kid1)
179
+ page3 = kid12.add_page
180
+ page4 = kid12.add_page
181
+ kid1[:Kids] << kid12
182
+
183
+ assert_equal(0, page1.index)
184
+ assert_equal(1, page2.index)
185
+ assert_equal(2, page3.index)
186
+ assert_equal(3, page4.index)
187
+ end
188
+ end
189
+
171
190
  describe "canvas" do
172
191
  before do
173
- @page = @doc.pages.add_page
192
+ @page = @doc.pages.add
174
193
  end
175
194
 
176
195
  it "works correctly if invoked on an empty page, using type :page in first invocation" do
@@ -234,7 +253,7 @@ describe HexaPDF::Type::Page do
234
253
 
235
254
  describe "to_form_xobject" do
236
255
  it "creates an independent form xobject" do
237
- page = @doc.pages.add_page
256
+ page = @doc.pages.add
238
257
  page.contents = "test"
239
258
  form = page.to_form_xobject
240
259
  refute(form.indirect?)
@@ -242,13 +261,13 @@ describe HexaPDF::Type::Page do
242
261
  end
243
262
 
244
263
  it "works for pages without content" do
245
- page = @doc.pages.add_page
264
+ page = @doc.pages.add
246
265
  form = page.to_form_xobject
247
266
  assert_equal('', form.stream)
248
267
  end
249
268
 
250
269
  it "uses the raw stream data if possible to avoid unnecessary work" do
251
- page = @doc.pages.add_page
270
+ page = @doc.pages.add
252
271
  page.contents = HexaPDF::StreamData.new(StringIO.new("test"))
253
272
  form = page.to_form_xobject
254
273
  assert_same(form.raw_stream, page[:Contents].raw_stream)
@@ -151,40 +151,47 @@ describe HexaPDF::Type::PageTreeNode do
151
151
  define_multilevel_page_tree
152
152
  end
153
153
 
154
- it "does nothing if the page index is not valid" do
155
- assert_nil(@root.delete_page(20))
156
- assert_nil(@root.delete_page(-20))
157
- assert_equal(8, @root.page_count)
158
- end
159
-
160
- it "deletes the correct page" do
154
+ it "deletes the correct page by index" do
161
155
  assert_equal(@pages[2], @root.delete_page(2))
162
156
  assert_equal(2, @kid12.page_count)
163
157
  assert_equal(4, @kid1.page_count)
164
158
  assert_equal(7, @root.page_count)
159
+ assert_nil(@pages[2][:Parent])
165
160
 
166
161
  assert_equal(@pages[5], @root.delete_page(4))
167
162
  assert_equal(6, @root.page_count)
163
+ assert_nil(@pages[5][:Parent])
168
164
  end
169
165
 
170
- it "deletes intermediate page tree nodes if they contain only one child after deletion" do
171
- assert_equal(@pages[0], @root.delete_page(0))
172
- assert_equal(4, @kid1.page_count)
166
+ it "deletes the given page" do
167
+ assert_equal(@pages[2], @root.delete_page(@pages[2]))
168
+ assert_equal(@pages[5], @root.delete_page(@pages[5]))
169
+ end
170
+
171
+ it "allows deleting a page from an intermediary node" do
172
+ assert_equal(@pages[2], @kid1.delete_page(@pages[2]))
173
173
  assert_equal(7, @root.page_count)
174
- assert_nil(@doc.object(@kid11).value)
175
- assert_equal(@pages[1], @kid1[:Kids][0])
176
174
  end
177
175
 
178
- it "deletes intermediate page tree nodes if they don't have any children after deletion" do
179
- node = @doc.add(Type: :Pages, Parent: @root)
180
- page = node.add_page
181
- @root[:Kids] << node
182
- @root[:Count] += 1
176
+ it "does nothing if the page index is not valid" do
177
+ assert_nil(@root.delete_page(20))
178
+ assert_nil(@root.delete_page(-20))
179
+ assert_equal(8, @root.page_count)
180
+ end
183
181
 
184
- assert_equal(page, @root.delete_page(-1))
185
- assert_nil(@doc.object(node).value)
186
- refute_equal(node, @root[:Kids].last)
187
- assert(8, @root.page_count)
182
+ it "does nothing if the page is not in its parent's /Kids array" do
183
+ @kid12[:Kids].shift
184
+ assert_nil(@root.delete_page(@pages[2]))
185
+ assert_equal(8, @root.page_count)
186
+ end
187
+
188
+ it "does nothing if the page is not part of the page tree" do
189
+ pages = @doc.add(Type: :Pages, Count: 1)
190
+ page = @doc.add(Type: :Page, Parent: pages)
191
+ pages[:Kids] << page
192
+
193
+ assert_nil(@root.delete_page(page))
194
+ assert_equal(8, @root.page_count)
188
195
  end
189
196
  end
190
197
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-26 00:00:00.000000000 Z
11
+ date: 2016-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -31,19 +31,25 @@ dependencies:
31
31
  - !ruby/object:Gem::Version
32
32
  version: 3.0.1
33
33
  - !ruby/object:Gem::Dependency
34
- name: ronn
34
+ name: kramdown
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.7'
39
+ version: '1.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.13.0
40
43
  type: :development
41
44
  prerelease: false
42
45
  version_requirements: !ruby/object:Gem::Requirement
43
46
  requirements:
44
47
  - - "~>"
45
48
  - !ruby/object:Gem::Version
46
- version: '0.7'
49
+ version: '1.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.13.0
47
53
  description: |-
48
54
  HexaPDF is a pure Ruby library with an accompanying application for working with PDF files.
49
55
 
@@ -56,6 +62,7 @@ executables:
56
62
  extensions: []
57
63
  extra_rdoc_files: []
58
64
  files:
65
+ - CHANGELOG.md
59
66
  - CONTRIBUTERS
60
67
  - LICENSE
61
68
  - README.md
@@ -112,7 +119,10 @@ files:
112
119
  - lib/hexapdf/dictionary.rb
113
120
  - lib/hexapdf/dictionary_fields.rb
114
121
  - lib/hexapdf/document.rb
115
- - lib/hexapdf/document_utils.rb
122
+ - lib/hexapdf/document/files.rb
123
+ - lib/hexapdf/document/fonts.rb
124
+ - lib/hexapdf/document/images.rb
125
+ - lib/hexapdf/document/pages.rb
116
126
  - lib/hexapdf/encryption.rb
117
127
  - lib/hexapdf/encryption/aes.rb
118
128
  - lib/hexapdf/encryption/arc4.rb
@@ -173,7 +183,6 @@ files:
173
183
  - lib/hexapdf/font_loader.rb
174
184
  - lib/hexapdf/font_loader/from_configuration.rb
175
185
  - lib/hexapdf/font_loader/standard14.rb
176
- - lib/hexapdf/font_utils.rb
177
186
  - lib/hexapdf/image_loader.rb
178
187
  - lib/hexapdf/image_loader/jpeg.rb
179
188
  - lib/hexapdf/image_loader/pdf.rb
@@ -306,6 +315,10 @@ files:
306
315
  - test/hexapdf/content/test_parser.rb
307
316
  - test/hexapdf/content/test_processor.rb
308
317
  - test/hexapdf/content/test_transformation_matrix.rb
318
+ - test/hexapdf/document/test_files.rb
319
+ - test/hexapdf/document/test_fonts.rb
320
+ - test/hexapdf/document/test_images.rb
321
+ - test/hexapdf/document/test_pages.rb
309
322
  - test/hexapdf/encryption/common.rb
310
323
  - test/hexapdf/encryption/test_aes.rb
311
324
  - test/hexapdf/encryption/test_arc4.rb
@@ -350,6 +363,7 @@ files:
350
363
  - test/hexapdf/font/true_type/test_table.rb
351
364
  - test/hexapdf/font/type1/test_afm_parser.rb
352
365
  - test/hexapdf/font/type1/test_font.rb
366
+ - test/hexapdf/font/type1/test_font_metrics.rb
353
367
  - test/hexapdf/font/type1/test_pfb_parser.rb
354
368
  - test/hexapdf/font_loader/test_from_configuration.rb
355
369
  - test/hexapdf/font_loader/test_standard14.rb
@@ -363,9 +377,7 @@ files:
363
377
  - test/hexapdf/test_dictionary.rb
364
378
  - test/hexapdf/test_dictionary_fields.rb
365
379
  - test/hexapdf/test_document.rb
366
- - test/hexapdf/test_document_utils.rb
367
380
  - test/hexapdf/test_filter.rb
368
- - test/hexapdf/test_font_utils.rb
369
381
  - test/hexapdf/test_importer.rb
370
382
  - test/hexapdf/test_object.rb
371
383
  - test/hexapdf/test_parser.rb
@@ -1,209 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- #--
4
- # This file is part of HexaPDF.
5
- #
6
- # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
8
- #
9
- # HexaPDF is free software: you can redistribute it and/or modify it
10
- # under the terms of the GNU Affero General Public License version 3 as
11
- # published by the Free Software Foundation with the addition of the
12
- # following permission added to Section 15 as permitted in Section 7(a):
13
- # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
- # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
- # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
- #
17
- # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
- # License for more details.
21
- #
22
- # You should have received a copy of the GNU Affero General Public License
23
- # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
- #
25
- # The interactive user interfaces in modified source and object code
26
- # versions of HexaPDF must display Appropriate Legal Notices, as required
27
- # under Section 5 of the GNU Affero General Public License version 3.
28
- #
29
- # In accordance with Section 7(b) of the GNU Affero General Public
30
- # License, a covered work must retain the producer line in every PDF that
31
- # is created or manipulated using HexaPDF.
32
- #++
33
-
34
- require 'hexapdf/configuration'
35
-
36
- module HexaPDF
37
-
38
- # This class provides utility functions for PDF documents. It is available through the
39
- # HexaPDF::Document#utils method.
40
- #
41
- # Some functions can't be attributed to a single "manager" object. For example, while embedding a
42
- # file can be done within a HexaPDF::Type::Filespecification object, loading an image from a file
43
- # as a PDF object doesn't have such a place. Such functions are available via this class.
44
- class DocumentUtils
45
-
46
- # This module provides methods for managing the images embedded in a PDF file; images
47
- # themselves are represented by the HexaPDF::Type::Image class.
48
- #
49
- # Since an image can be used as a mask for another image, not all image objects found in a PDF
50
- # are really used as images. Such cases are all handled by this class automatically.
51
- module Images
52
-
53
- # :call-seq:
54
- # images.add_image(file) -> image
55
- # images.add_image(io) -> image
56
- #
57
- # Adds the image from the given file or IO to the PDF and returns the image object.
58
- #
59
- # If the image has been added to the PDF before (i.e. if there is an image object with the
60
- # same path name), the already existing image object is returned.
61
- def add_image(file_or_io)
62
- name = if file_or_io.kind_of?(String)
63
- file_or_io
64
- elsif file_or_io.respond_to?(:to_path)
65
- file_or_io.to_path
66
- end
67
- if name
68
- name = File.absolute_path(name)
69
- image = each_image.find {|im| im.source_path == name}
70
- end
71
- unless image
72
- image = image_loader_for(file_or_io).load(@document, file_or_io)
73
- image.source_path = name
74
- end
75
- image
76
- end
77
-
78
- # :call-seq:
79
- # images.each_image {|image| block } -> images
80
- # images.each_image -> Enumerator
81
- #
82
- # Iterates over all images in the PDF.
83
- #
84
- # Note that only real images are yielded which means, for example, that images used as soft
85
- # mask are not.
86
- def each_image(&block)
87
- images = @document.each(current: false).select do |obj|
88
- obj[:Subtype] == :Image && !obj[:ImageMask]
89
- end
90
- masks = images.each_with_object([]) do |image, temp|
91
- temp << image[:Mask] if image[:Mask].kind_of?(Stream)
92
- temp << image[:SMask] if image[:SMask].kind_of?(Stream)
93
- end
94
- (images - masks).each(&block)
95
- end
96
-
97
- private
98
-
99
- # Returns the image loader (see ImageLoader) for the given file or IO stream or raises an
100
- # error if no suitable image loader is found.
101
- def image_loader_for(file_or_io)
102
- GlobalConfiguration['image_loader'].each_index do |index|
103
- loader = GlobalConfiguration.constantize('image_loader', index) do
104
- raise HexaPDF::Error, "Couldn't retrieve image loader from configuration"
105
- end
106
- return loader if loader.handles?(file_or_io)
107
- end
108
-
109
- raise HexaPDF::Error, "Couldn't find suitable image loader"
110
- end
111
-
112
- end
113
-
114
-
115
- # This module provides methods for managing file specification of a PDF file.
116
- #
117
- # Note that for a given PDF file not all file specifications may be found, e.g. when a file
118
- # specification is only a string. Therefore this module can only handle those file
119
- # specifications that are indirect file specification dictionaries with the /Type key set.
120
- module Files
121
-
122
- # :call-seq:
123
- # files.add_file(filename, name: File.basename(filename), description: nil, embed: true) -> file_spec
124
- # files.add_file(io, name:, description: nil) -> file_spec
125
- #
126
- # Adds the file or IO to the PDF and returns the corresponding file specification object.
127
- #
128
- # Options:
129
- #
130
- # name::
131
- # The name that should be used for the file path. This name is also for registering the
132
- # file in the EmbeddedFiles name tree.
133
- #
134
- # description::
135
- # A description of the file.
136
- #
137
- # embed::
138
- # When an IO object is given, it is always embedded and this option is ignored.
139
- #
140
- # When a filename is given and this option is +true+, then the file is embedded. Otherwise
141
- # only a reference to it is stored.
142
- #
143
- # See: HexaPDF::Type::FileSpecification
144
- def add_file(file_or_io, name: nil, description: nil, embed: true)
145
- name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
146
- if name.nil?
147
- raise ArgumentError, "The name argument is mandatory when given an IO object"
148
- end
149
-
150
- spec = @document.add(Type: :Filespec)
151
- spec.path = name
152
- spec[:Desc] = description if description
153
- spec.embed(file_or_io, name: name, register: true) if embed || !file_or_io.kind_of?(String)
154
- spec
155
- end
156
-
157
- # :call-seq:
158
- # files.each_file(search: false) {|file_spec| block } -> files
159
- # files.each_file(search: false) -> Enumerator
160
- #
161
- # Iterates over indirect file specification dictionaries of the PDF.
162
- #
163
- # By default, only the file specifications in their standard locations, namely in the
164
- # EmbeddedFiles name tree and in the page annotations, are returned. If the +search+ option is
165
- # +true+, then all indirect objects are searched for file specification dictionaries which is
166
- # much slower.
167
- def each_file(search: false)
168
- return to_enum(__method__, search: search) unless block_given?
169
-
170
- if search
171
- @document.each(current: false) do |obj|
172
- yield(obj) if obj.type == :Filespec
173
- end
174
- else
175
- seen = {}
176
- tree = @document.catalog[:Names] && @document.catalog[:Names][:EmbeddedFiles]
177
- tree.each_entry do |_, spec|
178
- seen[spec] = true
179
- yield(spec)
180
- end if tree
181
-
182
- @document.pages.each_page do |page|
183
- next unless page[:Annots]
184
- page[:Annots].each do |annot|
185
- annot = @document.deref(annot)
186
- next unless annot[:Subtype] == :FileAttachment
187
- spec = @document.deref(annot[:FS])
188
- yield(spec) unless seen.key?(spec)
189
- seen[spec] = true
190
- end
191
- end
192
- end
193
-
194
- self
195
- end
196
-
197
- end
198
-
199
- include Images
200
- include Files
201
-
202
- # Creates a new DocumentUtils object for the given PDF document.
203
- def initialize(document)
204
- @document = document
205
- end
206
-
207
- end
208
-
209
- end