hexapdf 0.1.0 → 0.2.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 +56 -0
- data/CONTRIBUTERS +1 -1
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/examples/arc.rb +1 -1
- data/examples/graphics.rb +1 -1
- data/examples/hello_world.rb +1 -1
- data/examples/merging.rb +1 -1
- data/examples/show_char_bboxes.rb +1 -1
- data/examples/standard_pdf_fonts.rb +1 -1
- data/examples/truetype.rb +1 -1
- data/lib/hexapdf/cli.rb +14 -7
- data/lib/hexapdf/cli/extract.rb +1 -1
- data/lib/hexapdf/cli/info.rb +2 -2
- data/lib/hexapdf/cli/inspect.rb +4 -4
- data/lib/hexapdf/cli/modify.rb +151 -51
- data/lib/hexapdf/configuration.rb +1 -1
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/dictionary.rb +6 -19
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +23 -16
- data/lib/hexapdf/document/files.rb +130 -0
- data/lib/hexapdf/{font_utils.rb → document/fonts.rb} +40 -38
- data/lib/hexapdf/document/images.rb +117 -0
- data/lib/hexapdf/document/pages.rb +125 -0
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -10
- data/lib/hexapdf/encryption/standard_security_handler.rb +11 -8
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -6
- data/lib/hexapdf/font/cmap/writer.rb +5 -7
- data/lib/hexapdf/font/true_type.rb +4 -1
- data/lib/hexapdf/font/true_type/font.rb +8 -16
- data/lib/hexapdf/font/true_type/table.rb +5 -16
- data/lib/hexapdf/font/true_type/table/cmap.rb +2 -7
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +2 -6
- data/lib/hexapdf/font/true_type/table/directory.rb +0 -5
- data/lib/hexapdf/font/true_type/table/glyf.rb +3 -11
- data/lib/hexapdf/font/true_type/table/head.rb +0 -12
- data/lib/hexapdf/font/true_type/table/hhea.rb +0 -7
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -5
- data/lib/hexapdf/font/true_type/table/loca.rb +0 -4
- data/lib/hexapdf/font/true_type/table/maxp.rb +0 -8
- data/lib/hexapdf/font/true_type/table/name.rb +3 -17
- data/lib/hexapdf/font/true_type/table/os2.rb +0 -14
- data/lib/hexapdf/font/true_type/table/post.rb +0 -8
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
- data/lib/hexapdf/font/type1.rb +2 -2
- data/lib/hexapdf/font/type1/font.rb +2 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +10 -1
- data/lib/hexapdf/font/type1_wrapper.rb +2 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/object.rb +18 -5
- data/lib/hexapdf/rectangle.rb +8 -1
- data/lib/hexapdf/revisions.rb +4 -2
- data/lib/hexapdf/serializer.rb +3 -3
- data/lib/hexapdf/stream.rb +3 -2
- data/lib/hexapdf/task/dereference.rb +4 -5
- data/lib/hexapdf/task/optimize.rb +6 -3
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/form.rb +19 -0
- data/lib/hexapdf/type/page.rb +21 -6
- data/lib/hexapdf/type/page_tree_node.rb +27 -34
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/man/man1/hexapdf.1 +259 -187
- data/test/hexapdf/content/graphic_object/test_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +1 -1
- data/test/hexapdf/document/test_files.rb +71 -0
- data/test/hexapdf/{test_font_utils.rb → document/test_fonts.rb} +1 -2
- data/test/hexapdf/document/test_images.rb +78 -0
- data/test/hexapdf/document/test_pages.rb +114 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -5
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/common.rb +0 -4
- data/test/hexapdf/font/true_type/table/test_cmap.rb +0 -6
- data/test/hexapdf/font/true_type/table/test_directory.rb +0 -5
- data/test/hexapdf/font/true_type/table/test_glyf.rb +5 -8
- data/test/hexapdf/font/true_type/table/test_head.rb +0 -20
- data/test/hexapdf/font/true_type/table/test_hhea.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +2 -7
- data/test/hexapdf/font/true_type/table/test_loca.rb +4 -8
- data/test/hexapdf/font/true_type/table/test_maxp.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_name.rb +0 -19
- data/test/hexapdf/font/true_type/table/test_os2.rb +0 -8
- data/test/hexapdf/font/true_type/table/test_post.rb +0 -13
- data/test/hexapdf/font/true_type/test_font.rb +14 -38
- data/test/hexapdf/font/true_type/test_table.rb +0 -9
- data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
- data/test/hexapdf/task/test_dereference.rb +5 -1
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_dictionary.rb +4 -0
- data/test/hexapdf/test_document.rb +0 -7
- data/test/hexapdf/test_importer.rb +4 -4
- data/test/hexapdf/test_object.rb +31 -9
- data/test/hexapdf/test_rectangle.rb +18 -0
- data/test/hexapdf/test_revisions.rb +7 -0
- data/test/hexapdf/test_serializer.rb +6 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_form.rb +12 -0
- data/test/hexapdf/type/test_page.rb +39 -20
- data/test/hexapdf/type/test_page_tree_node.rb +28 -21
- metadata +21 -9
- data/lib/hexapdf/document_utils.rb +0 -209
- 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
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ startxref
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 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.
|
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 "
|
57
|
-
page
|
58
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 "
|
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
|
171
|
-
assert_equal(@pages[
|
172
|
-
assert_equal(
|
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 "
|
179
|
-
|
180
|
-
|
181
|
-
@root
|
182
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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.
|
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-
|
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:
|
34
|
+
name: kramdown
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0
|
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
|
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/
|
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
|