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