pdf-core 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/pdf/core/annotations.rb +51 -13
- data/lib/pdf/core/byte_string.rb +3 -1
- data/lib/pdf/core/destinations.rb +64 -24
- data/lib/pdf/core/document_state.rb +97 -14
- data/lib/pdf/core/filter_list.rb +30 -1
- data/lib/pdf/core/filters.rb +26 -7
- data/lib/pdf/core/graphics_state.rb +68 -23
- data/lib/pdf/core/literal_string.rb +9 -9
- data/lib/pdf/core/name_tree.rb +74 -13
- data/lib/pdf/core/object_store.rb +69 -19
- data/lib/pdf/core/outline_item.rb +53 -4
- data/lib/pdf/core/outline_root.rb +18 -2
- data/lib/pdf/core/page.rb +148 -23
- data/lib/pdf/core/page_geometry.rb +4 -58
- data/lib/pdf/core/pdf_object.rb +57 -36
- data/lib/pdf/core/reference.rb +50 -14
- data/lib/pdf/core/renderer.rb +115 -44
- data/lib/pdf/core/stream.rb +38 -8
- data/lib/pdf/core/text.rb +242 -102
- data/lib/pdf/core/utils.rb +8 -0
- data/lib/pdf/core.rb +26 -16
- data/pdf-core.gemspec +27 -23
- data.tar.gz.sig +0 -0
- metadata +44 -107
- metadata.gz.sig +2 -2
- data/Gemfile +0 -5
- data/Rakefile +0 -29
data/lib/pdf/core/page.rb
CHANGED
@@ -1,34 +1,93 @@
|
|
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
|
-
|
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
|
17
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
|
55
|
+
|
56
|
+
# A convenince constant of no indents.
|
18
57
|
ZERO_INDENTS = {
|
19
58
|
left: 0,
|
20
59
|
bottom: 0,
|
21
60
|
right: 0,
|
22
|
-
top: 0
|
61
|
+
top: 0,
|
23
62
|
}.freeze
|
24
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.
|
25
84
|
def initialize(document, options = {})
|
26
85
|
@document = document
|
27
86
|
@margins = options[:margins] || {
|
28
87
|
left: 36,
|
29
88
|
right: 36,
|
30
89
|
top: 36,
|
31
|
-
bottom: 36
|
90
|
+
bottom: 36,
|
32
91
|
}
|
33
92
|
@crops = options[:crops] || ZERO_INDENTS
|
34
93
|
@bleeds = options[:bleeds] || ZERO_INDENTS
|
@@ -51,16 +110,23 @@ module PDF
|
|
51
110
|
BleedBox: bleed_box,
|
52
111
|
TrimBox: trim_box,
|
53
112
|
ArtBox: art_box,
|
54
|
-
Contents: content
|
113
|
+
Contents: content,
|
55
114
|
)
|
56
115
|
|
57
116
|
resources[:ProcSet] = %i[PDF Text ImageB ImageC ImageI]
|
58
117
|
end
|
59
118
|
|
119
|
+
# Current graphic state.
|
120
|
+
#
|
121
|
+
# @return [PDF::Core::GraphicState]
|
60
122
|
def graphic_state
|
61
123
|
stack.current_state
|
62
124
|
end
|
63
125
|
|
126
|
+
# Page layout.
|
127
|
+
#
|
128
|
+
# @return [:portrait] if page is talled than wider
|
129
|
+
# @return [:landscape] otherwise
|
64
130
|
def layout
|
65
131
|
return @layout if defined?(@layout) && @layout
|
66
132
|
|
@@ -72,17 +138,29 @@ module PDF
|
|
72
138
|
end
|
73
139
|
end
|
74
140
|
|
141
|
+
# Page size.
|
142
|
+
#
|
143
|
+
# @return [Array<Numeric>] a two-element array containing width and height
|
144
|
+
# of the page.
|
75
145
|
def size
|
76
|
-
defined?(@size) && @size || dimensions[2, 2]
|
146
|
+
(defined?(@size) && @size) || dimensions[2, 2]
|
77
147
|
end
|
78
148
|
|
149
|
+
# Are we drawing to a stamp right now?
|
150
|
+
#
|
151
|
+
# @return [Boolean]
|
79
152
|
def in_stamp_stream?
|
80
153
|
!@stamp_stream.nil?
|
81
154
|
end
|
82
155
|
|
156
|
+
# Draw to stamp.
|
157
|
+
#
|
158
|
+
# @param dictionary [PDF::Core::Reference<Hash>] stamp dictionary
|
159
|
+
# @yield outputs to the stamp
|
160
|
+
# @return [void]
|
83
161
|
def stamp_stream(dictionary)
|
84
162
|
@stamp_dictionary = dictionary
|
85
|
-
@stamp_stream
|
163
|
+
@stamp_stream = @stamp_dictionary.stream
|
86
164
|
graphic_stack_size = stack.stack.size
|
87
165
|
|
88
166
|
document.save_graphics_state
|
@@ -93,19 +171,30 @@ module PDF
|
|
93
171
|
document.restore_graphics_state
|
94
172
|
end
|
95
173
|
|
96
|
-
@stamp_stream
|
97
|
-
@stamp_dictionary
|
174
|
+
@stamp_stream = nil
|
175
|
+
@stamp_dictionary = nil
|
98
176
|
end
|
99
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>]
|
100
182
|
def content
|
101
183
|
@stamp_stream || document.state.store[@content]
|
102
184
|
end
|
103
185
|
|
186
|
+
# Current content dictionary. Can be either the page dictionary or a stamp
|
187
|
+
# dictionary.
|
188
|
+
#
|
189
|
+
# @return [PDF::Core::Reference<Hash>]
|
104
190
|
def dictionary
|
105
|
-
defined?(@stamp_dictionary) && @stamp_dictionary ||
|
191
|
+
(defined?(@stamp_dictionary) && @stamp_dictionary) ||
|
106
192
|
document.state.store[@dictionary]
|
107
193
|
end
|
108
194
|
|
195
|
+
# Page resources dictionary.
|
196
|
+
#
|
197
|
+
# @return [Hash]
|
109
198
|
def resources
|
110
199
|
if dictionary.data[:Resources]
|
111
200
|
document.deref(dictionary.data[:Resources])
|
@@ -114,6 +203,9 @@ module PDF
|
|
114
203
|
end
|
115
204
|
end
|
116
205
|
|
206
|
+
# Fonts dictionary.
|
207
|
+
#
|
208
|
+
# @return [Hash]
|
117
209
|
def fonts
|
118
210
|
if resources[:Font]
|
119
211
|
document.deref(resources[:Font])
|
@@ -122,6 +214,9 @@ module PDF
|
|
122
214
|
end
|
123
215
|
end
|
124
216
|
|
217
|
+
# External objects dictionary.
|
218
|
+
#
|
219
|
+
# @return [Hash]
|
125
220
|
def xobjects
|
126
221
|
if resources[:XObject]
|
127
222
|
document.deref(resources[:XObject])
|
@@ -130,6 +225,9 @@ module PDF
|
|
130
225
|
end
|
131
226
|
end
|
132
227
|
|
228
|
+
# Graphic state parameter dictionary.
|
229
|
+
#
|
230
|
+
# @return [Hash]
|
133
231
|
def ext_gstates
|
134
232
|
if resources[:ExtGState]
|
135
233
|
document.deref(resources[:ExtGState])
|
@@ -138,6 +236,9 @@ module PDF
|
|
138
236
|
end
|
139
237
|
end
|
140
238
|
|
239
|
+
# Finalize page.
|
240
|
+
#
|
241
|
+
# @return [void]
|
141
242
|
def finalize
|
142
243
|
if dictionary.data[:Contents].is_a?(Array)
|
143
244
|
dictionary.data[:Contents].each do |stream|
|
@@ -148,9 +249,12 @@ module PDF
|
|
148
249
|
end
|
149
250
|
end
|
150
251
|
|
252
|
+
# Page dimensions.
|
253
|
+
#
|
254
|
+
# @return [Array<Numeric>]
|
151
255
|
def dimensions
|
152
256
|
coords = PDF::Core::PageGeometry::SIZES[size] || size
|
153
|
-
|
257
|
+
coords =
|
154
258
|
case layout
|
155
259
|
when :portrait
|
156
260
|
coords
|
@@ -160,45 +264,66 @@ module PDF
|
|
160
264
|
raise PDF::Core::Errors::InvalidPageLayout,
|
161
265
|
'Layout must be either :portrait or :landscape'
|
162
266
|
end
|
267
|
+
[0, 0].concat(coords)
|
163
268
|
end
|
164
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>]
|
165
275
|
def art_box
|
166
276
|
left, bottom, right, top = dimensions
|
167
277
|
[
|
168
278
|
left + art_indents[:left],
|
169
279
|
bottom + art_indents[:bottom],
|
170
280
|
right - art_indents[:right],
|
171
|
-
top - art_indents[:top]
|
281
|
+
top - art_indents[:top],
|
172
282
|
]
|
173
283
|
end
|
174
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>]
|
175
290
|
def bleed_box
|
176
291
|
left, bottom, right, top = dimensions
|
177
292
|
[
|
178
293
|
left + bleeds[:left],
|
179
294
|
bottom + bleeds[:bottom],
|
180
295
|
right - bleeds[:right],
|
181
|
-
top - bleeds[:top]
|
296
|
+
top - bleeds[:top],
|
182
297
|
]
|
183
298
|
end
|
184
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>]
|
185
306
|
def crop_box
|
186
307
|
left, bottom, right, top = dimensions
|
187
308
|
[
|
188
309
|
left + crops[:left],
|
189
310
|
bottom + crops[:bottom],
|
190
311
|
right - crops[:right],
|
191
|
-
top - crops[:top]
|
312
|
+
top - crops[:top],
|
192
313
|
]
|
193
314
|
end
|
194
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>]
|
195
320
|
def trim_box
|
196
321
|
left, bottom, right, top = dimensions
|
197
322
|
[
|
198
323
|
left + trims[:left],
|
199
324
|
bottom + trims[:bottom],
|
200
325
|
right - trims[:right],
|
201
|
-
top - trims[:top]
|
326
|
+
top - trims[:top],
|
202
327
|
]
|
203
328
|
end
|
204
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,45 +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
|
-
format('
|
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
43
|
str.unpack1('H*').force_encoding(::Encoding::US_ASCII)
|
33
44
|
end
|
34
45
|
|
46
|
+
# Characters to escape in name objects
|
47
|
+
# @api private
|
35
48
|
ESCAPED_NAME_CHARACTERS = (1..32).to_a + [35, 40, 41, 47, 60, 62] + (127..255).to_a
|
36
49
|
|
50
|
+
# How to escape special characters in literal strings
|
51
|
+
# @api private
|
52
|
+
STRING_ESCAPE_MAP = { '(' => '\(', ')' => '\)', '\\' => '\\\\', "\r" => '\r' }.freeze
|
53
|
+
|
37
54
|
# Serializes Ruby objects to their PDF equivalents. Most primitive objects
|
38
55
|
# will work as expected, but please note that Name objects are represented
|
39
56
|
# by Ruby Symbol objects and Dictionary objects are represented by Ruby
|
40
57
|
# hashes (keyed by symbols)
|
41
58
|
#
|
42
|
-
#
|
59
|
+
# Examples:
|
43
60
|
#
|
44
61
|
# pdf_object(true) #=> "true"
|
45
62
|
# pdf_object(false) #=> "false"
|
@@ -48,28 +65,34 @@ module PDF
|
|
48
65
|
# pdf_object(:Symbol) #=> "/Symbol"
|
49
66
|
# pdf_object(['foo',:bar, [1,2]]) #=> "[foo /bar [1 2]]"
|
50
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]
|
51
77
|
def pdf_object(obj, in_content_stream = false)
|
52
78
|
case obj
|
53
|
-
when NilClass
|
54
|
-
when TrueClass
|
79
|
+
when NilClass then 'null'
|
80
|
+
when TrueClass then 'true'
|
55
81
|
when FalseClass then 'false'
|
56
82
|
when Numeric
|
57
|
-
|
58
|
-
|
59
|
-
# NOTE: this can fail on huge floating point numbers, but it seems
|
60
|
-
# unlikely to ever happen in practice.
|
61
|
-
num_string = String(obj)
|
83
|
+
num_string = obj.is_a?(Integer) ? String(obj) : real(obj)
|
62
84
|
|
63
85
|
# Truncate trailing fraction zeroes
|
64
|
-
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
|
65
88
|
when Array
|
66
89
|
"[#{obj.map { |e| pdf_object(e, in_content_stream) }.join(' ')}]"
|
67
90
|
when PDF::Core::LiteralString
|
68
|
-
obj = obj.gsub(/[\\\
|
91
|
+
obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
|
69
92
|
"(#{obj})"
|
70
93
|
when Time
|
71
94
|
obj = "#{obj.strftime('D:%Y%m%d%H%M%S%z').chop.chop}'00'"
|
72
|
-
obj = obj.gsub(/[\\\
|
95
|
+
obj = obj.gsub(/[\\\r()]/, STRING_ESCAPE_MAP)
|
73
96
|
"(#{obj})"
|
74
97
|
when PDF::Core::ByteString
|
75
98
|
"<#{obj.unpack1('H*')}>"
|
@@ -77,18 +100,18 @@ module PDF
|
|
77
100
|
obj = utf8_to_utf16(obj) unless in_content_stream
|
78
101
|
"<#{string_to_hex(obj)}>"
|
79
102
|
when Symbol
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end.join
|
88
|
-
"/#{name_string}"
|
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}"
|
106
|
+
else
|
107
|
+
n.chr
|
108
|
+
end
|
109
|
+
}.join
|
89
110
|
when ::Hash
|
90
111
|
output = +'<< '
|
91
|
-
obj
|
112
|
+
obj
|
113
|
+
.sort_by { |k, _v| k.to_s }
|
114
|
+
.each do |(k, v)|
|
92
115
|
unless k.is_a?(String) || k.is_a?(Symbol)
|
93
116
|
raise PDF::Core::Errors::FailedObjectConversion,
|
94
117
|
'A PDF Dictionary must be keyed by names'
|
@@ -99,12 +122,10 @@ module PDF
|
|
99
122
|
output << '>>'
|
100
123
|
when PDF::Core::Reference
|
101
124
|
obj.to_s
|
102
|
-
when PDF::Core::NameTree::Node
|
125
|
+
when PDF::Core::NameTree::Node, PDF::Core::OutlineRoot, PDF::Core::OutlineItem
|
103
126
|
pdf_object(obj.to_hash)
|
104
127
|
when PDF::Core::NameTree::Value
|
105
128
|
"#{pdf_object(obj.name)} #{pdf_object(obj.value)}"
|
106
|
-
when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
|
107
|
-
pdf_object(obj.to_hash)
|
108
129
|
else
|
109
130
|
raise PDF::Core::Errors::FailedObjectConversion,
|
110
131
|
"This object cannot be serialized to PDF (#{obj.inspect})"
|
data/lib/pdf/core/reference.rb
CHANGED
@@ -1,31 +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
|
26
|
+
|
27
|
+
# Object stream
|
28
|
+
# @return [Stream]
|
29
|
+
attr_accessor :stream
|
15
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.
|
16
33
|
class CannotAttachStream < StandardError
|
34
|
+
# @param message [String] Error message
|
17
35
|
def initialize(message = 'Cannot attach stream to a non-dictionary object')
|
18
36
|
super
|
19
37
|
end
|
20
38
|
end
|
21
39
|
|
40
|
+
# @param id [Integer] Object identifier
|
41
|
+
# @param data [any] Object data
|
22
42
|
def initialize(id, data)
|
23
43
|
@identifier = id
|
24
|
-
@gen
|
25
|
-
@data
|
26
|
-
@stream
|
44
|
+
@gen = 0
|
45
|
+
@data = data
|
46
|
+
@stream = Stream.new
|
27
47
|
end
|
28
48
|
|
49
|
+
# Serialized PDF object
|
50
|
+
#
|
51
|
+
# @return [String]
|
29
52
|
def object
|
30
53
|
output = +"#{@identifier} #{gen} obj\n"
|
31
54
|
if @stream.empty?
|
@@ -38,6 +61,11 @@ module PDF
|
|
38
61
|
output << "endobj\n"
|
39
62
|
end
|
40
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
|
41
69
|
def <<(io)
|
42
70
|
unless @data.is_a?(::Hash)
|
43
71
|
raise CannotAttachStream
|
@@ -46,13 +74,18 @@ module PDF
|
|
46
74
|
(@stream ||= Stream.new) << io
|
47
75
|
end
|
48
76
|
|
77
|
+
# Object reference in PDF format
|
78
|
+
#
|
79
|
+
# @return [String]
|
49
80
|
def to_s
|
50
81
|
"#{@identifier} #{gen} R"
|
51
82
|
end
|
52
83
|
|
53
|
-
# Creates a deep copy of this ref.
|
54
|
-
# given dictionary entries between the old ref and the new.
|
84
|
+
# Creates a deep copy of this ref.
|
55
85
|
#
|
86
|
+
# @param share [Array<Symbol>] a list of dictionary entries to share
|
87
|
+
# between the old ref and the new
|
88
|
+
# @return [Reference]
|
56
89
|
def deep_copy(share = [])
|
57
90
|
r = dup
|
58
91
|
|
@@ -73,8 +106,11 @@ module PDF
|
|
73
106
|
end
|
74
107
|
|
75
108
|
# Replaces the data and stream with that of other_ref.
|
109
|
+
#
|
110
|
+
# @param other_ref [Reference]
|
111
|
+
# @return [void]
|
76
112
|
def replace(other_ref)
|
77
|
-
@data
|
113
|
+
@data = other_ref.data
|
78
114
|
@stream = other_ref.stream
|
79
115
|
end
|
80
116
|
end
|