pdf-core 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,56 @@
1
+ PDF::Core is copyrighted free software produced by Gregory Brown along with
2
+ community contributions. See git log for authorship information.
3
+
4
+ Licensing terms follow:
5
+
6
+ You can redistribute PDF::Core and/or modify it under either the terms of the GPLv2
7
+ or GPLv3 (see GPLv2 and GPLv3 files), or the conditions below:
8
+
9
+ 1. You may make and give away verbatim copies of the source form of the
10
+ software without restriction, provided that you duplicate all of the
11
+ original copyright notices and associated disclaimers.
12
+
13
+ 2. You may modify your copy of the software in any way, provided that
14
+ you do at least ONE of the following:
15
+
16
+ a) place your modifications in the Public Domain or otherwise
17
+ make them Freely Available, such as by posting said
18
+ modifications to Usenet or an equivalent medium, or by allowing
19
+ the author to include your modifications in the software.
20
+
21
+ b) use the modified software only within your corporation or
22
+ organization.
23
+
24
+ c) rename any non-standard executables so the names do not conflict
25
+ with standard executables, which must also be provided.
26
+
27
+ d) make other distribution arrangements with the author.
28
+
29
+ 3. You may distribute the software in object code or executable
30
+ form, provided that you do at least ONE of the following:
31
+
32
+ a) distribute the executables and library files of the software,
33
+ together with instructions (in the manual page or equivalent)
34
+ on where to get the original distribution.
35
+
36
+ b) accompany the distribution with the machine-readable source of
37
+ the software.
38
+
39
+ c) give non-standard executables non-standard names, with
40
+ instructions on where to get the original software distribution.
41
+
42
+ d) make other distribution arrangements with the author.
43
+
44
+ 4. You may modify and include the part of the software into any other
45
+ software (possibly commercial).
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require 'rake'
5
+ require 'rspec/core/rake_task'
6
+ task :default => [:spec]
7
+
8
+ desc "Run all rspec files"
9
+ RSpec::Core::RakeTask.new("spec") do |c|
10
+ c.rspec_opts = "-t ~unresolved"
11
+ end
data/pdf-core.gemspec CHANGED
@@ -3,7 +3,9 @@ Gem::Specification.new do |spec|
3
3
  spec.version = File.read(File.expand_path('VERSION', File.dirname(__FILE__))).strip
4
4
  spec.platform = Gem::Platform::RUBY
5
5
  spec.summary = "PDF::Core is used by Prawn to render PDF documents"
6
- spec.files = Dir.glob("{lib}/**/**/*") +
6
+ spec.files = Dir.glob("{lib,spec}/**/**/*") +
7
+ ["COPYING", "GPLv2", "GPLv3", "LICENSE"] +
8
+ ["Gemfile", "Rakefile"] +
7
9
  ["pdf-core.gemspec"]
8
10
  spec.require_path = "lib"
9
11
  spec.required_ruby_version = '>= 1.9.3'
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative "spec_helper"
4
+
5
+ FILTERS = {
6
+ :FlateDecode => {'test' => "x\x9C+I-.\x01\x00\x04]\x01\xC1".force_encoding(Encoding::ASCII_8BIT) },
7
+ :DCTDecode => {'test' => "test"}
8
+ }
9
+
10
+ FILTERS.each do |filter_name, examples|
11
+ filter = PDF::Core::Filters.const_get(filter_name)
12
+
13
+ describe "#{filter_name} filter" do
14
+ it "should encode stream" do
15
+ examples.each do |in_stream, out_stream|
16
+ filter.encode(in_stream).should == out_stream
17
+ end
18
+ end
19
+
20
+ it "should decode stream" do
21
+ examples.each do |in_stream, out_stream|
22
+ filter.decode(out_stream).should == in_stream
23
+ end
24
+ end
25
+
26
+ it "should be symmetric" do
27
+ examples.each do |in_stream, out_stream|
28
+ filter.decode(filter.encode(in_stream)).should == in_stream
29
+
30
+ filter.encode(filter.decode(out_stream)).should == out_stream
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,122 @@
1
+ require_relative "spec_helper"
2
+
3
+ def tree_dump(tree)
4
+ if tree.is_a?(PDF::Core::NameTree::Node)
5
+ "[" + tree.children.map { |child| tree_dump(child) }.join(",") + "]"
6
+ else
7
+ "#{tree.name}=#{tree.value}"
8
+ end
9
+ end
10
+
11
+ def tree_add(tree, *args)
12
+ args.each do |(name, value)|
13
+ tree.add(name, value)
14
+ end
15
+ end
16
+
17
+ def tree_value(name, value)
18
+ PDF::Core::NameTree::Value.new(name, value)
19
+ end
20
+
21
+ # FIXME: This is a dummy that's meant to stand in for a Prawn::Document.
22
+ # It causes the tests to pass but I have no idea if it's really a
23
+ # sufficient test double or not.
24
+ class RefExposingDocument
25
+ def initialize
26
+ @object_store = []
27
+ end
28
+
29
+ attr_reader :object_store
30
+
31
+ def ref!(obj)
32
+ @object_store << obj
33
+ end
34
+ end
35
+
36
+ describe "Name Tree" do
37
+ before(:each) { @pdf = RefExposingDocument.new }
38
+
39
+ it "should have no children when first initialized" do
40
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
41
+ node.children.length.should == 0
42
+ end
43
+
44
+ it "should have no subtrees while child limit is not reached" do
45
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
46
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3])
47
+ tree_dump(node).should == "[one=1,three=3,two=2]"
48
+ end
49
+
50
+ it "should split into subtrees when limit is exceeded" do
51
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
52
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
53
+ tree_dump(node).should == "[[four=4,one=1],[three=3,two=2]]"
54
+ end
55
+
56
+ it "should create a two new references when root is split" do
57
+ ref_count = @pdf.object_store.length
58
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
59
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
60
+ @pdf.object_store.length.should == ref_count+2
61
+ end
62
+
63
+ it "should create a one new reference when subtree is split" do
64
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
65
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
66
+
67
+ ref_count = @pdf.object_store.length # save when root is split
68
+ tree_add(node, ["five", 5], ["six", 6], ["seven", 7])
69
+ tree_dump(node).should == "[[five=5,four=4,one=1],[seven=7,six=6],[three=3,two=2]]"
70
+ @pdf.object_store.length.should == ref_count+1
71
+ end
72
+
73
+ it "should keep tree balanced when subtree split cascades to root" do
74
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
75
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
76
+ tree_add(node, ["five", 5], ["six", 6], ["seven", 7], ["eight", 8])
77
+ tree_dump(node).should == "[[[eight=8,five=5],[four=4,one=1]],[[seven=7,six=6],[three=3,two=2]]]"
78
+ end
79
+
80
+ it "should maintain order of already properly ordered nodes" do
81
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
82
+ tree_add(node, ["eight", 8], ["five", 5], ["four", 4], ["one", 1])
83
+ tree_add(node, ['seven', 7], ['six', 6], ['three', 3], ['two', 2])
84
+ tree_dump(node).should == "[[[eight=8,five=5],[four=4,one=1]],[[seven=7,six=6],[three=3,two=2]]]"
85
+ end
86
+
87
+ it "should emit only :Names key with to_hash if root is only node" do
88
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
89
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3])
90
+ node.to_hash.should ==(
91
+ { :Names => [tree_value("one", 1), tree_value("three", 3), tree_value("two", 2)] }
92
+ )
93
+ end
94
+
95
+ it "should emit only :Kids key with to_hash if root has children" do
96
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
97
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
98
+ node.to_hash.should ==({ :Kids => node.children.map { |child| child.ref } })
99
+ end
100
+
101
+ it "should emit :Limits and :Names keys with to_hash for leaf node" do
102
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
103
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
104
+ node.children.first.to_hash.should ==(
105
+ { :Limits => %w(four one),
106
+ :Names => [tree_value("four", 4), tree_value("one", 1)] }
107
+ )
108
+ end
109
+
110
+ it "should emit :Limits and :Kids keys with to_hash for inner node" do
111
+ node = PDF::Core::NameTree::Node.new(@pdf, 3)
112
+ tree_add(node, ["one", 1], ["two", 2], ["three", 3], ["four", 4])
113
+ tree_add(node, ["five", 5], ["six", 6], ["seven", 7], ["eight", 8])
114
+ tree_add(node, ["nine", 9], ["ten", 10], ["eleven", 11], ["twelve", 12])
115
+ tree_add(node, ["thirteen", 13], ["fourteen", 14], ["fifteen", 15], ["sixteen", 16])
116
+ node.children.first.to_hash.should ==(
117
+ { :Limits => %w(eight one),
118
+ :Kids => node.children.first.children.map { |child| child.ref } }
119
+ )
120
+ end
121
+ end
122
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative "spec_helper"
4
+
5
+ describe "PDF::Core::ObjectStore" do
6
+ before(:each) do
7
+ @store = PDF::Core::ObjectStore.new
8
+ end
9
+
10
+ it "should create required roots by default, including info passed to new" do
11
+ store = PDF::Core::ObjectStore.new(:info => {:Test => 3})
12
+ store.size.should == 3 # 3 default roots
13
+ store.info.data[:Test].should == 3
14
+ store.pages.data[:Count].should == 0
15
+ store.root.data[:Pages].should == store.pages
16
+ end
17
+
18
+
19
+ it "should add to its objects when ref() is called" do
20
+ count = @store.size
21
+ @store.ref("blah")
22
+ @store.size.should == count + 1
23
+ end
24
+
25
+ it "should accept push with a Prawn::Reference" do
26
+ r = PDF::Core::Reference(123, "blah")
27
+ @store.push(r)
28
+ @store[r.identifier].should == r
29
+ end
30
+
31
+ it "should accept arbitrary data and use it to create a Prawn::Reference" do
32
+ @store.push(123, "blahblah")
33
+ @store[123].data.should == "blahblah"
34
+ end
35
+
36
+ it "should be Enumerable, yielding in order of submission" do
37
+ # higher IDs to bypass the default roots
38
+ [10, 11, 12].each do |id|
39
+ @store.push(id, "some data #{id}")
40
+ end
41
+ @store.map{|ref| ref.identifier}[-3..-1].should == [10, 11, 12]
42
+ end
43
+
44
+ it "should accept option to disabling PDF scaling in PDF clients" do
45
+ @store = PDF::Core::ObjectStore.new(:print_scaling => :none)
46
+ @store.root.data[:ViewerPreferences].should == {:PrintScaling => :None}
47
+ end
48
+
49
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: ASCII-8BIT
2
+ require_relative "spec_helper"
3
+
4
+ # See PDF Reference, Sixth Edition (1.7) pp51-60 for details
5
+ describe "PDF Object Serialization" do
6
+
7
+ it "should convert Ruby's nil to PDF null" do
8
+ PDF::Core::PdfObject(nil).should == "null"
9
+ end
10
+
11
+ it "should convert Ruby booleans to PDF booleans" do
12
+ PDF::Core::PdfObject(true).should == "true"
13
+ PDF::Core::PdfObject(false).should == "false"
14
+ end
15
+
16
+ it "should convert a Ruby number to PDF number" do
17
+ PDF::Core::PdfObject(1).should == "1"
18
+ PDF::Core::PdfObject(1.214112421).should == "1.214112421"
19
+ # scientific notation is not valid in PDF
20
+ PDF::Core::PdfObject(0.000005).should == "0.000005"
21
+ end
22
+
23
+ it "should convert a Ruby time object to a PDF timestamp" do
24
+ t = Time.now
25
+ PDF::Core::PdfObject(t).should == t.strftime("(D:%Y%m%d%H%M%S%z").chop.chop + "'00')"
26
+ end
27
+
28
+ it "should convert a Ruby string to PDF string when inside a content stream" do
29
+ s = "I can has a string"
30
+ PDF::Inspector.parse(PDF::Core::PdfObject(s, true)).should == s
31
+ end
32
+
33
+ it "should convert a Ruby string to a UTF-16 PDF string when outside a content stream" do
34
+ s = "I can has a string"
35
+ s_utf16 = "\xFE\xFF" + s.unpack("U*").pack("n*")
36
+ PDF::Inspector.parse(PDF::Core::PdfObject(s, false)).should == s_utf16
37
+ end
38
+
39
+ it "should convert a Ruby string with characters outside the BMP to its " +
40
+ "UTF-16 representation with a BOM" do
41
+ # U+10192 ROMAN SEMUNCIA SIGN
42
+ semuncia = [65938].pack("U")
43
+ PDF::Core::PdfObject(semuncia, false).upcase.should == "<FEFFD800DD92>"
44
+ end
45
+
46
+ it "should pass through bytes regardless of content stream status for ByteString" do
47
+ PDF::Core::PdfObject(PDF::Core::ByteString.new("\xDE\xAD\xBE\xEF")).upcase.
48
+ should == "<DEADBEEF>"
49
+ end
50
+
51
+ it "should escape parens when converting from Ruby string to PDF" do
52
+ s = 'I )(can has a string'
53
+ PDF::Inspector.parse(PDF::Core::PdfObject(s, true)).should == s
54
+ end
55
+
56
+ it "should handle ruby escaped parens when converting to PDF string" do
57
+ s = 'I can \\)( has string'
58
+ PDF::Inspector.parse(PDF::Core::PdfObject(s, true)).should == s
59
+ end
60
+
61
+ it "should escape various strings correctly when converting a LiteralString" do
62
+ ls = PDF::Core::LiteralString.new("abc")
63
+ PDF::Core::PdfObject(ls).should == "(abc)"
64
+
65
+ ls = PDF::Core::LiteralString.new("abc\x0Ade") # should escape \n
66
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x0Ade)"
67
+
68
+ ls = PDF::Core::LiteralString.new("abc\x0Dde") # should escape \r
69
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x0Dde)"
70
+
71
+ ls = PDF::Core::LiteralString.new("abc\x09de") # should escape \t
72
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x09de)"
73
+
74
+ ls = PDF::Core::LiteralString.new("abc\x08de") # should escape \b
75
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x08de)"
76
+
77
+ ls = PDF::Core::LiteralString.new("abc\x0Cde") # should escape \f
78
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x0Cde)"
79
+
80
+ ls = PDF::Core::LiteralString.new("abc(de") # should escape \(
81
+ PDF::Core::PdfObject(ls).should == "(abc\x5C(de)"
82
+
83
+ ls = PDF::Core::LiteralString.new("abc)de") # should escape \)
84
+ PDF::Core::PdfObject(ls).should == "(abc\x5C)de)"
85
+
86
+ ls = PDF::Core::LiteralString.new("abc\x5Cde") # should escape \\
87
+ PDF::Core::PdfObject(ls).should == "(abc\x5C\x5Cde)"
88
+ PDF::Core::PdfObject(ls).size.should == 9
89
+ end
90
+
91
+ it "should escape strings correctly when converting a LiteralString that is not utf-8" do
92
+ data = "\x43\xaf\xc9\x7f\xef\xf\xe6\xa8\xcb\x5c\xaf\xd0"
93
+ ls = PDF::Core::LiteralString.new(data)
94
+ PDF::Core::PdfObject(ls).should == "(\x43\xaf\xc9\x7f\xef\xf\xe6\xa8\xcb\x5c\x5c\xaf\xd0)"
95
+ end
96
+
97
+ it "should convert a Ruby symbol to PDF name" do
98
+ PDF::Core::PdfObject(:my_symbol).should == "/my_symbol"
99
+ PDF::Core::PdfObject(:"A;Name_With-Various***Characters?").should ==
100
+ "/A;Name_With-Various***Characters?"
101
+ end
102
+
103
+ it "should convert a whitespace or delimiter containing Ruby symbol to a PDF name" do
104
+ PDF::Core::PdfObject(:"my symbol").should == "/my#20symbol"
105
+ PDF::Core::PdfObject(:"my#symbol").should == "/my#23symbol"
106
+ PDF::Core::PdfObject(:"my/symbol").should == "/my#2Fsymbol"
107
+ PDF::Core::PdfObject(:"my(symbol").should == "/my#28symbol"
108
+ PDF::Core::PdfObject(:"my)symbol").should == "/my#29symbol"
109
+ PDF::Core::PdfObject(:"my<symbol").should == "/my#3Csymbol"
110
+ PDF::Core::PdfObject(:"my>symbol").should == "/my#3Esymbol"
111
+ end
112
+
113
+ it "should convert a Ruby array to PDF Array when inside a content stream" do
114
+ PDF::Core::PdfObject([1,2,3]).should == "[1 2 3]"
115
+ PDF::Inspector.parse(PDF::Core::PdfObject([[1,2],:foo,"Bar"], true)).should ==
116
+ [[1,2],:foo, "Bar"]
117
+ end
118
+
119
+ it "should convert a Ruby array to PDF Array when outside a content stream" do
120
+ bar = "\xFE\xFF" + "Bar".unpack("U*").pack("n*")
121
+ PDF::Core::PdfObject([1,2,3]).should == "[1 2 3]"
122
+ PDF::Inspector.parse(PDF::Core::PdfObject([[1,2],:foo,"Bar"], false)).should ==
123
+ [[1,2],:foo, bar]
124
+ end
125
+
126
+ it "should convert a Ruby hash to a PDF Dictionary when inside a content stream" do
127
+ dict = PDF::Core::PdfObject( {:foo => :bar,
128
+ "baz" => [1,2,3],
129
+ :bang => {:a => "what", :b => [:you, :say] }}, true )
130
+
131
+ res = PDF::Inspector.parse(dict)
132
+
133
+ res[:foo].should == :bar
134
+ res[:baz].should == [1,2,3]
135
+ res[:bang].should == { :a => "what", :b => [:you, :say] }
136
+
137
+ end
138
+
139
+ it "should convert a Ruby hash to a PDF Dictionary when outside a content stream" do
140
+ what = "\xFE\xFF" + "what".unpack("U*").pack("n*")
141
+ dict = PDF::Core::PdfObject( {:foo => :bar,
142
+ "baz" => [1,2,3],
143
+ :bang => {:a => "what", :b => [:you, :say] }}, false )
144
+
145
+ res = PDF::Inspector.parse(dict)
146
+
147
+ res[:foo].should == :bar
148
+ res[:baz].should == [1,2,3]
149
+ res[:bang].should == { :a => what, :b => [:you, :say] }
150
+
151
+ end
152
+
153
+ it "should not allow keys other than strings or symbols for PDF dicts" do
154
+ lambda { PDF::Core::PdfObject(:foo => :bar, :baz => :bang, 1 => 4) }.
155
+ should raise_error(PDF::Core::Errors::FailedObjectConversion)
156
+ end
157
+
158
+ it "should convert a Prawn::Reference to a PDF indirect object reference" do
159
+ ref = PDF::Core::Reference(1,true)
160
+ PDF::Core::PdfObject(ref).should == ref.to_s
161
+ end
162
+
163
+ it "should convert a NameTree::Node to a PDF hash" do
164
+ # FIXME: Soft dependench on Prawn::Document exists in Node
165
+ node = PDF::Core::NameTree::Node.new(nil, 10)
166
+ node.add "hello", 1.0
167
+ node.add "world", 2.0
168
+ data = PDF::Core::PdfObject(node)
169
+ res = PDF::Inspector.parse(data)
170
+ res.should == {:Names => ["hello", 1.0, "world", 2.0]}
171
+ end
172
+ end