origami 1.2.7 → 2.0.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
- data/CHANGELOG.md +66 -0
- data/README.md +112 -0
- data/bin/config/pdfcop.conf.yml +232 -233
- data/bin/gui/about.rb +27 -37
- data/bin/gui/config.rb +108 -117
- data/bin/gui/file.rb +416 -365
- data/bin/gui/gtkhex.rb +1138 -1153
- data/bin/gui/hexview.rb +55 -57
- data/bin/gui/imgview.rb +48 -51
- data/bin/gui/menu.rb +388 -386
- data/bin/gui/properties.rb +114 -130
- data/bin/gui/signing.rb +571 -617
- data/bin/gui/textview.rb +77 -95
- data/bin/gui/treeview.rb +382 -387
- data/bin/gui/walker.rb +227 -232
- data/bin/gui/xrefs.rb +56 -60
- data/bin/pdf2pdfa +53 -57
- data/bin/pdf2ruby +212 -228
- data/bin/pdfcop +338 -348
- data/bin/pdfdecompress +58 -65
- data/bin/pdfdecrypt +56 -60
- data/bin/pdfencrypt +75 -80
- data/bin/pdfexplode +185 -182
- data/bin/pdfextract +201 -218
- data/bin/pdfmetadata +83 -82
- data/bin/pdfsh +4 -5
- data/bin/pdfwalker +1 -2
- data/bin/shell/.irbrc +45 -82
- data/bin/shell/console.rb +105 -130
- data/bin/shell/hexdump.rb +40 -64
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
- data/examples/flash/flash.rb +37 -0
- data/{samples → examples}/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami.rb +29 -42
- data/lib/origami/3d.rb +350 -225
- data/lib/origami/acroform.rb +262 -288
- data/lib/origami/actions.rb +268 -288
- data/lib/origami/annotations.rb +697 -722
- data/lib/origami/array.rb +258 -184
- data/lib/origami/boolean.rb +74 -84
- data/lib/origami/catalog.rb +397 -434
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/destinations.rb +233 -194
- data/lib/origami/dictionary.rb +253 -232
- data/lib/origami/encryption.rb +1274 -1243
- data/lib/origami/export.rb +232 -268
- data/lib/origami/extensions/fdf.rb +307 -220
- data/lib/origami/extensions/ppklite.rb +368 -435
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters.rb +301 -295
- data/lib/origami/filters/ascii.rb +177 -180
- data/lib/origami/filters/ccitt.rb +528 -535
- data/lib/origami/filters/crypt.rb +26 -35
- data/lib/origami/filters/dct.rb +46 -52
- data/lib/origami/filters/flate.rb +95 -94
- data/lib/origami/filters/jbig2.rb +49 -55
- data/lib/origami/filters/jpx.rb +38 -44
- data/lib/origami/filters/lzw.rb +189 -183
- data/lib/origami/filters/predictors.rb +221 -235
- data/lib/origami/filters/runlength.rb +103 -104
- data/lib/origami/font.rb +173 -186
- data/lib/origami/functions.rb +67 -81
- data/lib/origami/graphics.rb +25 -21
- data/lib/origami/graphics/colors.rb +178 -187
- data/lib/origami/graphics/instruction.rb +79 -85
- data/lib/origami/graphics/path.rb +142 -148
- data/lib/origami/graphics/patterns.rb +160 -167
- data/lib/origami/graphics/render.rb +43 -50
- data/lib/origami/graphics/state.rb +138 -153
- data/lib/origami/graphics/text.rb +188 -205
- data/lib/origami/graphics/xobject.rb +819 -815
- data/lib/origami/header.rb +63 -78
- data/lib/origami/javascript.rb +596 -597
- data/lib/origami/linearization.rb +285 -290
- data/lib/origami/metadata.rb +139 -148
- data/lib/origami/name.rb +112 -148
- data/lib/origami/null.rb +53 -62
- data/lib/origami/numeric.rb +162 -175
- data/lib/origami/obfuscation.rb +186 -174
- data/lib/origami/object.rb +593 -573
- data/lib/origami/outline.rb +42 -47
- data/lib/origami/outputintents.rb +73 -82
- data/lib/origami/page.rb +703 -592
- data/lib/origami/parser.rb +238 -290
- data/lib/origami/parsers/fdf.rb +41 -33
- data/lib/origami/parsers/pdf.rb +75 -95
- data/lib/origami/parsers/pdf/lazy.rb +137 -0
- data/lib/origami/parsers/pdf/linear.rb +64 -66
- data/lib/origami/parsers/ppklite.rb +34 -70
- data/lib/origami/pdf.rb +1030 -1005
- data/lib/origami/reference.rb +102 -102
- data/lib/origami/signature.rb +591 -609
- data/lib/origami/stream.rb +668 -551
- data/lib/origami/string.rb +397 -373
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +144 -158
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +88 -79
- data/lib/origami/xfa.rb +2863 -2882
- data/lib/origami/xreftable.rb +472 -384
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +82 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +90 -0
- data/test/test_pages.rb +31 -0
- data/test/test_pdf.rb +16 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +95 -0
- data/test/test_pdf_parse.rb +96 -0
- data/test/test_pdf_sign.rb +58 -0
- data/test/test_streams.rb +182 -0
- data/test/test_xrefs.rb +67 -0
- metadata +88 -58
- data/README +0 -67
- data/bin/pdf2graph +0 -121
- data/bin/pdfcocoon +0 -104
- data/lib/origami/file.rb +0 -233
- data/samples/README.txt +0 -45
- data/samples/actions/launch/calc.rb +0 -87
- data/samples/actions/launch/winparams.rb +0 -22
- data/samples/actions/loop/loopgoto.rb +0 -24
- data/samples/actions/loop/loopnamed.rb +0 -21
- data/samples/actions/named/named.rb +0 -31
- data/samples/actions/samba/smbrelay.rb +0 -26
- data/samples/actions/webbug/submitform.js +0 -26
- data/samples/actions/webbug/webbug-browser.rb +0 -68
- data/samples/actions/webbug/webbug-js.rb +0 -67
- data/samples/actions/webbug/webbug-reader.rb +0 -90
- data/samples/attachments/attach.rb +0 -40
- data/samples/attachments/attached.txt +0 -1
- data/samples/crypto/crypto.rb +0 -28
- data/samples/digsig/signed.rb +0 -46
- data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
- data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
- data/samples/exploits/exploit_customdictopen.rb +0 -55
- data/samples/exploits/getannots.rb +0 -69
- data/samples/flash/flash.rb +0 -31
- data/samples/javascript/attached.txt +0 -1
- data/samples/javascript/js.rb +0 -52
- data/templates/patterns.rb +0 -66
- data/templates/widgets.rb +0 -173
- data/templates/xdp.rb +0 -92
- data/test/ts_pdf.rb +0 -50
|
@@ -1,904 +1,908 @@
|
|
|
1
1
|
=begin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
GNU Lesser General Public License for more details.
|
|
20
|
-
|
|
21
|
-
You should have received a copy of the GNU Lesser General Public License
|
|
22
|
-
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
|
5
|
+
|
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
Origami is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU Lesser General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
|
23
18
|
|
|
24
19
|
=end
|
|
25
20
|
|
|
26
21
|
module Origami
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
DEFAULT_SIZE = 12
|
|
34
|
-
DEFAULT_FONT = :F1
|
|
35
|
-
DEFAULT_LEADING = 20
|
|
36
|
-
DEFAULT_STROKE_COLOR = Graphics::Color::GrayScale.new(0.0)
|
|
37
|
-
DEFAULT_FILL_COLOR = Graphics::Color::GrayScale.new(1.0)
|
|
38
|
-
DEFAULT_LINECAP = Graphics::LineCapStyle::BUTT_CAP
|
|
39
|
-
DEFAULT_LINEJOIN = Graphics::LineJoinStyle::MITER_JOIN
|
|
40
|
-
DEFAULT_DASHPATTERN = Graphics::DashPattern.new([], 0)
|
|
41
|
-
DEFAULT_LINEWIDTH = 1.0
|
|
42
|
-
|
|
43
|
-
attr_reader :instructions
|
|
44
|
-
attr_accessor :canvas
|
|
45
|
-
|
|
46
|
-
def initialize(rawdata = "", dictionary = {})
|
|
47
|
-
|
|
48
|
-
@instructions = nil
|
|
49
|
-
@canvas = Graphics::DummyCanvas.new
|
|
50
|
-
|
|
51
|
-
super(rawdata, dictionary)
|
|
52
|
-
end
|
|
23
|
+
#
|
|
24
|
+
# A class representing a Stream containing the contents of a Page.
|
|
25
|
+
#
|
|
26
|
+
class ContentStream < Stream
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
|
|
28
|
+
DEFAULT_SIZE = 12
|
|
29
|
+
DEFAULT_FONT = :F1
|
|
30
|
+
DEFAULT_LEADING = 20
|
|
31
|
+
DEFAULT_STROKE_COLOR = Graphics::Color::GrayScale.new(0.0)
|
|
32
|
+
DEFAULT_FILL_COLOR = Graphics::Color::GrayScale.new(1.0)
|
|
33
|
+
DEFAULT_LINECAP = Graphics::LineCapStyle::BUTT_CAP
|
|
34
|
+
DEFAULT_LINEJOIN = Graphics::LineJoinStyle::MITER_JOIN
|
|
35
|
+
DEFAULT_DASHPATTERN = Graphics::DashPattern.new([], 0)
|
|
36
|
+
DEFAULT_LINEWIDTH = 1.0
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
instruction.render(engine)
|
|
59
|
-
end
|
|
38
|
+
attr_accessor :canvas
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
def initialize(data = "", dictionary = {})
|
|
41
|
+
super
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
68
|
-
end
|
|
43
|
+
@instructions = nil
|
|
44
|
+
@canvas = Graphics::DummyCanvas.new
|
|
45
|
+
end
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
super
|
|
73
|
-
end
|
|
47
|
+
def render(engine)
|
|
48
|
+
load! if @instructions.nil?
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
50
|
+
@instructions.each do |instruction|
|
|
51
|
+
instruction.render(engine)
|
|
52
|
+
end
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
80
56
|
|
|
81
|
-
|
|
82
|
-
|
|
57
|
+
def pre_build #:nodoc:
|
|
58
|
+
unless @instructions.nil?
|
|
59
|
+
if @canvas.gs.text_state.is_in_text_object?
|
|
60
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
61
|
+
end
|
|
83
62
|
|
|
84
|
-
|
|
63
|
+
@data = @instructions.join
|
|
64
|
+
end
|
|
85
65
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@instructions << PDF::Instruction.new('Do', name)
|
|
89
|
-
@instructions << PDF::Instruction.new('Q')
|
|
90
|
-
end
|
|
66
|
+
super
|
|
67
|
+
end
|
|
91
68
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
def draw_line(from, to, attr = {})
|
|
96
|
-
draw_polygon([from, to], attr)
|
|
97
|
-
end
|
|
69
|
+
def instructions
|
|
70
|
+
load! if @instructions.nil?
|
|
98
71
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
def draw_polygon(coords = [], attr = {})
|
|
103
|
-
load! if @instructions.nil?
|
|
104
|
-
|
|
105
|
-
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
106
|
-
fill_color = attr[:fill_color] || DEFAULT_FILL_COLOR
|
|
107
|
-
line_cap = attr[:line_cap] || DEFAULT_LINECAP
|
|
108
|
-
line_join = attr[:line_join] || DEFAULT_LINEJOIN
|
|
109
|
-
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
110
|
-
dash_pattern = attr[:dash] || DEFAULT_DASHPATTERN
|
|
111
|
-
|
|
112
|
-
stroke = attr[:stroke].nil? ? true : attr[:stroke]
|
|
113
|
-
fill = attr[:fill].nil? ? false : attr[:fill]
|
|
114
|
-
|
|
115
|
-
stroke = true if fill == false and stroke == false
|
|
116
|
-
|
|
117
|
-
set_fill_color(fill_color) if fill
|
|
118
|
-
set_stroke_color(stroke_color) if stroke
|
|
119
|
-
set_line_width(line_width)
|
|
120
|
-
set_line_cap(line_cap)
|
|
121
|
-
set_line_join(line_join)
|
|
122
|
-
set_dash_pattern(dash_pattern)
|
|
123
|
-
|
|
124
|
-
if @canvas.gs.text_state.is_in_text_object?
|
|
125
|
-
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
unless coords.size < 1
|
|
129
|
-
x,y = coords.slice!(0)
|
|
130
|
-
@instructions << PDF::Instruction.new('m',x,y).render(@canvas)
|
|
131
|
-
|
|
132
|
-
coords.each do |px,py|
|
|
133
|
-
@instructions << PDF::Instruction.new('l',px,py).render(@canvas)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
@instructions << (i =
|
|
137
|
-
if stroke and not fill
|
|
138
|
-
PDF::Instruction.new('s')
|
|
139
|
-
elsif fill and not stroke
|
|
140
|
-
PDF::Instruction.new('f')
|
|
141
|
-
elsif fill and stroke
|
|
142
|
-
PDF::Instruction.new('b')
|
|
143
|
-
end
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
i.render(@canvas)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
self
|
|
150
|
-
end
|
|
72
|
+
@instructions
|
|
73
|
+
end
|
|
151
74
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
#
|
|
155
|
-
def draw_rectangle(x, y, width, height, attr = {})
|
|
156
|
-
load! if @instructions.nil?
|
|
157
|
-
|
|
158
|
-
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
159
|
-
fill_color = attr[:fill_color] || DEFAULT_FILL_COLOR
|
|
160
|
-
line_cap = attr[:line_cap] || DEFAULT_LINECAP
|
|
161
|
-
line_join = attr[:line_join] || DEFAULT_LINEJOIN
|
|
162
|
-
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
163
|
-
dash_pattern = attr[:dash] || DEFAULT_DASHPATTERN
|
|
164
|
-
|
|
165
|
-
stroke = attr[:stroke].nil? ? true : attr[:stroke]
|
|
166
|
-
fill = attr[:fill].nil? ? false : attr[:fill]
|
|
167
|
-
|
|
168
|
-
stroke = true if fill == false and stroke == false
|
|
169
|
-
|
|
170
|
-
set_fill_color(fill_color) if fill
|
|
171
|
-
set_stroke_color(stroke_color) if stroke
|
|
172
|
-
set_line_width(line_width)
|
|
173
|
-
set_line_cap(line_cap)
|
|
174
|
-
set_line_join(line_join)
|
|
175
|
-
set_dash_pattern(dash_pattern)
|
|
176
|
-
|
|
177
|
-
if @canvas.gs.text_state.is_in_text_object?
|
|
178
|
-
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
@instructions << PDF::Instruction.new('re', x,y,width,height).render(@canvas)
|
|
182
|
-
|
|
183
|
-
@instructions << (i =
|
|
184
|
-
if stroke and not fill
|
|
185
|
-
PDF::Instruction.new('S')
|
|
186
|
-
elsif fill and not stroke
|
|
187
|
-
PDF::Instruction.new('f')
|
|
188
|
-
elsif fill and stroke
|
|
189
|
-
PDF::Instruction.new('B')
|
|
190
|
-
end
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
i.render(@canvas)
|
|
194
|
-
|
|
195
|
-
self
|
|
196
|
-
end
|
|
75
|
+
def draw_image(name, attr = {})
|
|
76
|
+
load! if @instructions.nil?
|
|
197
77
|
|
|
198
|
-
|
|
199
|
-
# Adds text to the content stream with custom formatting attributes.
|
|
200
|
-
# _text_:: Text to write.
|
|
201
|
-
# _attr_:: Formatting attributes.
|
|
202
|
-
#
|
|
203
|
-
def write(text, attr = {})
|
|
204
|
-
load! if @instructions.nil?
|
|
205
|
-
|
|
206
|
-
x,y = attr[:x], attr[:y]
|
|
207
|
-
font = attr[:font] || DEFAULT_FONT
|
|
208
|
-
size = attr[:size] || DEFAULT_SIZE
|
|
209
|
-
leading = attr[:leading] || DEFAULT_LEADING
|
|
210
|
-
color = attr[:color] || attr[:fill_color] || DEFAULT_STROKE_COLOR
|
|
211
|
-
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
212
|
-
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
213
|
-
word_spacing = attr[:word_spacing]
|
|
214
|
-
char_spacing = attr[:char_spacing]
|
|
215
|
-
scale = attr[:scale]
|
|
216
|
-
rise = attr[:rise]
|
|
217
|
-
rendering = attr[:rendering]
|
|
218
|
-
|
|
219
|
-
@instructions << PDF::Instruction.new('ET').render(@canvas) if (x or y) and @canvas.gs.text_state.is_in_text_object?
|
|
220
|
-
|
|
221
|
-
unless @canvas.gs.text_state.is_in_text_object?
|
|
222
|
-
@instructions << PDF::Instruction.new('BT').render(@canvas)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
set_text_font(font, size)
|
|
226
|
-
set_text_pos(x, y) if x or y
|
|
227
|
-
set_text_leading(leading) if leading
|
|
228
|
-
set_text_rendering(rendering) if rendering
|
|
229
|
-
set_text_rise(rise) if rise
|
|
230
|
-
set_text_scale(scale) if scale
|
|
231
|
-
set_text_word_spacing(word_spacing) if word_spacing
|
|
232
|
-
set_text_char_spacing(char_spacing) if char_spacing
|
|
233
|
-
set_fill_color(color)
|
|
234
|
-
set_stroke_color(stroke_color)
|
|
235
|
-
set_line_width(line_width)
|
|
236
|
-
|
|
237
|
-
write_text_block(text)
|
|
238
|
-
|
|
239
|
-
self
|
|
240
|
-
end
|
|
78
|
+
x, y = attr[:x], attr[:y]
|
|
241
79
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
80
|
+
@instructions << PDF::Instruction.new('q')
|
|
81
|
+
@instructions << PDF::Instruction.new('cm', 300, 0, 0, 300, x, y)
|
|
82
|
+
@instructions << PDF::Instruction.new('Do', name)
|
|
83
|
+
@instructions << PDF::Instruction.new('Q')
|
|
84
|
+
end
|
|
245
85
|
|
|
246
|
-
|
|
247
|
-
|
|
86
|
+
#
|
|
87
|
+
# Draw a straight line from the point at coord _from_, to the point at coord _to_.
|
|
88
|
+
#
|
|
89
|
+
def draw_line(from, to, attr = {})
|
|
90
|
+
draw_polygon([from, to], attr)
|
|
91
|
+
end
|
|
248
92
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
93
|
+
#
|
|
94
|
+
# Draw a polygon from a array of coordinates.
|
|
95
|
+
#
|
|
96
|
+
def draw_polygon(coords = [], attr = {})
|
|
97
|
+
load! if @instructions.nil?
|
|
98
|
+
|
|
99
|
+
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
100
|
+
fill_color = attr[:fill_color] || DEFAULT_FILL_COLOR
|
|
101
|
+
line_cap = attr[:line_cap] || DEFAULT_LINECAP
|
|
102
|
+
line_join = attr[:line_join] || DEFAULT_LINEJOIN
|
|
103
|
+
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
104
|
+
dash_pattern = attr[:dash] || DEFAULT_DASHPATTERN
|
|
105
|
+
|
|
106
|
+
stroke = attr[:stroke].nil? ? true : attr[:stroke]
|
|
107
|
+
fill = attr[:fill].nil? ? false : attr[:fill]
|
|
108
|
+
|
|
109
|
+
stroke = true if fill == false and stroke == false
|
|
110
|
+
|
|
111
|
+
set_fill_color(fill_color) if fill
|
|
112
|
+
set_stroke_color(stroke_color) if stroke
|
|
113
|
+
set_line_width(line_width)
|
|
114
|
+
set_line_cap(line_cap)
|
|
115
|
+
set_line_join(line_join)
|
|
116
|
+
set_dash_pattern(dash_pattern)
|
|
117
|
+
|
|
118
|
+
if @canvas.gs.text_state.is_in_text_object?
|
|
119
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
120
|
+
end
|
|
254
121
|
|
|
255
|
-
|
|
256
|
-
|
|
122
|
+
unless coords.size < 1
|
|
123
|
+
x,y = coords.slice!(0)
|
|
124
|
+
@instructions << PDF::Instruction.new('m',x,y).render(@canvas)
|
|
257
125
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
self
|
|
263
|
-
end
|
|
126
|
+
coords.each do |px,py|
|
|
127
|
+
@instructions << PDF::Instruction.new('l',px,py).render(@canvas)
|
|
128
|
+
end
|
|
264
129
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
130
|
+
@instructions << (i =
|
|
131
|
+
if stroke and not fill
|
|
132
|
+
PDF::Instruction.new('s')
|
|
133
|
+
elsif fill and not stroke
|
|
134
|
+
PDF::Instruction.new('f')
|
|
135
|
+
elsif fill and stroke
|
|
136
|
+
PDF::Instruction.new('b')
|
|
137
|
+
end
|
|
138
|
+
)
|
|
270
139
|
|
|
271
|
-
|
|
272
|
-
|
|
140
|
+
i.render(@canvas)
|
|
141
|
+
end
|
|
273
142
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if rendering != @canvas.gs.text_state.rendering_mode
|
|
277
|
-
@instructions << PDF::Instruction.new('Tr', rendering).render(@canvas)
|
|
278
|
-
end
|
|
143
|
+
self
|
|
144
|
+
end
|
|
279
145
|
|
|
280
|
-
|
|
281
|
-
|
|
146
|
+
#
|
|
147
|
+
# Draw a rectangle at position (_x_,_y_) with defined _width_ and _height_.
|
|
148
|
+
#
|
|
149
|
+
def draw_rectangle(x, y, width, height, attr = {})
|
|
150
|
+
load! if @instructions.nil?
|
|
151
|
+
|
|
152
|
+
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
153
|
+
fill_color = attr[:fill_color] || DEFAULT_FILL_COLOR
|
|
154
|
+
line_cap = attr[:line_cap] || DEFAULT_LINECAP
|
|
155
|
+
line_join = attr[:line_join] || DEFAULT_LINEJOIN
|
|
156
|
+
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
157
|
+
dash_pattern = attr[:dash] || DEFAULT_DASHPATTERN
|
|
158
|
+
|
|
159
|
+
stroke = attr[:stroke].nil? ? true : attr[:stroke]
|
|
160
|
+
fill = attr[:fill].nil? ? false : attr[:fill]
|
|
161
|
+
|
|
162
|
+
stroke = true if fill == false and stroke == false
|
|
163
|
+
|
|
164
|
+
set_fill_color(fill_color) if fill
|
|
165
|
+
set_stroke_color(stroke_color) if stroke
|
|
166
|
+
set_line_width(line_width)
|
|
167
|
+
set_line_cap(line_cap)
|
|
168
|
+
set_line_join(line_join)
|
|
169
|
+
set_dash_pattern(dash_pattern)
|
|
170
|
+
|
|
171
|
+
if @canvas.gs.text_state.is_in_text_object?
|
|
172
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
|
173
|
+
end
|
|
282
174
|
|
|
283
|
-
|
|
284
|
-
load! if @instructions.nil?
|
|
285
|
-
if rise != @canvas.gs.text_state.text_rise
|
|
286
|
-
@instructions << PDF::Instruction.new('Ts', rise).render(@canvas)
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
self
|
|
290
|
-
end
|
|
175
|
+
@instructions << PDF::Instruction.new('re', x,y,width,height).render(@canvas)
|
|
291
176
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
177
|
+
@instructions << (i =
|
|
178
|
+
if stroke and not fill
|
|
179
|
+
PDF::Instruction.new('S')
|
|
180
|
+
elsif fill and not stroke
|
|
181
|
+
PDF::Instruction.new('f')
|
|
182
|
+
elsif fill and stroke
|
|
183
|
+
PDF::Instruction.new('B')
|
|
184
|
+
end
|
|
185
|
+
)
|
|
297
186
|
|
|
298
|
-
|
|
299
|
-
end
|
|
187
|
+
i.render(@canvas)
|
|
300
188
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if word_spacing != @canvas.gs.text_state.word_spacing
|
|
304
|
-
@instructions << PDF::Instruction.new('Tw', word_spacing).render(@canvas)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
self
|
|
308
|
-
end
|
|
189
|
+
self
|
|
190
|
+
end
|
|
309
191
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
192
|
+
#
|
|
193
|
+
# Adds text to the content stream with custom formatting attributes.
|
|
194
|
+
# _text_:: Text to write.
|
|
195
|
+
# _attr_:: Formatting attributes.
|
|
196
|
+
#
|
|
197
|
+
def write(text, attr = {})
|
|
198
|
+
load! if @instructions.nil?
|
|
199
|
+
|
|
200
|
+
x,y = attr[:x], attr[:y]
|
|
201
|
+
font = attr[:font] || DEFAULT_FONT
|
|
202
|
+
size = attr[:size] || DEFAULT_SIZE
|
|
203
|
+
leading = attr[:leading] || DEFAULT_LEADING
|
|
204
|
+
color = attr[:color] || attr[:fill_color] || DEFAULT_STROKE_COLOR
|
|
205
|
+
stroke_color = attr[:stroke_color] || DEFAULT_STROKE_COLOR
|
|
206
|
+
line_width = attr[:line_width] || DEFAULT_LINEWIDTH
|
|
207
|
+
word_spacing = attr[:word_spacing]
|
|
208
|
+
char_spacing = attr[:char_spacing]
|
|
209
|
+
scale = attr[:scale]
|
|
210
|
+
rise = attr[:rise]
|
|
211
|
+
rendering = attr[:rendering]
|
|
212
|
+
|
|
213
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas) if (x or y) and @canvas.gs.text_state.is_in_text_object?
|
|
214
|
+
|
|
215
|
+
unless @canvas.gs.text_state.is_in_text_object?
|
|
216
|
+
@instructions << PDF::Instruction.new('BT').render(@canvas)
|
|
217
|
+
end
|
|
315
218
|
|
|
316
|
-
|
|
317
|
-
|
|
219
|
+
set_text_font(font, size)
|
|
220
|
+
set_text_pos(x, y) if x or y
|
|
221
|
+
set_text_leading(leading) if leading
|
|
222
|
+
set_text_rendering(rendering) if rendering
|
|
223
|
+
set_text_rise(rise) if rise
|
|
224
|
+
set_text_scale(scale) if scale
|
|
225
|
+
set_text_word_spacing(word_spacing) if word_spacing
|
|
226
|
+
set_text_char_spacing(char_spacing) if char_spacing
|
|
227
|
+
set_fill_color(color)
|
|
228
|
+
set_stroke_color(stroke_color)
|
|
229
|
+
set_line_width(line_width)
|
|
230
|
+
|
|
231
|
+
write_text_block(text)
|
|
232
|
+
|
|
233
|
+
self
|
|
234
|
+
end
|
|
318
235
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
@instructions << ( i =
|
|
323
|
-
if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b) or (color.is_a?(::Array) and color.size == 3)
|
|
324
|
-
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
|
325
|
-
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
|
326
|
-
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
|
327
|
-
PDF::Instruction.new('rg', r, g, b) if @canvas.gs.nonstroking_color != [r,g,b]
|
|
328
|
-
|
|
329
|
-
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
|
330
|
-
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
|
331
|
-
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
|
332
|
-
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
|
333
|
-
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
|
334
|
-
PDF::Instruction.new('k', c, m, y, k) if @canvas.gs.nonstroking_color != [c,m,y,k]
|
|
335
|
-
|
|
336
|
-
elsif color.respond_to?:g or (0.0..1.0) === color
|
|
337
|
-
g = color.respond_to?(:g) ? color.g : color
|
|
338
|
-
PDF::Instruction.new('g', g) if @canvas.gs.nonstroking_color != [ g ]
|
|
339
|
-
|
|
340
|
-
else
|
|
341
|
-
raise TypeError, "Invalid color : #{color}"
|
|
342
|
-
end
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
i.render(@canvas) if i
|
|
346
|
-
self
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
def set_stroke_color(color)
|
|
350
|
-
load! if @instructions.nil?
|
|
351
|
-
|
|
352
|
-
@instructions << ( i =
|
|
353
|
-
if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b) or (color.is_a?(::Array) and color.size == 3)
|
|
354
|
-
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
|
355
|
-
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
|
356
|
-
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
|
357
|
-
PDF::Instruction.new('RG', r, g, b) if @canvas.gs.stroking_color != [r,g,b]
|
|
358
|
-
|
|
359
|
-
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
|
360
|
-
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
|
361
|
-
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
|
362
|
-
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
|
363
|
-
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
|
364
|
-
PDF::Instruction.new('K', c, m, y, k) if @canvas.gs.stroking_color != [c,m,y,k]
|
|
365
|
-
|
|
366
|
-
elsif color.respond_to?:g or (0.0..1.0) === color
|
|
367
|
-
g = color.respond_to?(:g) ? color.g : color
|
|
368
|
-
PDF::Instruction.new('G', g) if @canvas.gs.stroking_color != [ g ]
|
|
369
|
-
|
|
370
|
-
else
|
|
371
|
-
raise TypeError, "Invalid color : #{color}"
|
|
372
|
-
end
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
i.render(@canvas) if i
|
|
376
|
-
self
|
|
377
|
-
end
|
|
236
|
+
def paint_shading(shade)
|
|
237
|
+
load! if @instructions.nil?
|
|
238
|
+
@instructions << PDF::Instruction.new('sh', shade).render(@canvas)
|
|
378
239
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
unless @canvas.gs.dash_pattern.eql? pattern
|
|
382
|
-
@instructions << PDF::Instruction.new('d', pattern.array, pattern.phase).render(@canvas)
|
|
383
|
-
end
|
|
240
|
+
self
|
|
241
|
+
end
|
|
384
242
|
|
|
385
|
-
|
|
386
|
-
|
|
243
|
+
def set_text_font(fontname, size)
|
|
244
|
+
load! if @instructions.nil?
|
|
245
|
+
if fontname != @canvas.gs.text_state.font or size != @canvas.gs.text_state.font_size
|
|
246
|
+
@instructions << PDF::Instruction.new('Tf', fontname, size).render(@canvas)
|
|
247
|
+
end
|
|
387
248
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if @canvas.gs.line_width != width
|
|
391
|
-
@instructions << PDF::Instruction.new('w', width).render(@canvas)
|
|
392
|
-
end
|
|
249
|
+
self
|
|
250
|
+
end
|
|
393
251
|
|
|
394
|
-
|
|
395
|
-
|
|
252
|
+
def set_text_pos(tx,ty)
|
|
253
|
+
load! if @instructions.nil?
|
|
254
|
+
@instructions << PDF::Instruction.new('Td', tx, ty).render(@canvas)
|
|
396
255
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if @canvas.gs.line_cap != cap
|
|
400
|
-
@instructions << PDF::Instruction.new('J', cap).render(@canvas)
|
|
401
|
-
end
|
|
256
|
+
self
|
|
257
|
+
end
|
|
402
258
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if @canvas.gs.line_join != join
|
|
409
|
-
@instructions << PDF::Instruction.new('j', join).render(@canvas)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
self
|
|
413
|
-
end
|
|
259
|
+
def set_text_leading(leading)
|
|
260
|
+
load! if @instructions.nil?
|
|
261
|
+
if leading != @canvas.gs.text_state.leading
|
|
262
|
+
@instructions << PDF::Instruction.new('TL', leading).render(@canvas)
|
|
263
|
+
end
|
|
414
264
|
|
|
415
|
-
|
|
265
|
+
self
|
|
266
|
+
end
|
|
416
267
|
|
|
417
|
-
|
|
418
|
-
|
|
268
|
+
def set_text_rendering(rendering)
|
|
269
|
+
load! if @instructions.nil?
|
|
270
|
+
if rendering != @canvas.gs.text_state.rendering_mode
|
|
271
|
+
@instructions << PDF::Instruction.new('Tr', rendering).render(@canvas)
|
|
272
|
+
end
|
|
419
273
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
until code.eos?
|
|
424
|
-
insn = PDF::Instruction.parse(code)
|
|
425
|
-
@instructions << insn if insn
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
self
|
|
429
|
-
end
|
|
274
|
+
self
|
|
275
|
+
end
|
|
430
276
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
lines.each do |line|
|
|
437
|
-
@instructions << PDF::Instruction.new("'", line).render(@canvas)
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
end #class ContentStream
|
|
277
|
+
def set_text_rise(rise)
|
|
278
|
+
load! if @instructions.nil?
|
|
279
|
+
if rise != @canvas.gs.text_state.text_rise
|
|
280
|
+
@instructions << PDF::Instruction.new('Ts', rise).render(@canvas)
|
|
281
|
+
end
|
|
443
282
|
|
|
444
|
-
|
|
283
|
+
self
|
|
284
|
+
end
|
|
445
285
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
stream = stream.cast_to(ContentStream) unless stream.is_a? ContentStream
|
|
286
|
+
def set_text_scale(scaling)
|
|
287
|
+
load! if @instructions.nil?
|
|
288
|
+
if scale != @canvas.gs.text_state.scaling
|
|
289
|
+
@instructions << PDF::Instruction.new('Tz', scaling).render(@canvas)
|
|
290
|
+
end
|
|
452
291
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
end
|
|
292
|
+
self
|
|
293
|
+
end
|
|
456
294
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
295
|
+
def set_text_word_spacing(word_spacing)
|
|
296
|
+
load! if @instructions.nil?
|
|
297
|
+
if word_spacing != @canvas.gs.text_state.word_spacing
|
|
298
|
+
@instructions << PDF::Instruction.new('Tw', word_spacing).render(@canvas)
|
|
299
|
+
end
|
|
461
300
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
last_content_stream.draw_line(from, to, attr); self
|
|
465
|
-
end
|
|
301
|
+
self
|
|
302
|
+
end
|
|
466
303
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
# See ContentStream#draw_rectangle.
|
|
473
|
-
def draw_rectangle(x, y, width, height, attr = {})
|
|
474
|
-
last_content_stream.draw_rectangle(x, y, width, height, attr); self
|
|
475
|
-
end
|
|
304
|
+
def set_text_char_spacing(char_spacing)
|
|
305
|
+
load! if @instructions.nil?
|
|
306
|
+
if char_spacing != @canvas.gs.text_state.char_spacing
|
|
307
|
+
@instructions << PDF::Instruction.new('Tc', char_spacing).render(@canvas)
|
|
308
|
+
end
|
|
476
309
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
last_content_stream.write(text, attr); self
|
|
480
|
-
end
|
|
310
|
+
self
|
|
311
|
+
end
|
|
481
312
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
raise NotImplementedError
|
|
485
|
-
end
|
|
313
|
+
def set_fill_color(color)
|
|
314
|
+
load! if @instructions.nil?
|
|
486
315
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
316
|
+
@instructions << ( i =
|
|
317
|
+
if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b) or (color.is_a?(::Array) and color.size == 3)
|
|
318
|
+
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
|
319
|
+
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
|
320
|
+
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
|
321
|
+
PDF::Instruction.new('rg', r, g, b) if @canvas.gs.nonstroking_color != [r,g,b]
|
|
491
322
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
323
|
+
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
|
324
|
+
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
|
325
|
+
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
|
326
|
+
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
|
327
|
+
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
|
328
|
+
PDF::Instruction.new('k', c, m, y, k) if @canvas.gs.nonstroking_color != [c,m,y,k]
|
|
496
329
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
end
|
|
330
|
+
elsif color.respond_to?:g or (0.0..1.0) === color
|
|
331
|
+
g = color.respond_to?(:g) ? color.g : color
|
|
332
|
+
PDF::Instruction.new('g', g) if @canvas.gs.nonstroking_color != [ g ]
|
|
501
333
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
334
|
+
else
|
|
335
|
+
raise TypeError, "Invalid color : #{color}"
|
|
336
|
+
end
|
|
337
|
+
)
|
|
506
338
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
end
|
|
339
|
+
i.render(@canvas) if i
|
|
340
|
+
self
|
|
341
|
+
end
|
|
511
342
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
last_content_stream.set_text_scale(scaling); self
|
|
515
|
-
end
|
|
343
|
+
def set_stroke_color(color)
|
|
344
|
+
load! if @instructions.nil?
|
|
516
345
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
346
|
+
@instructions << ( i =
|
|
347
|
+
if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b) or (color.is_a?(::Array) and color.size == 3)
|
|
348
|
+
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
|
349
|
+
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
|
350
|
+
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
|
351
|
+
PDF::Instruction.new('RG', r, g, b) if @canvas.gs.stroking_color != [r,g,b]
|
|
521
352
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
353
|
+
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
|
354
|
+
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
|
355
|
+
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
|
356
|
+
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
|
357
|
+
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
|
358
|
+
PDF::Instruction.new('K', c, m, y, k) if @canvas.gs.stroking_color != [c,m,y,k]
|
|
526
359
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
# See ContentStream#set_stroke_color.
|
|
533
|
-
def set_stroke_color(color)
|
|
534
|
-
last_content_stream.set_stroke_color(color); self
|
|
535
|
-
end
|
|
360
|
+
elsif color.respond_to?:g or (0.0..1.0) === color
|
|
361
|
+
g = color.respond_to?(:g) ? color.g : color
|
|
362
|
+
PDF::Instruction.new('G', g) if @canvas.gs.stroking_color != [ g ]
|
|
536
363
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
364
|
+
else
|
|
365
|
+
raise TypeError, "Invalid color : #{color}"
|
|
366
|
+
end
|
|
367
|
+
)
|
|
541
368
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
end
|
|
369
|
+
i.render(@canvas) if i
|
|
370
|
+
self
|
|
371
|
+
end
|
|
546
372
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
# See ContentStream#set_line_join.
|
|
553
|
-
def set_line_join(join)
|
|
554
|
-
last_content_stream.set_line_join(join); self
|
|
555
|
-
end
|
|
373
|
+
def set_dash_pattern(pattern)
|
|
374
|
+
load! if @instructions.nil?
|
|
375
|
+
unless @canvas.gs.dash_pattern.eql? pattern
|
|
376
|
+
@instructions << PDF::Instruction.new('d', pattern.array, pattern.phase).render(@canvas)
|
|
377
|
+
end
|
|
556
378
|
|
|
557
|
-
|
|
379
|
+
self
|
|
380
|
+
end
|
|
558
381
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
382
|
+
def set_line_width(width)
|
|
383
|
+
load! if @instructions.nil?
|
|
384
|
+
if @canvas.gs.line_width != width
|
|
385
|
+
@instructions << PDF::Instruction.new('w', width).render(@canvas)
|
|
386
|
+
end
|
|
564
387
|
|
|
565
|
-
|
|
388
|
+
self
|
|
389
|
+
end
|
|
566
390
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
391
|
+
def set_line_cap(cap)
|
|
392
|
+
load! if @instructions.nil?
|
|
393
|
+
if @canvas.gs.line_cap != cap
|
|
394
|
+
@instructions << PDF::Instruction.new('J', cap).render(@canvas)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
self
|
|
398
|
+
end
|
|
572
399
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
field :FormType, :Type => Integer, :Default => 1
|
|
579
|
-
field :BBox, :Type => Array, :Required => true
|
|
580
|
-
field :Matrix, :Type => Array, :Default => [1, 0, 0, 1, 0, 0]
|
|
581
|
-
field :Resources, :Type => Resources, :Version => "1.2"
|
|
582
|
-
field :Group, :Type => Dictionary, :Version => "1.4"
|
|
583
|
-
field :Ref, :Type => Dictionary, :Version => "1.4"
|
|
584
|
-
field :Metadata, :Type => Stream, :Version => "1.4"
|
|
585
|
-
field :PieceInfo, :Type => Dictionary, :Version => "1.3"
|
|
586
|
-
field :LastModified, :Type => String, :Version => "1.3"
|
|
587
|
-
field :StructParent, :Type => Integer, :Version => "1.3"
|
|
588
|
-
field :StructParents, :Type => Integer, :Version => "1.3"
|
|
589
|
-
field :OPI, :Type => Dictionary, :Version => "1.2"
|
|
590
|
-
field :OC, :Type => Dictionary, :Version => "1.5"
|
|
591
|
-
field :Name, :Type => Name
|
|
592
|
-
field :Measure, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
593
|
-
field :PtData, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
594
|
-
|
|
595
|
-
def pre_build
|
|
596
|
-
self.Resources = Resources.new.pre_build unless has_field?(:Resources)
|
|
597
|
-
|
|
598
|
-
super
|
|
599
|
-
end
|
|
400
|
+
def set_line_join(join)
|
|
401
|
+
load! if @instructions.nil?
|
|
402
|
+
if @canvas.gs.line_join != join
|
|
403
|
+
@instructions << PDF::Instruction.new('j', join).render(@canvas)
|
|
404
|
+
end
|
|
600
405
|
|
|
601
|
-
|
|
406
|
+
self
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
private
|
|
410
|
+
|
|
411
|
+
def load!
|
|
412
|
+
decode!
|
|
413
|
+
|
|
414
|
+
code = StringScanner.new self.data
|
|
415
|
+
@instructions = []
|
|
416
|
+
|
|
417
|
+
until code.eos?
|
|
418
|
+
insn = PDF::Instruction.parse(code)
|
|
419
|
+
@instructions << insn if insn
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
self
|
|
423
|
+
end
|
|
602
424
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
end
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
425
|
+
def write_text_block(text)
|
|
426
|
+
lines = text.split("\n").map!{|line| line.to_s}
|
|
427
|
+
|
|
428
|
+
@instructions << PDF::Instruction.new('Tj', lines.slice!(0)).render(@canvas)
|
|
429
|
+
lines.each do |line|
|
|
430
|
+
@instructions << PDF::Instruction.new("'", line).render(@canvas)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end #class ContentStream
|
|
434
|
+
|
|
435
|
+
class Page < Dictionary
|
|
436
|
+
|
|
437
|
+
def render(engine) #:nodoc:
|
|
438
|
+
contents = self.Contents
|
|
439
|
+
contents = [ contents ] unless contents.is_a? Array
|
|
440
|
+
|
|
441
|
+
contents.each do |stream|
|
|
442
|
+
stream = stream.cast_to(ContentStream) unless stream.is_a? ContentStream
|
|
443
|
+
|
|
444
|
+
stream.render(engine)
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# TODO :nodoc:
|
|
449
|
+
def draw_image
|
|
450
|
+
raise NotImplementedError
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# See ContentStream#draw_line.
|
|
454
|
+
def draw_line(from, to, attr = {})
|
|
455
|
+
last_content_stream.draw_line(from, to, attr); self
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# See ContentStream#draw_polygon.
|
|
459
|
+
def draw_polygon(coords = [], attr = {})
|
|
460
|
+
last_content_stream.draw_polygon(coords, attr); self
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# See ContentStream#draw_rectangle.
|
|
464
|
+
def draw_rectangle(x, y, width, height, attr = {})
|
|
465
|
+
last_content_stream.draw_rectangle(x, y, width, height, attr); self
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# See ContentStream#write.
|
|
469
|
+
def write(text, attr = {})
|
|
470
|
+
last_content_stream.write(text, attr); self
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# TODO :nodoc:
|
|
474
|
+
def paint_shading(shade)
|
|
475
|
+
raise NotImplementedError
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# TODO :nodoc:
|
|
479
|
+
def set_text_font(font, size)
|
|
480
|
+
raise NotImplementedError
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# See ContentStream#set_text_pos.
|
|
484
|
+
def set_text_pos(tx, ty)
|
|
485
|
+
last_content_stream.set_text_pos(tx, ty); self
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# See ContentStream#set_text_leading.
|
|
489
|
+
def set_text_leading(leading)
|
|
490
|
+
last_content_stream.set_text_leading(leading); self
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# See ContentStream#set_text_rendering.
|
|
494
|
+
def set_text_rendering(rendering)
|
|
495
|
+
last_content_stream.set_text_rendering(rendering); self
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# See ContentStream#set_text_rise.
|
|
499
|
+
def set_text_rise(rise)
|
|
500
|
+
last_content_stream.set_text_rise(rise); self
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# See ContentStream#set_text_scale.
|
|
504
|
+
def set_text_scale(scaling)
|
|
505
|
+
last_content_stream.set_text_scale(scaling); self
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# See ContentStream#set_text_word_spacing.
|
|
509
|
+
def set_text_word_spacing(word_spacing)
|
|
510
|
+
last_content_stream.set_text_word_spacing(word_spacing); self
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# See ContentStream#set_text_char_spacing.
|
|
514
|
+
def set_text_char_spacing(char_spacing)
|
|
515
|
+
last_content_stream.set_text_char_spacing(char_spacing); self
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# See ContentStream#set_fill_color.
|
|
519
|
+
def set_fill_color(color)
|
|
520
|
+
last_content_stream.set_fill_color(color); self
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# See ContentStream#set_stroke_color.
|
|
524
|
+
def set_stroke_color(color)
|
|
525
|
+
last_content_stream.set_stroke_color(color); self
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# See ContentStream#set_dash_pattern.
|
|
529
|
+
def set_dash_pattern(pattern)
|
|
530
|
+
last_content_stream.set_dash_pattern(pattern); self
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# See ContentStream#set_line_width.
|
|
534
|
+
def set_line_width(width)
|
|
535
|
+
last_content_stream.set_line_width(width); self
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# See ContentStream#set_line_cap.
|
|
539
|
+
def set_line_cap(cap)
|
|
540
|
+
last_content_stream.set_line_cap(cap); self
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# See ContentStream#set_line_join.
|
|
544
|
+
def set_line_join(join)
|
|
545
|
+
last_content_stream.set_line_join(join); self
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
private
|
|
549
|
+
|
|
550
|
+
def last_content_stream #:nodoc:
|
|
551
|
+
streams = self.content_streams
|
|
552
|
+
if streams.empty?
|
|
553
|
+
self.Contents = ContentStream.new
|
|
554
|
+
else
|
|
555
|
+
streams.last
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
end # class Page
|
|
559
|
+
|
|
560
|
+
module Graphics
|
|
561
|
+
|
|
562
|
+
module XObject
|
|
563
|
+
def self.included(receiver)
|
|
564
|
+
receiver.field :Type, :Type => Name, :Default => :XObject
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
class FormXObject < ContentStream
|
|
569
|
+
include XObject
|
|
570
|
+
include ResourcesHolder
|
|
571
|
+
|
|
572
|
+
class Group < Dictionary
|
|
573
|
+
include StandardObject
|
|
574
|
+
|
|
575
|
+
module Type
|
|
576
|
+
TRANSPARENCY = :Transparency
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
field :Type, :Type => Name, :Default => :Group
|
|
580
|
+
field :S, :Type => Name, :Default => Type::TRANSPARENCY, :Required => true
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
class Reference < Dictionary
|
|
584
|
+
include StandardObject
|
|
585
|
+
|
|
586
|
+
field :F, :Type => FileSpec, :Required => true
|
|
587
|
+
field :Page, :Type => [ Integer, String ], :Required => true
|
|
588
|
+
field :ID, :Type => Array.of(String, length: 2)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
field :Subtype, :Type => Name, :Default => :Form, :Required => true
|
|
592
|
+
field :FormType, :Type => Integer, :Default => 1
|
|
593
|
+
field :BBox, :Type => Rectangle, :Required => true
|
|
594
|
+
field :Matrix, :Type => Array.of(Number, length: 6), :Default => [1, 0, 0, 1, 0, 0]
|
|
595
|
+
field :Resources, :Type => Resources, :Version => "1.2"
|
|
596
|
+
field :Group, :Type => Group, :Version => "1.4"
|
|
597
|
+
field :Ref, :Type => Reference, :Version => "1.4"
|
|
598
|
+
field :Metadata, :Type => MetadataStream, :Version => "1.4"
|
|
599
|
+
field :PieceInfo, :Type => Dictionary, :Version => "1.3"
|
|
600
|
+
field :LastModified, :Type => String, :Version => "1.3"
|
|
601
|
+
field :StructParent, :Type => Integer, :Version => "1.3"
|
|
602
|
+
field :StructParents, :Type => Integer, :Version => "1.3"
|
|
603
|
+
field :OPI, :Type => Dictionary, :Version => "1.2"
|
|
604
|
+
field :OC, :Type => Dictionary, :Version => "1.5"
|
|
605
|
+
field :Name, :Type => Name
|
|
606
|
+
field :Measure, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
607
|
+
field :PtData, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
608
|
+
|
|
609
|
+
def pre_build
|
|
610
|
+
self.Resources = Resources.new.pre_build unless has_field?(:Resources)
|
|
611
|
+
|
|
612
|
+
super
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
class ImageXObject < Stream
|
|
617
|
+
include XObject
|
|
618
|
+
|
|
619
|
+
field :Subtype, :Type => Name, :Default => :Image, :Required => true
|
|
620
|
+
field :Width, :Type => Integer, :Required => true
|
|
621
|
+
field :Height, :Type => Integer, :Required => true
|
|
622
|
+
field :ColorSpace, :Type => [ Name, Array ]
|
|
623
|
+
field :BitsPerComponent, :Type => Integer
|
|
624
|
+
field :Intent, :Type => Name, :Version => "1.1"
|
|
625
|
+
field :ImageMask, :Type => Boolean, :Default => false
|
|
626
|
+
field :Mask, :Type => [ ImageXObject, Array.of(Integer) ], :Version => "1.3"
|
|
627
|
+
field :Decode, :Type => Array.of(Number)
|
|
628
|
+
field :Interpolate, :Type => Boolean, :Default => false
|
|
629
|
+
field :Alternates, :Type => Array, :Version => "1.3"
|
|
630
|
+
field :SMask, :Type => ImageXObject, :Version => "1.4"
|
|
631
|
+
field :SMaskInData, :Type => Integer, :Default => 0, :Version => "1.5"
|
|
632
|
+
field :Name, :Type => Name
|
|
633
|
+
field :StructParent, :Type => Integer, :Version => "1.3"
|
|
634
|
+
field :ID, :Type => String, :Version => "1.3"
|
|
635
|
+
field :OPI, :Type => Dictionary, :Version => "1.2"
|
|
636
|
+
field :Matte, :Type => Array.of(Number), :Version => "1.4" # Used in Soft-Mask images.
|
|
637
|
+
field :Metadata, :Type => MetadataStream, :Version => "1.4"
|
|
638
|
+
field :OC, :Type => Dictionary, :Version => "1.5"
|
|
639
|
+
field :Measure, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
640
|
+
field :PtData, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
|
641
|
+
|
|
642
|
+
def self.from_image_file(path, format = nil)
|
|
643
|
+
if path.respond_to?(:read)
|
|
644
|
+
data = fd.read
|
|
645
|
+
else
|
|
646
|
+
data = File.binread(File.expand_path(path))
|
|
647
|
+
format ||= File.extname(path)
|
|
648
|
+
format.slice!(0) if format and format[0,1] == '.'
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
image = ImageXObject.new
|
|
652
|
+
|
|
653
|
+
raise ArgumentError, "Missing file format" if format.nil?
|
|
654
|
+
case format.downcase
|
|
655
|
+
when 'jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi'
|
|
656
|
+
image.setFilter :DCTDecode
|
|
657
|
+
image.encoded_data = data
|
|
658
|
+
|
|
659
|
+
image
|
|
660
|
+
|
|
661
|
+
when 'jp2','jpx','j2k','jpf','jpm','mj2'
|
|
662
|
+
image.setFilter :JPXDecode
|
|
663
|
+
image.encoded_data = data
|
|
664
|
+
|
|
665
|
+
image
|
|
666
|
+
|
|
667
|
+
when 'jb2', 'jbig', 'jbig2'
|
|
668
|
+
image.setFilter :JBIG2Decode
|
|
669
|
+
image.encoded_data = data
|
|
670
|
+
|
|
671
|
+
image
|
|
672
|
+
else
|
|
673
|
+
raise NotImplementedError, "Unknown file format: '#{format}'"
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
#
|
|
678
|
+
# Converts an ImageXObject stream into an image file data.
|
|
679
|
+
# Output format depends on the stream encoding:
|
|
680
|
+
# * JPEG for DCTDecode
|
|
681
|
+
# * JPEG2000 for JPXDecode
|
|
682
|
+
# * JBIG2 for JBIG2Decode
|
|
683
|
+
# * PNG for everything else
|
|
684
|
+
#
|
|
685
|
+
# Returns an array of the form [ _format_, _data_ ]
|
|
686
|
+
#
|
|
687
|
+
def to_image_file
|
|
688
|
+
encoding = self.Filter
|
|
689
|
+
encoding = encoding[0] if encoding.is_a? ::Array
|
|
690
|
+
|
|
691
|
+
case (encoding && encoding.value)
|
|
692
|
+
when :DCTDecode then return [ 'jpg', self.data ]
|
|
693
|
+
when :JBIG2Decode then return [ 'jbig2', self.data ]
|
|
694
|
+
when :JPXDecode then return [ 'jp2', self.data ]
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Assume PNG data.
|
|
698
|
+
|
|
699
|
+
raise InvalidColorError, "No colorspace specified" unless self.ColorSpace
|
|
700
|
+
|
|
701
|
+
case cs = self.ColorSpace.value
|
|
702
|
+
when Color::Space::DEVICE_GRAY
|
|
703
|
+
colortype = 0
|
|
704
|
+
components = 1
|
|
705
|
+
when Color::Space::DEVICE_RGB
|
|
706
|
+
colortype = 2
|
|
707
|
+
components = 3
|
|
708
|
+
when ::Array
|
|
709
|
+
cstype = cs[0].is_a?(Reference) ? cs[0].solve : cs[0]
|
|
710
|
+
case cstype.value
|
|
711
|
+
when :Indexed
|
|
712
|
+
colortype = 3
|
|
713
|
+
components = 3
|
|
714
|
+
csbase = cs[1].is_a?(Reference) ? cs[1].solve : cs[1]
|
|
715
|
+
lookup = cs[3].is_a?(Reference) ? cs[3].solve : cs[3]
|
|
716
|
+
|
|
717
|
+
when :ICCBased
|
|
718
|
+
iccprofile = cs[1].is_a?(Reference) ? cs[1].solve : cs[1]
|
|
719
|
+
raise InvalidColorError,
|
|
720
|
+
"Invalid ICC Profile parameter" unless iccprofile.is_a?(Stream)
|
|
721
|
+
|
|
722
|
+
case iccprofile.N
|
|
723
|
+
when 1
|
|
724
|
+
colortype = 0
|
|
725
|
+
components = 1
|
|
726
|
+
when 3
|
|
727
|
+
colortype = 2
|
|
728
|
+
components = 3
|
|
729
|
+
else
|
|
730
|
+
raise InvalidColorError,
|
|
731
|
+
"Invalid number of components in ICC profile: #{iccprofile.N}"
|
|
732
|
+
end
|
|
733
|
+
else
|
|
734
|
+
raise InvalidColorError, "Unsupported color space: #{self.ColorSpace}"
|
|
735
|
+
end
|
|
736
|
+
else
|
|
737
|
+
raise InvalidColorError, "Unsupported color space: #{self.ColorSpace}"
|
|
807
738
|
end
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
739
|
+
|
|
740
|
+
bpc = self.BitsPerComponent || 8
|
|
741
|
+
w,h = self.Width, self.Height
|
|
742
|
+
pixels = self.data
|
|
743
|
+
|
|
744
|
+
hdr = [137, 80, 78, 71, 13, 10, 26, 10].pack('C*')
|
|
745
|
+
chunks = []
|
|
746
|
+
|
|
747
|
+
chunks <<
|
|
748
|
+
[
|
|
749
|
+
'IHDR',
|
|
750
|
+
[
|
|
751
|
+
w, h,
|
|
752
|
+
bpc, colortype, 0, 0, 0
|
|
753
|
+
].pack("N2C5")
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
if self.Intents
|
|
758
|
+
intents =
|
|
759
|
+
case self.Intents.value
|
|
760
|
+
when Intents::PERCEPTUAL then 0
|
|
761
|
+
when Intents::RELATIVE then 1
|
|
762
|
+
when Intents::SATURATION then 2
|
|
763
|
+
when Intents::ABSOLUTE then 3
|
|
764
|
+
else
|
|
765
|
+
3
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
chunks <<
|
|
769
|
+
[
|
|
770
|
+
'sRGB',
|
|
771
|
+
[ intents ].pack('C')
|
|
772
|
+
]
|
|
773
|
+
|
|
774
|
+
chunks << [ 'gAMA', [ 45455 ].pack("N") ]
|
|
775
|
+
chunks <<
|
|
776
|
+
[
|
|
777
|
+
'cHRM',
|
|
778
|
+
[
|
|
779
|
+
31270,
|
|
780
|
+
32900,
|
|
781
|
+
64000,
|
|
782
|
+
33000,
|
|
783
|
+
30000,
|
|
784
|
+
60000,
|
|
785
|
+
15000,
|
|
786
|
+
6000
|
|
787
|
+
].pack("N8")
|
|
788
|
+
]
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
if colortype == 3
|
|
792
|
+
lookup =
|
|
793
|
+
case lookup
|
|
794
|
+
when Stream then lookup.data
|
|
795
|
+
when String then lookup.value
|
|
796
|
+
else
|
|
797
|
+
raise InvalidColorError, "Invalid indexed palette table"
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
raise InvalidColorError, "Invalid base color space" unless csbase
|
|
801
|
+
palette = ""
|
|
802
|
+
|
|
803
|
+
case csbase.value
|
|
804
|
+
when Color::Space::DEVICE_GRAY
|
|
817
805
|
lookup.each_byte do |g|
|
|
818
|
-
|
|
806
|
+
palette << Color.gray_to_rgb(g).pack("C3")
|
|
819
807
|
end
|
|
820
|
-
|
|
808
|
+
when Color::Space::DEVICE_RGB
|
|
821
809
|
palette << lookup[0, (lookup.size / 3) * 3]
|
|
810
|
+
|
|
811
|
+
when Color::Space::DEVICE_CMYK
|
|
812
|
+
(lookup.size / 4).times do |i|
|
|
813
|
+
cmyk = lookup[i * 4, 4].unpack("C4").map!{|c| c.to_f / 255}
|
|
814
|
+
palette << Color.cmyk_to_rgb(*cmyk).map!{|c| (c * 255).to_i}.pack("C3")
|
|
815
|
+
end
|
|
816
|
+
when ::Array
|
|
817
|
+
|
|
818
|
+
case csbase[0].solve.value
|
|
819
|
+
when :ICCBased
|
|
820
|
+
iccprofile = csbase[1].solve
|
|
821
|
+
raise InvalidColorError,
|
|
822
|
+
"Invalid ICC Profile parameter" unless iccprofile.is_a?(Stream)
|
|
823
|
+
|
|
824
|
+
case iccprofile.N
|
|
825
|
+
when 1
|
|
826
|
+
lookup.each_byte do |g|
|
|
827
|
+
palette << Color.gray_to_rgb(g).pack("C3")
|
|
828
|
+
end
|
|
829
|
+
when 3
|
|
830
|
+
palette << lookup[0, (lookup.size / 3) * 3]
|
|
831
|
+
else
|
|
832
|
+
raise InvalidColorError,
|
|
833
|
+
"Invalid number of components in ICC profile: #{iccprofile.N}"
|
|
834
|
+
end
|
|
835
|
+
else
|
|
836
|
+
raise InvalidColorError, "Unsupported color space: #{csbase}"
|
|
837
|
+
end
|
|
822
838
|
else
|
|
823
|
-
|
|
824
|
-
"Invalid number of components in ICC profile: #{iccprofile.N}"
|
|
839
|
+
raise InvalidColorError, "Unsupported color space: #{csbase}"
|
|
825
840
|
end
|
|
826
|
-
|
|
827
|
-
|
|
841
|
+
|
|
842
|
+
if iccprofile
|
|
843
|
+
chunks <<
|
|
844
|
+
[
|
|
845
|
+
'iCCP',
|
|
846
|
+
'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(iccprofile.data, Zlib::BEST_COMPRESSION)
|
|
847
|
+
]
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
chunks <<
|
|
851
|
+
[
|
|
852
|
+
'PLTE',
|
|
853
|
+
palette
|
|
854
|
+
]
|
|
855
|
+
|
|
856
|
+
bpr = w
|
|
857
|
+
|
|
858
|
+
else # colortype != 3
|
|
859
|
+
if iccprofile
|
|
860
|
+
chunks <<
|
|
861
|
+
[
|
|
862
|
+
'iCCP',
|
|
863
|
+
'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(iccprofile.data, Zlib::BEST_COMPRESSION)
|
|
864
|
+
]
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
bpr = (bpc >> 3) * components * w
|
|
828
868
|
end
|
|
829
|
-
else
|
|
830
|
-
raise InvalidColorError, "Unsupported color space: #{csbase}"
|
|
831
|
-
end
|
|
832
869
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(iccprofile.data, Zlib::BEST_COMPRESSION)
|
|
838
|
-
]
|
|
839
|
-
end
|
|
870
|
+
nrows = pixels.size / bpr
|
|
871
|
+
nrows.times do |irow|
|
|
872
|
+
pixels.insert(irow * bpr + irow, "\x00")
|
|
873
|
+
end
|
|
840
874
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(iccprofile.data, Zlib::BEST_COMPRESSION)
|
|
855
|
-
]
|
|
856
|
-
end
|
|
875
|
+
chunks <<
|
|
876
|
+
[
|
|
877
|
+
'IDAT',
|
|
878
|
+
Zlib::Deflate.deflate(pixels, Zlib::BEST_COMPRESSION)
|
|
879
|
+
]
|
|
880
|
+
|
|
881
|
+
if self.Metadata.is_a?(Stream)
|
|
882
|
+
chunks <<
|
|
883
|
+
[
|
|
884
|
+
'tEXt',
|
|
885
|
+
"XML:com.adobe.xmp" + "\x00" + self.Metadata.data
|
|
886
|
+
]
|
|
887
|
+
end
|
|
857
888
|
|
|
858
|
-
|
|
859
|
-
end
|
|
860
|
-
|
|
861
|
-
require 'zlib'
|
|
862
|
-
|
|
863
|
-
nrows = pixels.size / bpr
|
|
864
|
-
nrows.times do |irow|
|
|
865
|
-
pixels.insert(irow * bpr + irow, "\x00")
|
|
866
|
-
end
|
|
867
|
-
|
|
868
|
-
chunks <<
|
|
869
|
-
[
|
|
870
|
-
'IDAT',
|
|
871
|
-
Zlib::Deflate.deflate(pixels, Zlib::BEST_COMPRESSION)
|
|
872
|
-
]
|
|
873
|
-
|
|
874
|
-
if self.Metadata.is_a?(Stream)
|
|
875
|
-
chunks <<
|
|
876
|
-
[
|
|
877
|
-
'tEXt',
|
|
878
|
-
"XML:com.adobe.xmp" + "\x00" + self.Metadata.data
|
|
879
|
-
]
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
chunks << [ 'IEND', '' ]
|
|
883
|
-
|
|
884
|
-
[ 'png',
|
|
885
|
-
hdr + chunks.map!{ |chk|
|
|
886
|
-
[ chk[1].size, chk[0], chk[1], Zlib.crc32(chk[0] + chk[1]) ].pack("NA4A*N")
|
|
887
|
-
}.join
|
|
888
|
-
]
|
|
889
|
-
end
|
|
890
|
-
end
|
|
891
|
-
end
|
|
889
|
+
chunks << [ 'IEND', '' ]
|
|
892
890
|
|
|
893
|
-
|
|
894
|
-
|
|
891
|
+
[ 'png',
|
|
892
|
+
hdr + chunks.map!{ |chk|
|
|
893
|
+
[ chk[1].size, chk[0], chk[1], Zlib.crc32(chk[0] + chk[1]) ].pack("NA4A*N")
|
|
894
|
+
}.join
|
|
895
|
+
]
|
|
896
|
+
end
|
|
897
|
+
end
|
|
895
898
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
field :ID, :Tyoe => Array
|
|
899
|
-
end
|
|
899
|
+
class ReferenceDictionary < Dictionary
|
|
900
|
+
include StandardObject
|
|
900
901
|
|
|
901
|
-
|
|
902
|
+
field :F, :Type => Dictionary, :Required => true
|
|
903
|
+
field :Page, :Type => [Integer, String], :Required => true
|
|
904
|
+
field :ID, :Tyoe => Array
|
|
905
|
+
end
|
|
906
|
+
end
|
|
902
907
|
|
|
903
908
|
end
|
|
904
|
-
|