pdf-core 0.5.1 → 0.9.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Gemfile +3 -1
- data/Rakefile +24 -6
- data/lib/pdf/core.rb +22 -19
- data/lib/pdf/core/annotations.rb +17 -18
- data/lib/pdf/core/byte_string.rb +3 -2
- data/lib/pdf/core/destinations.rb +18 -15
- data/lib/pdf/core/document_state.rb +30 -22
- data/lib/pdf/core/filter_list.rb +18 -6
- data/lib/pdf/core/filters.rb +5 -5
- data/lib/pdf/core/graphics_state.rb +17 -20
- data/lib/pdf/core/literal_string.rb +2 -1
- data/lib/pdf/core/name_tree.rb +46 -43
- data/lib/pdf/core/object_store.rb +17 -22
- data/lib/pdf/core/outline_item.rb +11 -5
- data/lib/pdf/core/outline_root.rb +3 -1
- data/lib/pdf/core/page.rb +96 -66
- data/lib/pdf/core/page_geometry.rb +53 -55
- data/lib/pdf/core/pdf_object.rb +55 -46
- data/lib/pdf/core/reference.rb +21 -17
- data/lib/pdf/core/renderer.rb +46 -37
- data/lib/pdf/core/stream.rb +14 -8
- data/lib/pdf/core/text.rb +100 -47
- data/lib/pdf/core/utils.rb +13 -0
- data/pdf-core.gemspec +37 -22
- metadata +83 -29
- metadata.gz.sig +2 -0
- data/spec/decimal_rounding_spec.rb +0 -12
- 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,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# Implements graphics state saving and restoring
|
4
5
|
#
|
@@ -7,7 +8,6 @@
|
|
7
8
|
# This is free software. Please see the LICENSE and COPYING files for details
|
8
9
|
#
|
9
10
|
|
10
|
-
|
11
11
|
module PDF
|
12
12
|
module Core
|
13
13
|
class GraphicStateStack
|
@@ -34,28 +34,26 @@ module PDF
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def present?
|
37
|
-
stack.
|
37
|
+
!stack.empty?
|
38
38
|
end
|
39
39
|
|
40
40
|
def empty?
|
41
41
|
stack.empty?
|
42
42
|
end
|
43
|
-
|
44
43
|
end
|
45
44
|
|
46
45
|
# NOTE: This class may be a good candidate for a copy-on-write hash.
|
47
46
|
class GraphicState
|
48
|
-
attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width,
|
49
|
-
:fill_color, :stroke_color
|
47
|
+
attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width, :fill_color, :stroke_color
|
50
48
|
|
51
49
|
def initialize(previous_state = nil)
|
52
50
|
if previous_state
|
53
51
|
initialize_copy(previous_state)
|
54
52
|
else
|
55
53
|
@color_space = {}
|
56
|
-
@fill_color =
|
57
|
-
@stroke_color =
|
58
|
-
@dash = { :
|
54
|
+
@fill_color = '000000'
|
55
|
+
@stroke_color = '000000'
|
56
|
+
@dash = { dash: nil, space: nil, phase: 0 }
|
59
57
|
@cap_style = :butt
|
60
58
|
@join_style = :miter
|
61
59
|
@line_width = 1
|
@@ -63,17 +61,17 @@ module PDF
|
|
63
61
|
end
|
64
62
|
|
65
63
|
def dash_setting
|
66
|
-
return
|
64
|
+
return '[] 0 d' unless @dash[:dash]
|
67
65
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
"[#{PDF::Core.real_params(array)}] "
|
76
|
-
|
66
|
+
array =
|
67
|
+
if @dash[:dash].is_a?(Array)
|
68
|
+
@dash[:dash]
|
69
|
+
else
|
70
|
+
[@dash[:dash], @dash[:space]]
|
71
|
+
end
|
72
|
+
|
73
|
+
"[#{PDF::Core.real_params(array)}] "\
|
74
|
+
"#{PDF::Core.real(@dash[:phase])} d"
|
77
75
|
end
|
78
76
|
|
79
77
|
private
|
@@ -93,4 +91,3 @@ module PDF
|
|
93
91
|
end
|
94
92
|
end
|
95
93
|
end
|
96
|
-
|
data/lib/pdf/core/name_tree.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pdf/core/utils'
|
2
4
|
|
3
5
|
# name_tree.rb : Implements NameTree for PDF
|
4
6
|
#
|
@@ -16,7 +18,7 @@ module PDF
|
|
16
18
|
attr_accessor :parent
|
17
19
|
attr_accessor :ref
|
18
20
|
|
19
|
-
def initialize(document, limit, parent=nil)
|
21
|
+
def initialize(document, limit, parent = nil)
|
20
22
|
@document = document
|
21
23
|
@children = []
|
22
24
|
@limit = limit
|
@@ -29,7 +31,7 @@ module PDF
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def size
|
32
|
-
leaf? ? children.size : children.
|
34
|
+
leaf? ? children.size : children.sum(&:size)
|
33
35
|
end
|
34
36
|
|
35
37
|
def leaf?
|
@@ -47,10 +49,10 @@ module PDF
|
|
47
49
|
if leaf?
|
48
50
|
hash[:Names] = children if leaf?
|
49
51
|
else
|
50
|
-
hash[:Kids] = children.map
|
52
|
+
hash[:Kids] = children.map(&:ref)
|
51
53
|
end
|
52
54
|
|
53
|
-
|
55
|
+
hash
|
54
56
|
end
|
55
57
|
|
56
58
|
def least
|
@@ -76,23 +78,24 @@ module PDF
|
|
76
78
|
children.insert(insertion_point(value), value)
|
77
79
|
split! if children.length > limit
|
78
80
|
else
|
79
|
-
fit = children.
|
80
|
-
fit
|
81
|
+
fit = children.find { |child| child >= value }
|
82
|
+
fit ||= children.last
|
81
83
|
fit << value
|
82
84
|
end
|
83
85
|
|
84
86
|
value
|
85
87
|
end
|
86
88
|
|
87
|
-
def >=(
|
88
|
-
children.empty? || children.last >=
|
89
|
+
def >=(other)
|
90
|
+
children.empty? || children.last >= other
|
89
91
|
end
|
90
92
|
|
91
93
|
def split!
|
92
94
|
if parent
|
93
95
|
parent.split(self)
|
94
96
|
else
|
95
|
-
left
|
97
|
+
left = new_node(self)
|
98
|
+
right = new_node(self)
|
96
99
|
split_children(self, left, right)
|
97
100
|
children.replace([left, right])
|
98
101
|
end
|
@@ -103,51 +106,50 @@ module PDF
|
|
103
106
|
#
|
104
107
|
def deep_copy
|
105
108
|
node = dup
|
106
|
-
node.instance_variable_set(
|
107
|
-
|
108
|
-
node.instance_variable_set("@ref",
|
109
|
-
node.ref ? node.ref.deep_copy : nil)
|
109
|
+
node.instance_variable_set('@children', Utils.deep_clone(children))
|
110
|
+
node.instance_variable_set('@ref', node.ref ? node.ref.deep_copy : nil)
|
110
111
|
node
|
111
112
|
end
|
112
113
|
|
113
114
|
protected
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
def split(node)
|
117
|
+
new_child = new_node(self)
|
118
|
+
split_children(node, node, new_child)
|
119
|
+
index = children.index(node)
|
120
|
+
children.insert(index + 1, new_child)
|
121
|
+
split! if children.length > limit
|
122
|
+
end
|
122
123
|
|
123
124
|
private
|
124
125
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
126
|
+
def new_node(parent = nil)
|
127
|
+
node = Node.new(document, limit, parent)
|
128
|
+
node.ref = document.ref!(node)
|
129
|
+
node
|
130
|
+
end
|
130
131
|
|
131
|
-
|
132
|
-
|
132
|
+
def split_children(node, left, right)
|
133
|
+
half = (node.limit + 1) / 2
|
133
134
|
|
134
|
-
|
135
|
+
left_children = node.children[0...half]
|
136
|
+
right_children = node.children[half..-1]
|
135
137
|
|
136
|
-
|
137
|
-
|
138
|
+
left.children.replace(left_children)
|
139
|
+
right.children.replace(right_children)
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
end
|
141
|
+
unless node.leaf?
|
142
|
+
left_children.each { |child| child.parent = left }
|
143
|
+
right_children.each { |child| child.parent = right }
|
143
144
|
end
|
145
|
+
end
|
144
146
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
149
|
-
return children.length
|
147
|
+
def insertion_point(value)
|
148
|
+
children.each_with_index do |child, index|
|
149
|
+
return index if child >= value
|
150
150
|
end
|
151
|
+
children.length
|
152
|
+
end
|
151
153
|
end
|
152
154
|
|
153
155
|
class Value #:nodoc:
|
@@ -157,11 +159,12 @@ module PDF
|
|
157
159
|
attr_reader :value
|
158
160
|
|
159
161
|
def initialize(name, value)
|
160
|
-
@name
|
162
|
+
@name = PDF::Core::LiteralString.new(name)
|
163
|
+
@value = value
|
161
164
|
end
|
162
165
|
|
163
|
-
def <=>(
|
164
|
-
name <=>
|
166
|
+
def <=>(other)
|
167
|
+
name <=> other.name
|
165
168
|
end
|
166
169
|
|
167
170
|
def inspect
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Implements PDF object repository
|
4
4
|
#
|
@@ -18,12 +18,12 @@ module PDF
|
|
18
18
|
@identifiers = []
|
19
19
|
|
20
20
|
@info ||= ref(opts[:info] || {}).identifier
|
21
|
-
@root ||= ref(:
|
21
|
+
@root ||= ref(Type: :Catalog).identifier
|
22
22
|
if opts[:print_scaling] == :none
|
23
|
-
root.data[:ViewerPreferences] = {:
|
23
|
+
root.data[:ViewerPreferences] = { PrintScaling: :None }
|
24
24
|
end
|
25
25
|
if pages.nil?
|
26
|
-
root.data[:Pages] = ref(:
|
26
|
+
root.data[:Pages] = ref(Type: :Pages, Count: 0, Kids: [])
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -48,22 +48,23 @@ module PDF
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# Adds the given reference to the store and returns the reference object.
|
51
|
-
# If the object provided is not a PDF::Core::Reference, one is created
|
52
|
-
# arguments provided.
|
51
|
+
# If the object provided is not a PDF::Core::Reference, one is created
|
52
|
+
# from the arguments provided.
|
53
53
|
#
|
54
54
|
def push(*args, &block)
|
55
|
-
reference =
|
56
|
-
args.first
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
reference =
|
56
|
+
if args.first.is_a?(PDF::Core::Reference)
|
57
|
+
args.first
|
58
|
+
else
|
59
|
+
PDF::Core::Reference.new(*args, &block)
|
60
|
+
end
|
60
61
|
|
61
62
|
@objects[reference.identifier] = reference
|
62
63
|
@identifiers << reference.identifier
|
63
64
|
reference
|
64
65
|
end
|
65
66
|
|
66
|
-
|
67
|
+
alias << push
|
67
68
|
|
68
69
|
def each
|
69
70
|
@identifiers.each do |id|
|
@@ -78,7 +79,7 @@ module PDF
|
|
78
79
|
def size
|
79
80
|
@identifiers.size
|
80
81
|
end
|
81
|
-
|
82
|
+
alias length size
|
82
83
|
|
83
84
|
# returns the object ID for a particular page in the document. Pages
|
84
85
|
# are indexed starting at 1 (not 0!).
|
@@ -90,17 +91,11 @@ module PDF
|
|
90
91
|
# object_id_for_page(-11)
|
91
92
|
# => 17
|
92
93
|
#
|
93
|
-
def object_id_for_page(
|
94
|
-
|
94
|
+
def object_id_for_page(page)
|
95
|
+
page -= 1 if page.positive?
|
95
96
|
flat_page_ids = get_page_objects(pages).flatten
|
96
|
-
flat_page_ids[
|
97
|
-
end
|
98
|
-
|
99
|
-
def is_utf8?(str)
|
100
|
-
str.force_encoding(::Encoding::UTF_8)
|
101
|
-
str.valid_encoding?
|
97
|
+
flat_page_ids[page]
|
102
98
|
end
|
103
99
|
end
|
104
100
|
end
|
105
101
|
end
|
106
|
-
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PDF
|
2
4
|
module Core
|
3
5
|
class OutlineItem #:nodoc:
|
@@ -11,11 +13,15 @@ module PDF
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def to_hash
|
14
|
-
hash = {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
hash = {
|
17
|
+
Title: title,
|
18
|
+
Parent: parent,
|
19
|
+
Count: closed ? -count : count
|
20
|
+
}
|
21
|
+
[
|
22
|
+
{ First: first }, { Last: last }, { Next: defined?(@next) && @next },
|
23
|
+
{ Prev: prev }, { Dest: dest }
|
24
|
+
].each do |h|
|
19
25
|
unless h.values.first.nil?
|
20
26
|
hash.merge!(h)
|
21
27
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PDF
|
2
4
|
module Core
|
3
5
|
class OutlineRoot #:nodoc:
|
@@ -8,7 +10,7 @@ module PDF
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def to_hash
|
11
|
-
{:
|
13
|
+
{ Type: :Outlines, Count: count, First: first, Last: last }
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/pdf/core/page.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# prawn/core/page.rb : Implements low-level representation of a PDF page
|
4
4
|
#
|
@@ -12,21 +12,49 @@ require_relative 'graphics_state'
|
|
12
12
|
module PDF
|
13
13
|
module Core
|
14
14
|
class Page #:nodoc:
|
15
|
-
attr_accessor :document, :margins, :stack
|
15
|
+
attr_accessor :art_indents, :bleeds, :crops, :document, :margins, :stack, :trims
|
16
16
|
attr_writer :content, :dictionary
|
17
17
|
|
18
|
-
|
18
|
+
ZERO_INDENTS = {
|
19
|
+
left: 0,
|
20
|
+
bottom: 0,
|
21
|
+
right: 0,
|
22
|
+
top: 0
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def initialize(document, options = {})
|
19
26
|
@document = document
|
20
|
-
@margins
|
21
|
-
|
22
|
-
|
23
|
-
|
27
|
+
@margins = options[:margins] || {
|
28
|
+
left: 36,
|
29
|
+
right: 36,
|
30
|
+
top: 36,
|
31
|
+
bottom: 36
|
32
|
+
}
|
33
|
+
@crops = options[:crops] || ZERO_INDENTS
|
34
|
+
@bleeds = options[:bleeds] || ZERO_INDENTS
|
35
|
+
@trims = options[:trims] || ZERO_INDENTS
|
36
|
+
@art_indents = options[:art_indents] || ZERO_INDENTS
|
24
37
|
@stack = GraphicStateStack.new(options[:graphic_state])
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
@size = options[:size] || 'LETTER'
|
39
|
+
@layout = options[:layout] || :portrait
|
40
|
+
|
41
|
+
@stamp_stream = nil
|
42
|
+
@stamp_dictionary = nil
|
43
|
+
|
44
|
+
@content = document.ref({})
|
45
|
+
content << 'q' << "\n"
|
46
|
+
@dictionary = document.ref(
|
47
|
+
Type: :Page,
|
48
|
+
Parent: document.state.store.pages,
|
49
|
+
MediaBox: dimensions,
|
50
|
+
CropBox: crop_box,
|
51
|
+
BleedBox: bleed_box,
|
52
|
+
TrimBox: trim_box,
|
53
|
+
ArtBox: art_box,
|
54
|
+
Contents: content
|
55
|
+
)
|
56
|
+
|
57
|
+
resources[:ProcSet] = %i[PDF Text ImageB ImageC ImageI]
|
30
58
|
end
|
31
59
|
|
32
60
|
def graphic_state
|
@@ -45,28 +73,26 @@ module PDF
|
|
45
73
|
end
|
46
74
|
|
47
75
|
def size
|
48
|
-
defined?(@size) && @size || dimensions[2,2]
|
76
|
+
defined?(@size) && @size || dimensions[2, 2]
|
49
77
|
end
|
50
78
|
|
51
79
|
def in_stamp_stream?
|
52
|
-
|
80
|
+
!@stamp_stream.nil?
|
53
81
|
end
|
54
82
|
|
55
83
|
def stamp_stream(dictionary)
|
56
|
-
@stamp_stream = ""
|
57
84
|
@stamp_dictionary = dictionary
|
85
|
+
@stamp_stream = @stamp_dictionary.stream
|
58
86
|
graphic_stack_size = stack.stack.size
|
59
87
|
|
60
88
|
document.save_graphics_state
|
61
|
-
document.
|
89
|
+
document.__send__(:freeze_stamp_graphics)
|
62
90
|
yield if block_given?
|
63
91
|
|
64
92
|
until graphic_stack_size == stack.stack.size
|
65
93
|
document.restore_graphics_state
|
66
94
|
end
|
67
95
|
|
68
|
-
@stamp_dictionary << @stamp_stream
|
69
|
-
|
70
96
|
@stamp_stream = nil
|
71
97
|
@stamp_dictionary = nil
|
72
98
|
end
|
@@ -76,7 +102,8 @@ module PDF
|
|
76
102
|
end
|
77
103
|
|
78
104
|
def dictionary
|
79
|
-
defined?(@stamp_dictionary) && @stamp_dictionary ||
|
105
|
+
defined?(@stamp_dictionary) && @stamp_dictionary ||
|
106
|
+
document.state.store[@dictionary]
|
80
107
|
end
|
81
108
|
|
82
109
|
def resources
|
@@ -116,62 +143,67 @@ module PDF
|
|
116
143
|
dictionary.data[:Contents].each do |stream|
|
117
144
|
stream.stream.compress! if document.compression_enabled?
|
118
145
|
end
|
119
|
-
|
120
|
-
content.stream.compress!
|
146
|
+
elsif document.compression_enabled?
|
147
|
+
content.stream.compress!
|
121
148
|
end
|
122
149
|
end
|
123
150
|
|
124
|
-
def imported_page?
|
125
|
-
@imported_page
|
126
|
-
end
|
127
|
-
|
128
151
|
def dimensions
|
129
|
-
return inherited_dictionary_value(:MediaBox) if imported_page?
|
130
|
-
|
131
152
|
coords = PDF::Core::PageGeometry::SIZES[size] || size
|
132
|
-
[0,0] +
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
153
|
+
[0, 0] +
|
154
|
+
case layout
|
155
|
+
when :portrait
|
156
|
+
coords
|
157
|
+
when :landscape
|
158
|
+
coords.reverse
|
159
|
+
else
|
160
|
+
raise PDF::Core::Errors::InvalidPageLayout,
|
161
|
+
'Layout must be either :portrait or :landscape'
|
162
|
+
end
|
141
163
|
end
|
142
164
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
@stamp_stream = nil
|
153
|
-
@stamp_dictionary = nil
|
154
|
-
@imported_page = true
|
165
|
+
def art_box
|
166
|
+
left, bottom, right, top = dimensions
|
167
|
+
[
|
168
|
+
left + art_indents[:left],
|
169
|
+
bottom + art_indents[:bottom],
|
170
|
+
right - art_indents[:right],
|
171
|
+
top - art_indents[:top]
|
172
|
+
]
|
155
173
|
end
|
156
174
|
|
157
|
-
def
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
175
|
+
def bleed_box
|
176
|
+
left, bottom, right, top = dimensions
|
177
|
+
[
|
178
|
+
left + bleeds[:left],
|
179
|
+
bottom + bleeds[:bottom],
|
180
|
+
right - bleeds[:right],
|
181
|
+
top - bleeds[:top]
|
182
|
+
]
|
183
|
+
end
|
164
184
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
185
|
+
def crop_box
|
186
|
+
left, bottom, right, top = dimensions
|
187
|
+
[
|
188
|
+
left + crops[:left],
|
189
|
+
bottom + crops[:bottom],
|
190
|
+
right - crops[:right],
|
191
|
+
top - crops[:top]
|
192
|
+
]
|
193
|
+
end
|
171
194
|
|
172
|
-
|
195
|
+
def trim_box
|
196
|
+
left, bottom, right, top = dimensions
|
197
|
+
[
|
198
|
+
left + trims[:left],
|
199
|
+
bottom + trims[:bottom],
|
200
|
+
right - trims[:right],
|
201
|
+
top - trims[:top]
|
202
|
+
]
|
173
203
|
end
|
174
204
|
|
205
|
+
private
|
206
|
+
|
175
207
|
# some entries in the Page dict can be inherited from parent Pages dicts.
|
176
208
|
#
|
177
209
|
# Starting with the current page dict, this method will walk up the
|
@@ -183,12 +215,10 @@ module PDF
|
|
183
215
|
def inherited_dictionary_value(key, local_dict = nil)
|
184
216
|
local_dict ||= dictionary.data
|
185
217
|
|
186
|
-
if local_dict.
|
218
|
+
if local_dict.key?(key)
|
187
219
|
local_dict[key]
|
188
|
-
elsif local_dict.
|
220
|
+
elsif local_dict.key?(:Parent)
|
189
221
|
inherited_dictionary_value(key, local_dict[:Parent].data)
|
190
|
-
else
|
191
|
-
nil
|
192
222
|
end
|
193
223
|
end
|
194
224
|
end
|