pdf-core 0.9.0 → 0.10.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/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
|