pdf-core 0.8.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/lib/pdf/core/annotations.rb +51 -13
- data/lib/pdf/core/byte_string.rb +3 -2
- data/lib/pdf/core/destinations.rb +64 -24
- data/lib/pdf/core/document_state.rb +100 -17
- data/lib/pdf/core/filter_list.rb +44 -5
- data/lib/pdf/core/filters.rb +26 -7
- data/lib/pdf/core/graphics_state.rb +74 -30
- data/lib/pdf/core/literal_string.rb +9 -10
- data/lib/pdf/core/name_tree.rb +76 -16
- data/lib/pdf/core/object_store.rb +69 -19
- data/lib/pdf/core/outline_item.rb +53 -5
- data/lib/pdf/core/outline_root.rb +18 -2
- data/lib/pdf/core/page.rb +158 -32
- data/lib/pdf/core/page_geometry.rb +4 -58
- data/lib/pdf/core/pdf_object.rb +62 -37
- data/lib/pdf/core/reference.rb +58 -15
- data/lib/pdf/core/renderer.rb +116 -47
- data/lib/pdf/core/stream.rb +43 -7
- data/lib/pdf/core/text.rb +255 -106
- data/lib/pdf/core/utils.rb +10 -1
- data/lib/pdf/core.rb +26 -16
- data/pdf-core.gemspec +31 -27
- data.tar.gz.sig +0 -0
- metadata +36 -101
- metadata.gz.sig +2 -1
- data/Gemfile +0 -5
- data/Rakefile +0 -17
data/lib/pdf/core/page.rb
CHANGED
@@ -1,44 +1,104 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# prawn/core/page.rb : Implements low-level representation of a PDF page
|
4
|
-
#
|
5
|
-
# Copyright February 2010, Gregory Brown. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
-
#
|
9
|
-
|
10
3
|
require_relative 'graphics_state'
|
11
4
|
|
12
5
|
module PDF
|
13
6
|
module Core
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
# Low-level representation of a PDF page
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Page
|
11
|
+
# Page art box indents relative to page edges.
|
12
|
+
#
|
13
|
+
# @return [Hash<[:left, :right, :top, :bottom], Numeric>
|
14
|
+
attr_accessor :art_indents
|
15
|
+
|
16
|
+
# Page bleed box indents.
|
17
|
+
#
|
18
|
+
# @return [Hash<[:left, :right, :top, :bottom], Numeric>
|
19
|
+
attr_accessor :bleeds
|
20
|
+
|
21
|
+
# Page crop box indents.
|
22
|
+
#
|
23
|
+
# @return [Hash<[:left, :right, :top, :bottom], Numeric>
|
24
|
+
attr_accessor :crops
|
25
|
+
|
26
|
+
# Page trim box indents.
|
27
|
+
#
|
28
|
+
# @return [Hash<[:left, :right, :top, :bottom], Numeric>
|
29
|
+
attr_accessor :trims
|
30
|
+
|
31
|
+
# Page margins.
|
32
|
+
#
|
33
|
+
# @return [Hash<[:left, :right, :top, :bottom], Numeric>
|
34
|
+
attr_accessor :margins
|
35
|
+
|
36
|
+
# Owning document.
|
37
|
+
#
|
38
|
+
# @return [Prawn::Document]
|
39
|
+
attr_accessor :document
|
40
|
+
|
41
|
+
# Graphic state stack.
|
42
|
+
#
|
43
|
+
# @return [GraphicStateStack]
|
44
|
+
attr_accessor :stack
|
45
|
+
|
46
|
+
# Page content stream reference.
|
47
|
+
#
|
48
|
+
# @return [PDF::Core::Reference<Hash>]
|
49
|
+
attr_writer :content
|
50
|
+
|
51
|
+
# Page dictionary reference.
|
52
|
+
#
|
53
|
+
# @return [PDF::Core::Reference<Hash>]
|
54
|
+
attr_writer :dictionary
|
18
55
|
|
56
|
+
# A convenince constant of no indents.
|
19
57
|
ZERO_INDENTS = {
|
20
58
|
left: 0,
|
21
59
|
bottom: 0,
|
22
60
|
right: 0,
|
23
|
-
top: 0
|
61
|
+
top: 0,
|
24
62
|
}.freeze
|
25
63
|
|
64
|
+
# @param document [Prawn::Document]
|
65
|
+
# @param options [Hash]
|
66
|
+
# @option options :margins [Hash{:left, :right, :top, :bottom => Number}, nil]
|
67
|
+
# ({ left: 0, right: 0, top: 0, bottom: 0 }) Page margins
|
68
|
+
# @option options :crop [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
|
69
|
+
# Page crop box
|
70
|
+
# @option options :bleed [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
|
71
|
+
# Page bleed box
|
72
|
+
# @option options :trims [Hash{:left, :right, :top, :bottom => Number}, nil] (ZERO_INDENTS)
|
73
|
+
# Page trim box
|
74
|
+
# @option options :art_indents [Hash{:left, :right, :top, :bottom => Number}, Numeric>, nil] (ZERO_INDENTS)
|
75
|
+
# Page art box indents.
|
76
|
+
# @option options :graphic_state [PDF::Core::GraphicState, nil] (nil)
|
77
|
+
# Initial graphic state
|
78
|
+
# @option options :size [String, Array<Numeric>, nil] ('LETTER')
|
79
|
+
# Page size. A string identifies a named page size defined in
|
80
|
+
# {PageGeometry}. An array must be a two element array specifying width
|
81
|
+
# and height in points.
|
82
|
+
# @option options :layout [:portrait, :landscape, nil] (:portrait)
|
83
|
+
# Page orientation.
|
26
84
|
def initialize(document, options = {})
|
27
85
|
@document = document
|
28
|
-
@margins
|
29
|
-
|
30
|
-
|
31
|
-
|
86
|
+
@margins = options[:margins] || {
|
87
|
+
left: 36,
|
88
|
+
right: 36,
|
89
|
+
top: 36,
|
90
|
+
bottom: 36,
|
91
|
+
}
|
32
92
|
@crops = options[:crops] || ZERO_INDENTS
|
33
93
|
@bleeds = options[:bleeds] || ZERO_INDENTS
|
34
94
|
@trims = options[:trims] || ZERO_INDENTS
|
35
95
|
@art_indents = options[:art_indents] || ZERO_INDENTS
|
36
96
|
@stack = GraphicStateStack.new(options[:graphic_state])
|
37
|
-
@size
|
38
|
-
@layout
|
97
|
+
@size = options[:size] || 'LETTER'
|
98
|
+
@layout = options[:layout] || :portrait
|
39
99
|
|
40
|
-
@stamp_stream
|
41
|
-
@stamp_dictionary
|
100
|
+
@stamp_stream = nil
|
101
|
+
@stamp_dictionary = nil
|
42
102
|
|
43
103
|
@content = document.ref({})
|
44
104
|
content << 'q' << "\n"
|
@@ -50,16 +110,23 @@ module PDF
|
|
50
110
|
BleedBox: bleed_box,
|
51
111
|
TrimBox: trim_box,
|
52
112
|
ArtBox: art_box,
|
53
|
-
Contents: content
|
113
|
+
Contents: content,
|
54
114
|
)
|
55
115
|
|
56
116
|
resources[:ProcSet] = %i[PDF Text ImageB ImageC ImageI]
|
57
117
|
end
|
58
118
|
|
119
|
+
# Current graphic state.
|
120
|
+
#
|
121
|
+
# @return [PDF::Core::GraphicState]
|
59
122
|
def graphic_state
|
60
123
|
stack.current_state
|
61
124
|
end
|
62
125
|
|
126
|
+
# Page layout.
|
127
|
+
#
|
128
|
+
# @return [:portrait] if page is talled than wider
|
129
|
+
# @return [:landscape] otherwise
|
63
130
|
def layout
|
64
131
|
return @layout if defined?(@layout) && @layout
|
65
132
|
|
@@ -71,40 +138,63 @@ module PDF
|
|
71
138
|
end
|
72
139
|
end
|
73
140
|
|
141
|
+
# Page size.
|
142
|
+
#
|
143
|
+
# @return [Array<Numeric>] a two-element array containing width and height
|
144
|
+
# of the page.
|
74
145
|
def size
|
75
|
-
defined?(@size) && @size || dimensions[2, 2]
|
146
|
+
(defined?(@size) && @size) || dimensions[2, 2]
|
76
147
|
end
|
77
148
|
|
149
|
+
# Are we drawing to a stamp right now?
|
150
|
+
#
|
151
|
+
# @return [Boolean]
|
78
152
|
def in_stamp_stream?
|
79
153
|
!@stamp_stream.nil?
|
80
154
|
end
|
81
155
|
|
156
|
+
# Draw to stamp.
|
157
|
+
#
|
158
|
+
# @param dictionary [PDF::Core::Reference<Hash>] stamp dictionary
|
159
|
+
# @yield outputs to the stamp
|
160
|
+
# @return [void]
|
82
161
|
def stamp_stream(dictionary)
|
83
162
|
@stamp_dictionary = dictionary
|
84
|
-
@stamp_stream
|
163
|
+
@stamp_stream = @stamp_dictionary.stream
|
85
164
|
graphic_stack_size = stack.stack.size
|
86
165
|
|
87
166
|
document.save_graphics_state
|
88
|
-
document.
|
167
|
+
document.__send__(:freeze_stamp_graphics)
|
89
168
|
yield if block_given?
|
90
169
|
|
91
170
|
until graphic_stack_size == stack.stack.size
|
92
171
|
document.restore_graphics_state
|
93
172
|
end
|
94
173
|
|
95
|
-
@stamp_stream
|
96
|
-
@stamp_dictionary
|
174
|
+
@stamp_stream = nil
|
175
|
+
@stamp_dictionary = nil
|
97
176
|
end
|
98
177
|
|
178
|
+
# Current content stream. Can be either the page content stream or a stamp
|
179
|
+
# content stream.
|
180
|
+
#
|
181
|
+
# @return [PDF::Core::Reference<Hash>]
|
99
182
|
def content
|
100
183
|
@stamp_stream || document.state.store[@content]
|
101
184
|
end
|
102
185
|
|
186
|
+
# Current content dictionary. Can be either the page dictionary or a stamp
|
187
|
+
# dictionary.
|
188
|
+
#
|
189
|
+
# @return [PDF::Core::Reference<Hash>]
|
103
190
|
def dictionary
|
104
|
-
defined?(@stamp_dictionary) && @stamp_dictionary ||
|
191
|
+
(defined?(@stamp_dictionary) && @stamp_dictionary) ||
|
105
192
|
document.state.store[@dictionary]
|
106
193
|
end
|
107
194
|
|
195
|
+
# Page resources dictionary.
|
196
|
+
#
|
197
|
+
# @return [Hash]
|
108
198
|
def resources
|
109
199
|
if dictionary.data[:Resources]
|
110
200
|
document.deref(dictionary.data[:Resources])
|
@@ -113,6 +203,9 @@ module PDF
|
|
113
203
|
end
|
114
204
|
end
|
115
205
|
|
206
|
+
# Fonts dictionary.
|
207
|
+
#
|
208
|
+
# @return [Hash]
|
116
209
|
def fonts
|
117
210
|
if resources[:Font]
|
118
211
|
document.deref(resources[:Font])
|
@@ -121,6 +214,9 @@ module PDF
|
|
121
214
|
end
|
122
215
|
end
|
123
216
|
|
217
|
+
# External objects dictionary.
|
218
|
+
#
|
219
|
+
# @return [Hash]
|
124
220
|
def xobjects
|
125
221
|
if resources[:XObject]
|
126
222
|
document.deref(resources[:XObject])
|
@@ -129,6 +225,9 @@ module PDF
|
|
129
225
|
end
|
130
226
|
end
|
131
227
|
|
228
|
+
# Graphic state parameter dictionary.
|
229
|
+
#
|
230
|
+
# @return [Hash]
|
132
231
|
def ext_gstates
|
133
232
|
if resources[:ExtGState]
|
134
233
|
document.deref(resources[:ExtGState])
|
@@ -137,6 +236,9 @@ module PDF
|
|
137
236
|
end
|
138
237
|
end
|
139
238
|
|
239
|
+
# Finalize page.
|
240
|
+
#
|
241
|
+
# @return [void]
|
140
242
|
def finalize
|
141
243
|
if dictionary.data[:Contents].is_a?(Array)
|
142
244
|
dictionary.data[:Contents].each do |stream|
|
@@ -147,9 +249,12 @@ module PDF
|
|
147
249
|
end
|
148
250
|
end
|
149
251
|
|
252
|
+
# Page dimensions.
|
253
|
+
#
|
254
|
+
# @return [Array<Numeric>]
|
150
255
|
def dimensions
|
151
256
|
coords = PDF::Core::PageGeometry::SIZES[size] || size
|
152
|
-
|
257
|
+
coords =
|
153
258
|
case layout
|
154
259
|
when :portrait
|
155
260
|
coords
|
@@ -159,45 +264,66 @@ module PDF
|
|
159
264
|
raise PDF::Core::Errors::InvalidPageLayout,
|
160
265
|
'Layout must be either :portrait or :landscape'
|
161
266
|
end
|
267
|
+
[0, 0].concat(coords)
|
162
268
|
end
|
163
269
|
|
270
|
+
# A rectangle, expressed in default user space units, defining the extent
|
271
|
+
# of the page's meaningful content (including potential white space) as
|
272
|
+
# intended by the page's creator.
|
273
|
+
#
|
274
|
+
# @return [Array<Numeric>]
|
164
275
|
def art_box
|
165
276
|
left, bottom, right, top = dimensions
|
166
277
|
[
|
167
278
|
left + art_indents[:left],
|
168
279
|
bottom + art_indents[:bottom],
|
169
280
|
right - art_indents[:right],
|
170
|
-
top - art_indents[:top]
|
281
|
+
top - art_indents[:top],
|
171
282
|
]
|
172
283
|
end
|
173
284
|
|
285
|
+
# Page bleed box. A rectangle, expressed in default user space units,
|
286
|
+
# defining the region to which the contents of the page should be clipped
|
287
|
+
# when output in a production environment.
|
288
|
+
#
|
289
|
+
# @return [Array<Numeric>]
|
174
290
|
def bleed_box
|
175
291
|
left, bottom, right, top = dimensions
|
176
292
|
[
|
177
293
|
left + bleeds[:left],
|
178
294
|
bottom + bleeds[:bottom],
|
179
295
|
right - bleeds[:right],
|
180
|
-
top - bleeds[:top]
|
296
|
+
top - bleeds[:top],
|
181
297
|
]
|
182
298
|
end
|
183
299
|
|
300
|
+
# A rectangle, expressed in default user space units, defining the visible
|
301
|
+
# region of default user space. When the page is displayed or printed, its
|
302
|
+
# contents are to be clipped (cropped) to this rectangle and then imposed
|
303
|
+
# on the output medium in some implementation-defined manner.
|
304
|
+
#
|
305
|
+
# @return [Array<Numeric>]
|
184
306
|
def crop_box
|
185
307
|
left, bottom, right, top = dimensions
|
186
308
|
[
|
187
309
|
left + crops[:left],
|
188
310
|
bottom + crops[:bottom],
|
189
311
|
right - crops[:right],
|
190
|
-
top - crops[:top]
|
312
|
+
top - crops[:top],
|
191
313
|
]
|
192
314
|
end
|
193
315
|
|
316
|
+
# A rectangle, expressed in default user space units, defining the
|
317
|
+
# intended dimensions of the finished page after trimming.
|
318
|
+
#
|
319
|
+
# @return [Array<Numeric>]
|
194
320
|
def trim_box
|
195
321
|
left, bottom, right, top = dimensions
|
196
322
|
[
|
197
323
|
left + trims[:left],
|
198
324
|
bottom + trims[:bottom],
|
199
325
|
right - trims[:right],
|
200
|
-
top - trims[:top]
|
326
|
+
top - trims[:top],
|
201
327
|
]
|
202
328
|
end
|
203
329
|
|
@@ -9,64 +9,10 @@
|
|
9
9
|
module PDF
|
10
10
|
module Core
|
11
11
|
# Dimensions pulled from PDF::Writer, rubyforge.org/projects/ruby-pdf
|
12
|
-
#
|
13
|
-
# All of these dimensions are in PDF Points (1/72 inch)
|
14
|
-
#
|
15
|
-
# ===Inbuilt Sizes:
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# 4A0:: => 4767.87 x 6740.79
|
19
|
-
# 2A0:: => 3370.39 x 4767.87
|
20
|
-
# A0:: => 2383.94 x 3370.39
|
21
|
-
# A1:: => 1683.78 x 2383.94
|
22
|
-
# A2:: => 1190.55 x 1683.78
|
23
|
-
# A3:: => 841.89 x 1190.55
|
24
|
-
# A4:: => 595.28 x 841.89
|
25
|
-
# A5:: => 419.53 x 595.28
|
26
|
-
# A6:: => 297.64 x 419.53
|
27
|
-
# A7:: => 209.76 x 297.64
|
28
|
-
# A8:: => 147.40 x 209.76
|
29
|
-
# A9:: => 104.88 x 147.40
|
30
|
-
# A10:: => 73.70 x 104.88
|
31
|
-
# B0:: => 2834.65 x 4008.19
|
32
|
-
# B1:: => 2004.09 x 2834.65
|
33
|
-
# B2:: => 1417.32 x 2004.09
|
34
|
-
# B3:: => 1000.63 x 1417.32
|
35
|
-
# B4:: => 708.66 x 1000.63
|
36
|
-
# B5:: => 498.90 x 708.66
|
37
|
-
# B6:: => 354.33 x 498.90
|
38
|
-
# B7:: => 249.45 x 354.33
|
39
|
-
# B8:: => 175.75 x 249.45
|
40
|
-
# B9:: => 124.72 x 175.75
|
41
|
-
# B10:: => 87.87 x 124.72
|
42
|
-
# C0:: => 2599.37 x 3676.54
|
43
|
-
# C1:: => 1836.85 x 2599.37
|
44
|
-
# C2:: => 1298.27 x 1836.85
|
45
|
-
# C3:: => 918.43 x 1298.27
|
46
|
-
# C4:: => 649.13 x 918.43
|
47
|
-
# C5:: => 459.21 x 649.13
|
48
|
-
# C6:: => 323.15 x 459.21
|
49
|
-
# C7:: => 229.61 x 323.15
|
50
|
-
# C8:: => 161.57 x 229.61
|
51
|
-
# C9:: => 113.39 x 161.57
|
52
|
-
# C10:: => 79.37 x 113.39
|
53
|
-
# RA0:: => 2437.80 x 3458.27
|
54
|
-
# RA1:: => 1729.13 x 2437.80
|
55
|
-
# RA2:: => 1218.90 x 1729.13
|
56
|
-
# RA3:: => 864.57 x 1218.90
|
57
|
-
# RA4:: => 609.45 x 864.57
|
58
|
-
# SRA0:: => 2551.18 x 3628.35
|
59
|
-
# SRA1:: => 1814.17 x 2551.18
|
60
|
-
# SRA2:: => 1275.59 x 1814.17
|
61
|
-
# SRA3:: => 907.09 x 1275.59
|
62
|
-
# SRA4:: => 637.80 x 907.09
|
63
|
-
# EXECUTIVE:: => 521.86 x 756.00
|
64
|
-
# FOLIO:: => 612.00 x 936.00
|
65
|
-
# LEGAL:: => 612.00 x 1008.00
|
66
|
-
# LETTER:: => 612.00 x 792.00
|
67
|
-
# TABLOID:: => 792.00 x 1224.00
|
68
|
-
#
|
69
12
|
module PageGeometry
|
13
|
+
# Named page sizes.
|
14
|
+
#
|
15
|
+
# All of these dimensions are in PDF Points (1/72 inch).
|
70
16
|
SIZES = {
|
71
17
|
'4A0' => [4767.87, 6740.79],
|
72
18
|
'2A0' => [3370.39, 4767.87],
|
@@ -117,7 +63,7 @@ module PDF
|
|
117
63
|
'FOLIO' => [612.00, 936.00],
|
118
64
|
'LEGAL' => [612.00, 1008.00],
|
119
65
|
'LETTER' => [612.00, 792.00],
|
120
|
-
'TABLOID' => [792.00, 1224.00]
|
66
|
+
'TABLOID' => [792.00, 1224.00],
|
121
67
|
}.freeze
|
122
68
|
end
|
123
69
|
end
|
data/lib/pdf/core/pdf_object.rb
CHANGED
@@ -1,43 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# pdf_object.rb : Handles Ruby to PDF object serialization
|
4
|
-
#
|
5
|
-
# Copyright April 2008, Gregory Brown. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
-
|
9
|
-
# Top level Module
|
10
|
-
#
|
11
3
|
module PDF
|
12
4
|
module Core
|
13
5
|
module_function
|
14
6
|
|
7
|
+
# Serializes floating number into a string
|
8
|
+
#
|
9
|
+
# @param num [Numeric]
|
10
|
+
# @return [String]
|
15
11
|
def real(num)
|
16
|
-
num
|
12
|
+
result = format('%.5f', num)
|
13
|
+
result.sub!(/((?<!\.)0)+\z/, '')
|
14
|
+
result
|
17
15
|
end
|
18
16
|
|
17
|
+
# Serializes a n array of numbers. This is specifically for use in PDF
|
18
|
+
# content streams.
|
19
|
+
#
|
20
|
+
# @param array [Array<Numeric>]
|
21
|
+
# @return [String]
|
19
22
|
def real_params(array)
|
20
23
|
array.map { |e| real(e) }.join(' ')
|
21
24
|
end
|
22
25
|
|
26
|
+
# Converts string to UTF-16BE encoding as expected by PDF.
|
27
|
+
#
|
28
|
+
# @param str [String]
|
29
|
+
# @return [String]
|
30
|
+
# @api private
|
23
31
|
def utf8_to_utf16(str)
|
24
|
-
(+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE)
|
32
|
+
(+"\xFE\xFF").force_encoding(::Encoding::UTF_16BE) <<
|
25
33
|
str.encode(::Encoding::UTF_16BE)
|
26
34
|
end
|
27
35
|
|
28
|
-
#
|
36
|
+
# Encodes any string into a hex representation. The result is a string
|
29
37
|
# with only 0-9 and a-f characters. That result is valid ASCII so tag
|
30
|
-
# it as such to account for behaviour of different ruby VMs
|
38
|
+
# it as such to account for behaviour of different ruby VMs.
|
39
|
+
#
|
40
|
+
# @param str [String]
|
41
|
+
# @return [String]
|
31
42
|
def string_to_hex(str)
|
32
|
-
str.
|
43
|
+
str.unpack1('H*').force_encoding(::Encoding::US_ASCII)
|
33
44
|
end
|
34
45
|
|
46
|
+
# Characters to escape in name objects
|
47
|
+
# @api private
|
48
|
+
ESCAPED_NAME_CHARACTERS = (1..32).to_a + [35, 40, 41, 47, 60, 62] + (127..255).to_a
|
49
|
+
|
50
|
+
# How to escape special characters in literal strings
|
51
|
+
# @api private
|
52
|
+
STRING_ESCAPE_MAP = { '(' => '\(', ')' => '\)', '\\' => '\\\\', "\r" => '\r' }.freeze
|
53
|
+
|
35
54
|
# Serializes Ruby objects to their PDF equivalents. Most primitive objects
|
36
55
|
# will work as expected, but please note that Name objects are represented
|
37
56
|
# by Ruby Symbol objects and Dictionary objects are represented by Ruby
|
38
57
|
# hashes (keyed by symbols)
|
39
58
|
#
|
40
|
-
#
|
59
|
+
# Examples:
|
41
60
|
#
|
42
61
|
# pdf_object(true) #=> "true"
|
43
62
|
# pdf_object(false) #=> "false"
|
@@ -46,45 +65,53 @@ module PDF
|
|
46
65
|
# pdf_object(:Symbol) #=> "/Symbol"
|
47
66
|
# pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
|
48
67
|
#
|
68
|
+
# @param obj [nil, Boolean, Numeric, Array, Hash, Time, Symbol, String,
|
69
|
+
# PDF::Core::ByteString, PDF::Core::LiteralString,
|
70
|
+
# PDF::Core::NameTree::Node, PDF::Core::NameTree::Value,
|
71
|
+
# PDF::Core::OutlineRoot, PDF::Core::OutlineItem, PDF::Core::Reference]
|
72
|
+
# Object to serialise
|
73
|
+
# @param in_content_stream [Boolean] Specifies whther to use content stream
|
74
|
+
# format or object format
|
75
|
+
# @return [String]
|
76
|
+
# @raise [PDF::Core::Errors::FailedObjectConversion]
|
49
77
|
def pdf_object(obj, in_content_stream = false)
|
50
78
|
case obj
|
51
|
-
when NilClass
|
52
|
-
when TrueClass
|
79
|
+
when NilClass then 'null'
|
80
|
+
when TrueClass then 'true'
|
53
81
|
when FalseClass then 'false'
|
54
82
|
when Numeric
|
55
|
-
|
56
|
-
|
57
|
-
# NOTE: this can fail on huge floating point numbers, but it seems
|
58
|
-
# unlikely to ever happen in practice.
|
59
|
-
num_string = String(obj)
|
83
|
+
num_string = obj.is_a?(Integer) ? String(obj) : real(obj)
|
60
84
|
|
61
85
|
# Truncate trailing fraction zeroes
|
62
|
-
num_string.sub(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
|
86
|
+
num_string.sub!(/(\d*)((\.0*$)|(\.0*[1-9]*)0*$)/, '\1\4')
|
87
|
+
num_string
|
63
88
|
when Array
|
64
|
-
|
89
|
+
"[#{obj.map { |e| pdf_object(e, in_content_stream) }.join(' ')}]"
|
65
90
|
when PDF::Core::LiteralString
|
66
|
-
obj = obj.gsub(/[\\\
|
91
|
+
obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
|
67
92
|
"(#{obj})"
|
68
93
|
when Time
|
69
|
-
obj = obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop
|
70
|
-
obj = obj.gsub(/[\\\
|
94
|
+
obj = "#{obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop}'00'"
|
95
|
+
obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
|
71
96
|
"(#{obj})"
|
72
97
|
when PDF::Core::ByteString
|
73
|
-
"<#{obj.
|
98
|
+
"<#{obj.unpack1('H*')}>"
|
74
99
|
when String
|
75
100
|
obj = utf8_to_utf16(obj) unless in_content_stream
|
76
101
|
"<#{string_to_hex(obj)}>"
|
77
102
|
when Symbol
|
78
|
-
'/'
|
79
|
-
if
|
80
|
-
|
103
|
+
(@symbol_str_cache ||= {})[obj] ||= (+'/') << obj.to_s.unpack('C*').map { |n|
|
104
|
+
if ESCAPED_NAME_CHARACTERS.include?(n)
|
105
|
+
"##{n.to_s(16).upcase}"
|
81
106
|
else
|
82
|
-
|
107
|
+
n.chr
|
83
108
|
end
|
84
|
-
|
109
|
+
}.join
|
85
110
|
when ::Hash
|
86
111
|
output = +'<< '
|
87
|
-
obj
|
112
|
+
obj
|
113
|
+
.sort_by { |k, _v| k.to_s }
|
114
|
+
.each do |(k, v)|
|
88
115
|
unless k.is_a?(String) || k.is_a?(Symbol)
|
89
116
|
raise PDF::Core::Errors::FailedObjectConversion,
|
90
117
|
'A PDF Dictionary must be keyed by names'
|
@@ -95,12 +122,10 @@ module PDF
|
|
95
122
|
output << '>>'
|
96
123
|
when PDF::Core::Reference
|
97
124
|
obj.to_s
|
98
|
-
when PDF::Core::NameTree::Node
|
125
|
+
when PDF::Core::NameTree::Node, PDF::Core::OutlineRoot, PDF::Core::OutlineItem
|
99
126
|
pdf_object(obj.to_hash)
|
100
127
|
when PDF::Core::NameTree::Value
|
101
|
-
pdf_object(obj.name)
|
102
|
-
when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
|
103
|
-
pdf_object(obj.to_hash)
|
128
|
+
"#{pdf_object(obj.name)} #{pdf_object(obj.value)}"
|
104
129
|
else
|
105
130
|
raise PDF::Core::Errors::FailedObjectConversion,
|
106
131
|
"This object cannot be serialized to PDF (#{obj.inspect})"
|
data/lib/pdf/core/reference.rb
CHANGED
@@ -1,25 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# reference.rb : Implementation of PDF indirect objects
|
4
|
-
#
|
5
|
-
# Copyright April 2008, Gregory Brown. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
-
|
9
3
|
require 'pdf/core/utils'
|
10
4
|
|
11
5
|
module PDF
|
12
6
|
module Core
|
13
|
-
|
14
|
-
|
7
|
+
# PDF indirect objects
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Reference
|
11
|
+
# Object identifier
|
12
|
+
# @return [Integer]
|
13
|
+
attr_accessor :identifier
|
14
|
+
|
15
|
+
# Object generation
|
16
|
+
# @return [Integer]
|
17
|
+
attr_accessor :gen
|
18
|
+
|
19
|
+
# Object data
|
20
|
+
# @return [any]
|
21
|
+
attr_accessor :data
|
22
|
+
|
23
|
+
# Offset of the serialized object in the document
|
24
|
+
# @return [Integer]
|
25
|
+
attr_accessor :offset
|
15
26
|
|
27
|
+
# Object stream
|
28
|
+
# @return [Stream]
|
29
|
+
attr_accessor :stream
|
30
|
+
|
31
|
+
# In PDF only dict object can have a stream attached. This exception
|
32
|
+
# indicates someone tried to add a stream to another kind of object.
|
33
|
+
class CannotAttachStream < StandardError
|
34
|
+
# @param message [String] Error message
|
35
|
+
def initialize(message = 'Cannot attach stream to a non-dictionary object')
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param id [Integer] Object identifier
|
41
|
+
# @param data [any] Object data
|
16
42
|
def initialize(id, data)
|
17
43
|
@identifier = id
|
18
|
-
@gen
|
19
|
-
@data
|
20
|
-
@stream
|
44
|
+
@gen = 0
|
45
|
+
@data = data
|
46
|
+
@stream = Stream.new
|
21
47
|
end
|
22
48
|
|
49
|
+
# Serialized PDF object
|
50
|
+
#
|
51
|
+
# @return [String]
|
23
52
|
def object
|
24
53
|
output = +"#{@identifier} #{gen} obj\n"
|
25
54
|
if @stream.empty?
|
@@ -32,20 +61,31 @@ module PDF
|
|
32
61
|
output << "endobj\n"
|
33
62
|
end
|
34
63
|
|
64
|
+
# Appends data to object stream
|
65
|
+
#
|
66
|
+
# @param io [String] data
|
67
|
+
# @return [io]
|
68
|
+
# @raise [CannotAttachStream] if object is not a dict
|
35
69
|
def <<(io)
|
36
70
|
unless @data.is_a?(::Hash)
|
37
|
-
raise
|
71
|
+
raise CannotAttachStream
|
38
72
|
end
|
73
|
+
|
39
74
|
(@stream ||= Stream.new) << io
|
40
75
|
end
|
41
76
|
|
77
|
+
# Object reference in PDF format
|
78
|
+
#
|
79
|
+
# @return [String]
|
42
80
|
def to_s
|
43
81
|
"#{@identifier} #{gen} R"
|
44
82
|
end
|
45
83
|
|
46
|
-
# Creates a deep copy of this ref.
|
47
|
-
# given dictionary entries between the old ref and the new.
|
84
|
+
# Creates a deep copy of this ref.
|
48
85
|
#
|
86
|
+
# @param share [Array<Symbol>] a list of dictionary entries to share
|
87
|
+
# between the old ref and the new
|
88
|
+
# @return [Reference]
|
49
89
|
def deep_copy(share = [])
|
50
90
|
r = dup
|
51
91
|
|
@@ -66,8 +106,11 @@ module PDF
|
|
66
106
|
end
|
67
107
|
|
68
108
|
# Replaces the data and stream with that of other_ref.
|
109
|
+
#
|
110
|
+
# @param other_ref [Reference]
|
111
|
+
# @return [void]
|
69
112
|
def replace(other_ref)
|
70
|
-
@data
|
113
|
+
@data = other_ref.data
|
71
114
|
@stream = other_ref.stream
|
72
115
|
end
|
73
116
|
end
|