pdf-core 0.6.1 → 0.7.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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/Rakefile +9 -5
- data/lib/pdf/core.rb +20 -19
- data/lib/pdf/core/annotations.rb +16 -17
- data/lib/pdf/core/byte_string.rb +1 -1
- data/lib/pdf/core/destinations.rb +17 -14
- data/lib/pdf/core/document_state.rb +21 -16
- data/lib/pdf/core/filter_list.rb +4 -4
- data/lib/pdf/core/filters.rb +4 -4
- data/lib/pdf/core/graphics_state.rb +15 -19
- data/lib/pdf/core/name_tree.rb +44 -40
- data/lib/pdf/core/object_store.rb +13 -18
- data/lib/pdf/core/outline_item.rb +11 -6
- data/lib/pdf/core/outline_root.rb +1 -1
- data/lib/pdf/core/page.rb +93 -64
- data/lib/pdf/core/page_geometry.rb +52 -56
- data/lib/pdf/core/pdf_object.rb +41 -41
- data/lib/pdf/core/reference.rb +12 -17
- data/lib/pdf/core/renderer.rb +41 -32
- data/lib/pdf/core/stream.rb +6 -8
- data/lib/pdf/core/text.rb +83 -47
- data/lib/pdf/core/utils.rb +12 -0
- data/pdf-core.gemspec +33 -20
- metadata +79 -24
- metadata.gz.sig +0 -0
- data/spec/decimal_rounding_spec.rb +0 -12
- data/spec/document_state_spec.rb +0 -30
- data/spec/filters_spec.rb +0 -34
- data/spec/name_tree_spec.rb +0 -122
- data/spec/object_store_spec.rb +0 -49
- data/spec/pdf_object_spec.rb +0 -172
- data/spec/reference_spec.rb +0 -62
- data/spec/spec_helper.rb +0 -32
- data/spec/stream_spec.rb +0 -59
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
# Describes PDF page geometries
|
4
2
|
#
|
5
3
|
# Copyright April 2008, Gregory Brown. All Rights Reserved.
|
@@ -8,7 +6,6 @@
|
|
8
6
|
|
9
7
|
module PDF
|
10
8
|
module Core
|
11
|
-
|
12
9
|
# Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
|
13
10
|
#
|
14
11
|
# All of these dimensions are in PDF Points (1/72 inch)
|
@@ -68,59 +65,58 @@ module PDF
|
|
68
65
|
# TABLOID:: => 792.00 x 1224.00
|
69
66
|
#
|
70
67
|
module PageGeometry
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
68
|
+
SIZES = {
|
69
|
+
'4A0' => [4767.87, 6740.79],
|
70
|
+
'2A0' => [3370.39, 4767.87],
|
71
|
+
'A0' => [2383.94, 3370.39],
|
72
|
+
'A1' => [1683.78, 2383.94],
|
73
|
+
'A2' => [1190.55, 1683.78],
|
74
|
+
'A3' => [841.89, 1190.55],
|
75
|
+
'A4' => [595.28, 841.89],
|
76
|
+
'A5' => [419.53, 595.28],
|
77
|
+
'A6' => [297.64, 419.53],
|
78
|
+
'A7' => [209.76, 297.64],
|
79
|
+
'A8' => [147.40, 209.76],
|
80
|
+
'A9' => [104.88, 147.40],
|
81
|
+
'A10' => [73.70, 104.88],
|
82
|
+
'B0' => [2834.65, 4008.19],
|
83
|
+
'B1' => [2004.09, 2834.65],
|
84
|
+
'B2' => [1417.32, 2004.09],
|
85
|
+
'B3' => [1000.63, 1417.32],
|
86
|
+
'B4' => [708.66, 1000.63],
|
87
|
+
'B5' => [498.90, 708.66],
|
88
|
+
'B6' => [354.33, 498.90],
|
89
|
+
'B7' => [249.45, 354.33],
|
90
|
+
'B8' => [175.75, 249.45],
|
91
|
+
'B9' => [124.72, 175.75],
|
92
|
+
'B10' => [87.87, 124.72],
|
93
|
+
'C0' => [2599.37, 3676.54],
|
94
|
+
'C1' => [1836.85, 2599.37],
|
95
|
+
'C2' => [1298.27, 1836.85],
|
96
|
+
'C3' => [918.43, 1298.27],
|
97
|
+
'C4' => [649.13, 918.43],
|
98
|
+
'C5' => [459.21, 649.13],
|
99
|
+
'C6' => [323.15, 459.21],
|
100
|
+
'C7' => [229.61, 323.15],
|
101
|
+
'C8' => [161.57, 229.61],
|
102
|
+
'C9' => [113.39, 161.57],
|
103
|
+
'C10' => [79.37, 113.39],
|
104
|
+
'RA0' => [2437.80, 3458.27],
|
105
|
+
'RA1' => [1729.13, 2437.80],
|
106
|
+
'RA2' => [1218.90, 1729.13],
|
107
|
+
'RA3' => [864.57, 1218.90],
|
108
|
+
'RA4' => [609.45, 864.57],
|
109
|
+
'SRA0' => [2551.18, 3628.35],
|
110
|
+
'SRA1' => [1814.17, 2551.18],
|
111
|
+
'SRA2' => [1275.59, 1814.17],
|
112
|
+
'SRA3' => [907.09, 1275.59],
|
113
|
+
'SRA4' => [637.80, 907.09],
|
114
|
+
'EXECUTIVE' => [521.86, 756.00],
|
115
|
+
'FOLIO' => [612.00, 936.00],
|
116
|
+
'LEGAL' => [612.00, 1008.00],
|
117
|
+
'LETTER' => [612.00, 792.00],
|
118
|
+
'TABLOID' => [792.00, 1224.00]
|
119
|
+
}.freeze
|
123
120
|
end
|
124
121
|
end
|
125
122
|
end
|
126
|
-
|
data/lib/pdf/core/pdf_object.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
1
|
# pdf_object.rb : Handles Ruby to PDF object serialization
|
4
2
|
#
|
5
3
|
# Copyright April 2008, Gregory Brown. All Rights Reserved.
|
@@ -17,85 +15,87 @@ module PDF
|
|
17
15
|
end
|
18
16
|
|
19
17
|
def real_params(array)
|
20
|
-
array.map { |e| real(e) }.join(
|
18
|
+
array.map { |e| real(e) }.join(' ')
|
21
19
|
end
|
22
20
|
|
23
21
|
def utf8_to_utf16(str)
|
24
|
-
"\xFE\xFF".force_encoding(::Encoding::UTF_16BE) +
|
22
|
+
"\xFE\xFF".force_encoding(::Encoding::UTF_16BE) +
|
23
|
+
str.encode(::Encoding::UTF_16BE)
|
25
24
|
end
|
26
25
|
|
27
26
|
# encodes any string into a hex representation. The result is a string
|
28
27
|
# with only 0-9 and a-f characters. That result is valid ASCII so tag
|
29
28
|
# it as such to account for behaviour of different ruby VMs
|
30
29
|
def string_to_hex(str)
|
31
|
-
str.unpack(
|
30
|
+
str.unpack('H*').first.force_encoding(::Encoding::US_ASCII)
|
32
31
|
end
|
33
32
|
|
34
33
|
# Serializes Ruby objects to their PDF equivalents. Most primitive objects
|
35
34
|
# will work as expected, but please note that Name objects are represented
|
36
|
-
# by Ruby Symbol objects and Dictionary objects are represented by Ruby
|
37
|
-
# (keyed by symbols)
|
35
|
+
# by Ruby Symbol objects and Dictionary objects are represented by Ruby
|
36
|
+
# hashes (keyed by symbols)
|
38
37
|
#
|
39
38
|
# Examples:
|
40
39
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
40
|
+
# pdf_object(true) #=> "true"
|
41
|
+
# pdf_object(false) #=> "false"
|
42
|
+
# pdf_object(1.2124) #=> "1.2124"
|
43
|
+
# pdf_object('foo bar') #=> "(foo bar)"
|
44
|
+
# pdf_object(:Symbol) #=> "/Symbol"
|
45
|
+
# pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
|
47
46
|
#
|
48
|
-
def
|
49
|
-
case
|
50
|
-
when NilClass then
|
51
|
-
when TrueClass then
|
52
|
-
when FalseClass then
|
47
|
+
def pdf_object(obj, in_content_stream = false)
|
48
|
+
case obj
|
49
|
+
when NilClass then 'null'
|
50
|
+
when TrueClass then 'true'
|
51
|
+
when FalseClass then 'false'
|
53
52
|
when Numeric
|
54
|
-
obj = real(obj) unless obj.
|
53
|
+
obj = real(obj) unless obj.is_a?(Integer)
|
55
54
|
|
56
|
-
|
57
|
-
|
55
|
+
# NOTE: this can fail on huge floating point numbers, but it seems
|
56
|
+
# unlikely to ever happen in practice.
|
57
|
+
String(obj)
|
58
58
|
when Array
|
59
|
-
|
59
|
+
'[' << obj.map { |e| pdf_object(e, in_content_stream) }.join(' ') << ']'
|
60
60
|
when PDF::Core::LiteralString
|
61
61
|
obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/) { |m| "\\#{m}" }
|
62
62
|
"(#{obj})"
|
63
63
|
when Time
|
64
|
-
obj = obj.strftime(
|
64
|
+
obj = obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop + "'00'"
|
65
65
|
obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/) { |m| "\\#{m}" }
|
66
66
|
"(#{obj})"
|
67
67
|
when PDF::Core::ByteString
|
68
|
-
|
68
|
+
'<' << obj.unpack('H*').first << '>'
|
69
69
|
when String
|
70
70
|
obj = utf8_to_utf16(obj) unless in_content_stream
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
if n < 33 || n > 126 || [35,40,41,47,60,62].include?(n)
|
75
|
-
|
71
|
+
'<' << string_to_hex(obj) << '>'
|
72
|
+
when Symbol
|
73
|
+
'/' + obj.to_s.unpack('C*').map do |n|
|
74
|
+
if n < 33 || n > 126 || [35, 40, 41, 47, 60, 62].include?(n)
|
75
|
+
'#' + n.to_s(16).upcase
|
76
76
|
else
|
77
|
-
[n].pack(
|
77
|
+
[n].pack('C*')
|
78
78
|
end
|
79
|
-
|
79
|
+
end.join
|
80
80
|
when ::Hash
|
81
|
-
output =
|
82
|
-
obj.each do |k,v|
|
83
|
-
unless String
|
81
|
+
output = '<< '
|
82
|
+
obj.each do |k, v|
|
83
|
+
unless k.is_a?(String) || k.is_a?(Symbol)
|
84
84
|
raise PDF::Core::Errors::FailedObjectConversion,
|
85
|
-
|
85
|
+
'A PDF Dictionary must be keyed by names'
|
86
86
|
end
|
87
|
-
output <<
|
88
|
-
|
87
|
+
output << pdf_object(k.to_sym, in_content_stream) << ' ' <<
|
88
|
+
pdf_object(v, in_content_stream) << "\n"
|
89
89
|
end
|
90
|
-
output <<
|
90
|
+
output << '>>'
|
91
91
|
when PDF::Core::Reference
|
92
92
|
obj.to_s
|
93
93
|
when PDF::Core::NameTree::Node
|
94
|
-
|
94
|
+
pdf_object(obj.to_hash)
|
95
95
|
when PDF::Core::NameTree::Value
|
96
|
-
|
96
|
+
pdf_object(obj.name) + ' ' + pdf_object(obj.value)
|
97
97
|
when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
|
98
|
-
|
98
|
+
pdf_object(obj.to_hash)
|
99
99
|
else
|
100
100
|
raise PDF::Core::Errors::FailedObjectConversion,
|
101
101
|
"This object cannot be serialized to PDF (#{obj.inspect})"
|
data/lib/pdf/core/reference.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
# reference.rb : Implementation of PDF indirect objects
|
4
2
|
#
|
5
3
|
# Copyright April 2008, Gregory Brown. All Rights Reserved.
|
6
4
|
#
|
7
5
|
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
6
|
|
7
|
+
require 'pdf/core/utils'
|
9
8
|
|
10
9
|
module PDF
|
11
10
|
module Core
|
12
11
|
class Reference #:nodoc:
|
13
|
-
|
14
12
|
attr_accessor :gen, :data, :offset, :stream, :identifier
|
15
13
|
|
16
14
|
def initialize(id, data)
|
@@ -22,17 +20,20 @@ module PDF
|
|
22
20
|
|
23
21
|
def object
|
24
22
|
output = "#{@identifier} #{gen} obj\n"
|
25
|
-
|
26
|
-
output << PDF::Core
|
23
|
+
if @stream.empty?
|
24
|
+
output << PDF::Core.pdf_object(data) << "\n"
|
27
25
|
else
|
28
|
-
output << PDF::Core
|
26
|
+
output << PDF::Core.pdf_object(data.merge(@stream.data)) <<
|
27
|
+
"\n" << @stream.object
|
29
28
|
end
|
30
29
|
|
31
30
|
output << "endobj\n"
|
32
31
|
end
|
33
32
|
|
34
33
|
def <<(io)
|
35
|
-
|
34
|
+
unless @data.is_a?(::Hash)
|
35
|
+
raise 'Cannot attach stream to non-dictionary object'
|
36
|
+
end
|
36
37
|
(@stream ||= Stream.new) << io
|
37
38
|
end
|
38
39
|
|
@@ -43,22 +44,22 @@ module PDF
|
|
43
44
|
# Creates a deep copy of this ref. If +share+ is provided, shares the
|
44
45
|
# given dictionary entries between the old ref and the new.
|
45
46
|
#
|
46
|
-
def deep_copy(share=[])
|
47
|
+
def deep_copy(share = [])
|
47
48
|
r = dup
|
48
49
|
|
49
50
|
case r.data
|
50
51
|
when ::Hash
|
51
52
|
# Copy each entry not in +share+.
|
52
53
|
(r.data.keys - share).each do |k|
|
53
|
-
r.data[k] =
|
54
|
+
r.data[k] = Utils.deep_clone(r.data[k])
|
54
55
|
end
|
55
56
|
when PDF::Core::NameTree::Node
|
56
57
|
r.data = r.data.deep_copy
|
57
58
|
else
|
58
|
-
r.data =
|
59
|
+
r.data = Utils.deep_clone(r.data)
|
59
60
|
end
|
60
61
|
|
61
|
-
r.stream =
|
62
|
+
r.stream = Utils.deep_clone(r.stream)
|
62
63
|
r
|
63
64
|
end
|
64
65
|
|
@@ -68,11 +69,5 @@ module PDF
|
|
68
69
|
@stream = other_ref.stream
|
69
70
|
end
|
70
71
|
end
|
71
|
-
|
72
|
-
module_function
|
73
|
-
|
74
|
-
def Reference(*args, &block) #:nodoc:
|
75
|
-
Reference.new(*args, &block)
|
76
|
-
end
|
77
72
|
end
|
78
73
|
end
|
data/lib/pdf/core/renderer.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'stringio'
|
2
2
|
|
3
3
|
module PDF
|
4
4
|
module Core
|
@@ -6,7 +6,7 @@ module PDF
|
|
6
6
|
def initialize(state)
|
7
7
|
@state = state
|
8
8
|
@state.populate_pages_from_store(self)
|
9
|
-
|
9
|
+
|
10
10
|
min_version(state.store.min_version) if state.store.min_version
|
11
11
|
|
12
12
|
@page_number = 0
|
@@ -14,8 +14,8 @@ module PDF
|
|
14
14
|
|
15
15
|
attr_reader :state
|
16
16
|
|
17
|
-
# Creates a new Reference and adds it to the Document's object
|
18
|
-
#
|
17
|
+
# Creates a new Reference and adds it to the Document's object list. The
|
18
|
+
# +data+ argument is anything that Prawn.pdf_object() can convert.
|
19
19
|
#
|
20
20
|
# Returns the identifier which points to the reference in the ObjectStore
|
21
21
|
#
|
@@ -50,7 +50,7 @@ module PDF
|
|
50
50
|
#
|
51
51
|
# pdf.add_content("#{PDF::Core.real_params([x1, y1])} m") # move
|
52
52
|
# pdf.add_content("#{PDF::Core.real_params([ x2, y2 ])} l") # draw path
|
53
|
-
# pdf.add_content(
|
53
|
+
# pdf.add_content('S') # stroke
|
54
54
|
#
|
55
55
|
def add_content(str)
|
56
56
|
save_graphics_state if graphic_state.nil?
|
@@ -62,7 +62,7 @@ module PDF
|
|
62
62
|
# dictionary do not incur the additional overhead.
|
63
63
|
#
|
64
64
|
def names
|
65
|
-
state.store.root.data[:Names] ||= ref!(:
|
65
|
+
state.store.root.data[:Names] ||= ref!(Type: :Names)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Returns true if the Names dictionary is in use for this document.
|
@@ -80,29 +80,36 @@ module PDF
|
|
80
80
|
# Defines a block to be called just before a new page is started.
|
81
81
|
#
|
82
82
|
def on_page_create(&block)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
83
|
+
state.on_page_create_callback =
|
84
|
+
if block_given?
|
85
|
+
block
|
86
|
+
end
|
88
87
|
end
|
89
88
|
|
90
89
|
def start_new_page(options = {})
|
91
|
-
|
90
|
+
last_page = state.page
|
91
|
+
if last_page
|
92
92
|
last_page_size = last_page.size
|
93
93
|
last_page_layout = last_page.layout
|
94
94
|
last_page_margins = last_page.margins
|
95
95
|
end
|
96
96
|
|
97
|
-
page_options = {
|
98
|
-
|
99
|
-
|
97
|
+
page_options = {
|
98
|
+
size: options[:size] || last_page_size,
|
99
|
+
layout: options[:layout] || last_page_layout,
|
100
|
+
margins: last_page_margins
|
101
|
+
}
|
100
102
|
if last_page
|
101
|
-
|
103
|
+
if last_page.graphic_state
|
104
|
+
new_graphic_state = last_page.graphic_state.dup
|
105
|
+
end
|
102
106
|
|
103
|
-
#
|
104
|
-
|
105
|
-
|
107
|
+
# Erase the color space so that it gets reset on new page for fussy
|
108
|
+
# pdf-readers
|
109
|
+
if new_graphic_state
|
110
|
+
new_graphic_state.color_space = {}
|
111
|
+
end
|
112
|
+
page_options[:graphic_state] = new_graphic_state
|
106
113
|
end
|
107
114
|
|
108
115
|
state.page = PDF::Core::Page.new(self, page_options)
|
@@ -121,10 +128,10 @@ module PDF
|
|
121
128
|
# draw on it.
|
122
129
|
#
|
123
130
|
# See Prawn::Document#number_pages for a sample usage of this capability.
|
124
|
-
|
131
|
+
|
125
132
|
def go_to_page(k)
|
126
133
|
@page_number = k
|
127
|
-
state.page = state.pages[k-1]
|
134
|
+
state.page = state.pages[k - 1]
|
128
135
|
end
|
129
136
|
|
130
137
|
def finalize_all_page_contents
|
@@ -169,10 +176,10 @@ module PDF
|
|
169
176
|
|
170
177
|
# Renders the PDF document to file.
|
171
178
|
#
|
172
|
-
# pdf.render_file
|
179
|
+
# pdf.render_file 'foo.pdf'
|
173
180
|
#
|
174
181
|
def render_file(filename)
|
175
|
-
File.open(filename,
|
182
|
+
File.open(filename, 'wb') { |f| render(f) }
|
176
183
|
end
|
177
184
|
|
178
185
|
# Write out the PDF Header, as per spec 3.4.1
|
@@ -201,7 +208,7 @@ module PDF
|
|
201
208
|
output << "0 #{state.store.size + 1}\n"
|
202
209
|
output << "0000000000 65535 f \n"
|
203
210
|
state.store.each do |ref|
|
204
|
-
output.printf(
|
211
|
+
output.printf('%010d', ref.offset)
|
205
212
|
output << " 00000 n \n"
|
206
213
|
end
|
207
214
|
end
|
@@ -209,24 +216,26 @@ module PDF
|
|
209
216
|
# Write out the PDF Trailer, as per spec 3.4.4
|
210
217
|
#
|
211
218
|
def render_trailer(output)
|
212
|
-
trailer_hash = {
|
213
|
-
|
214
|
-
|
219
|
+
trailer_hash = {
|
220
|
+
Size: state.store.size + 1,
|
221
|
+
Root: state.store.root,
|
222
|
+
Info: state.store.info
|
223
|
+
}
|
215
224
|
trailer_hash.merge!(state.trailer) if state.trailer
|
216
225
|
|
217
226
|
output << "trailer\n"
|
218
|
-
output << PDF::Core
|
227
|
+
output << PDF::Core.pdf_object(trailer_hash) << "\n"
|
219
228
|
output << "startxref\n"
|
220
229
|
output << @xref_offset << "\n"
|
221
|
-
output <<
|
230
|
+
output << '%%EOF' << "\n"
|
222
231
|
end
|
223
232
|
|
224
233
|
def open_graphics_state
|
225
|
-
add_content
|
234
|
+
add_content 'q'
|
226
235
|
end
|
227
236
|
|
228
237
|
def close_graphics_state
|
229
|
-
add_content
|
238
|
+
add_content 'Q'
|
230
239
|
end
|
231
240
|
|
232
241
|
def save_graphics_state(graphic_state = nil)
|
@@ -242,7 +251,7 @@ module PDF
|
|
242
251
|
# false otherwise
|
243
252
|
#
|
244
253
|
def compression_enabled?
|
245
|
-
|
254
|
+
state.compress
|
246
255
|
end
|
247
256
|
|
248
257
|
# Pops the last saved graphics state off the graphics state stack and
|