pdf-core 0.8.1 → 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 +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
|