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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/examples/attachments/attachment.rb +7 -8
  4. data/examples/attachments/nested_document.rb +6 -5
  5. data/examples/encryption/encryption.rb +5 -4
  6. data/examples/events/events.rb +7 -6
  7. data/examples/flash/flash.rb +10 -9
  8. data/examples/forms/javascript.rb +14 -13
  9. data/examples/forms/xfa.rb +67 -66
  10. data/examples/javascript/hello_world.rb +6 -5
  11. data/examples/javascript/js_emulation.rb +26 -26
  12. data/examples/loop/goto.rb +12 -11
  13. data/examples/loop/named.rb +17 -16
  14. data/examples/signature/signature.rb +11 -11
  15. data/examples/uri/javascript.rb +25 -24
  16. data/examples/uri/open-uri.rb +5 -4
  17. data/examples/uri/submitform.rb +11 -10
  18. data/lib/origami/3d.rb +330 -334
  19. data/lib/origami/acroform.rb +267 -268
  20. data/lib/origami/actions.rb +266 -278
  21. data/lib/origami/annotations.rb +659 -670
  22. data/lib/origami/array.rb +192 -196
  23. data/lib/origami/boolean.rb +66 -70
  24. data/lib/origami/catalog.rb +360 -363
  25. data/lib/origami/collections.rb +132 -133
  26. data/lib/origami/compound.rb +125 -129
  27. data/lib/origami/destinations.rb +226 -237
  28. data/lib/origami/dictionary.rb +155 -154
  29. data/lib/origami/encryption.rb +967 -923
  30. data/lib/origami/extensions/fdf.rb +270 -275
  31. data/lib/origami/extensions/ppklite.rb +323 -328
  32. data/lib/origami/filespec.rb +170 -173
  33. data/lib/origami/filters/ascii.rb +162 -167
  34. data/lib/origami/filters/ccitt/tables.rb +248 -252
  35. data/lib/origami/filters/ccitt.rb +309 -312
  36. data/lib/origami/filters/crypt.rb +31 -34
  37. data/lib/origami/filters/dct.rb +47 -50
  38. data/lib/origami/filters/flate.rb +57 -60
  39. data/lib/origami/filters/jbig2.rb +50 -53
  40. data/lib/origami/filters/jpx.rb +40 -43
  41. data/lib/origami/filters/lzw.rb +151 -155
  42. data/lib/origami/filters/predictors.rb +250 -255
  43. data/lib/origami/filters/runlength.rb +111 -115
  44. data/lib/origami/filters.rb +319 -325
  45. data/lib/origami/font.rb +173 -177
  46. data/lib/origami/functions.rb +62 -66
  47. data/lib/origami/graphics/colors.rb +203 -208
  48. data/lib/origami/graphics/instruction.rb +79 -81
  49. data/lib/origami/graphics/path.rb +141 -144
  50. data/lib/origami/graphics/patterns.rb +156 -160
  51. data/lib/origami/graphics/render.rb +51 -47
  52. data/lib/origami/graphics/state.rb +144 -142
  53. data/lib/origami/graphics/text.rb +185 -188
  54. data/lib/origami/graphics/xobject.rb +818 -804
  55. data/lib/origami/graphics.rb +25 -26
  56. data/lib/origami/header.rb +63 -65
  57. data/lib/origami/javascript.rb +718 -651
  58. data/lib/origami/linearization.rb +284 -285
  59. data/lib/origami/metadata.rb +156 -135
  60. data/lib/origami/name.rb +98 -100
  61. data/lib/origami/null.rb +49 -51
  62. data/lib/origami/numeric.rb +133 -135
  63. data/lib/origami/obfuscation.rb +180 -182
  64. data/lib/origami/object.rb +634 -631
  65. data/lib/origami/optionalcontent.rb +147 -149
  66. data/lib/origami/outline.rb +46 -48
  67. data/lib/origami/outputintents.rb +76 -77
  68. data/lib/origami/page.rb +637 -596
  69. data/lib/origami/parser.rb +214 -221
  70. data/lib/origami/parsers/fdf.rb +44 -45
  71. data/lib/origami/parsers/pdf/lazy.rb +147 -154
  72. data/lib/origami/parsers/pdf/linear.rb +104 -109
  73. data/lib/origami/parsers/pdf.rb +109 -107
  74. data/lib/origami/parsers/ppklite.rb +44 -46
  75. data/lib/origami/pdf.rb +886 -896
  76. data/lib/origami/reference.rb +116 -120
  77. data/lib/origami/signature.rb +617 -625
  78. data/lib/origami/stream.rb +560 -558
  79. data/lib/origami/string.rb +366 -368
  80. data/lib/origami/template/patterns.rb +50 -52
  81. data/lib/origami/template/widgets.rb +111 -114
  82. data/lib/origami/trailer.rb +153 -157
  83. data/lib/origami/tree.rb +55 -57
  84. data/lib/origami/version.rb +19 -19
  85. data/lib/origami/webcapture.rb +87 -90
  86. data/lib/origami/xfa/config.rb +409 -414
  87. data/lib/origami/xfa/connectionset.rb +113 -117
  88. data/lib/origami/xfa/datasets.rb +38 -42
  89. data/lib/origami/xfa/localeset.rb +33 -37
  90. data/lib/origami/xfa/package.rb +49 -52
  91. data/lib/origami/xfa/pdf.rb +54 -59
  92. data/lib/origami/xfa/signature.rb +33 -37
  93. data/lib/origami/xfa/sourceset.rb +34 -38
  94. data/lib/origami/xfa/stylesheet.rb +35 -39
  95. data/lib/origami/xfa/template.rb +1630 -1634
  96. data/lib/origami/xfa/xdc.rb +33 -37
  97. data/lib/origami/xfa/xfa.rb +132 -123
  98. data/lib/origami/xfa/xfdf.rb +34 -38
  99. data/lib/origami/xfa/xmpmeta.rb +34 -38
  100. data/lib/origami/xfa.rb +50 -53
  101. data/lib/origami/xreftable.rb +462 -462
  102. data/lib/origami.rb +37 -38
  103. data/test/test_actions.rb +22 -20
  104. data/test/test_annotations.rb +54 -52
  105. data/test/test_forms.rb +23 -21
  106. data/test/test_native_types.rb +82 -78
  107. data/test/test_object_tree.rb +25 -24
  108. data/test/test_pages.rb +43 -41
  109. data/test/test_pdf.rb +2 -0
  110. data/test/test_pdf_attachment.rb +23 -21
  111. data/test/test_pdf_create.rb +16 -15
  112. data/test/test_pdf_encrypt.rb +69 -66
  113. data/test/test_pdf_parse.rb +131 -129
  114. data/test/test_pdf_parse_lazy.rb +53 -53
  115. data/test/test_pdf_sign.rb +67 -67
  116. data/test/test_streams.rb +145 -143
  117. data/test/test_xrefs.rb +46 -45
  118. metadata +64 -8
@@ -1,918 +1,932 @@
1
- =begin
2
-
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/>.
18
-
19
- =end
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
- # A class representing a Stream containing the contents of a Page.
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
- super
67
- end
48
+ @instructions.each do |instruction|
49
+ instruction.render(engine)
50
+ end
68
51
 
69
- def instructions
70
- load!
52
+ nil
53
+ end
71
54
 
72
- @instructions
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
- def draw_image(name, attr = {})
76
- load!
61
+ @data = @instructions.join
62
+ end
77
63
 
78
- x, y = attr[:x], attr[:y]
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
- # Draw a polygon from a array of coordinates.
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
- unless coords.size < 1
123
- x,y = coords.slice!(0)
124
- @instructions << PDF::Instruction.new('m',x,y).render(@canvas)
70
+ @instructions
71
+ end
125
72
 
126
- coords.each do |px,py|
127
- @instructions << PDF::Instruction.new('l',px,py).render(@canvas)
128
- end
73
+ def draw_image(name, attr = {})
74
+ load!
129
75
 
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
- )
139
-
140
- i.render(@canvas)
141
- end
76
+ x, y = attr[:x], attr[:y]
142
77
 
143
- self
144
- end
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
- # Draw a rectangle at position (_x_,_y_) with defined _width_ and _height_.
148
- #
149
- def draw_rectangle(x, y, width, height, attr = {})
150
- load!
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
- @instructions << PDF::Instruction.new('re', x,y,width,height).render(@canvas)
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
- @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
- )
138
+ i.render(@canvas)
139
+ end
186
140
 
187
- i.render(@canvas)
141
+ self
142
+ end
188
143
 
189
- self
190
- end
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
- # 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!
199
-
200
- x, y = attr[:x], attr[:y]
201
- font = attr.fetch(:font, DEFAULT_FONT)
202
- size = attr.fetch(:size, DEFAULT_SIZE)
203
- leading = attr.fetch(:leading, DEFAULT_LEADING)
204
- color = attr.fetch(:color, attr.fetch(:fill_color, DEFAULT_STROKE_COLOR))
205
- stroke_color = attr.fetch(:stroke_color, DEFAULT_STROKE_COLOR)
206
- line_width = attr.fetch(:line_width, DEFAULT_LINEWIDTH)
207
- word_spacing = attr.fetch(:word_spacing, @canvas.gs.text_state.word_spacing)
208
- char_spacing = attr.fetch(:char_spacing, @canvas.gs.text_state.char_spacing)
209
- scale = attr.fetch(:scale, @canvas.gs.text_state.scaling)
210
- rise = attr.fetch(:rise, @canvas.gs.text_state.text_rise)
211
- rendering = attr.fetch(:rendering, @canvas.gs.text_state.rendering_mode)
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
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
- set_text_font(font, size)
220
- set_text_pos(x, y) if x or y
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
- def paint_shading(shade)
237
- load!
237
+ @instructions << PDF::Instruction.new('sh', shade).render(@canvas)
238
238
 
239
- @instructions << PDF::Instruction.new('sh', shade).render(@canvas)
239
+ self
240
+ end
240
241
 
241
- self
242
- end
242
+ def set_text_font(fontname, size)
243
+ load!
243
244
 
244
- def set_text_font(fontname, size)
245
- load!
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
- if fontname != @canvas.gs.text_state.font or size != @canvas.gs.text_state.font_size
248
- @instructions << PDF::Instruction.new('Tf', fontname, size).render(@canvas)
249
- end
249
+ self
250
+ end
250
251
 
251
- self
252
- end
252
+ def set_text_pos(tx, ty)
253
+ load!
253
254
 
254
- def set_text_pos(tx,ty)
255
- load!
255
+ @instructions << PDF::Instruction.new('Td', tx, ty).render(@canvas)
256
256
 
257
- @instructions << PDF::Instruction.new('Td', tx, ty).render(@canvas)
257
+ self
258
+ end
258
259
 
259
- self
260
- end
260
+ def set_text_leading(leading)
261
+ load!
261
262
 
262
- def set_text_leading(leading)
263
- load!
263
+ if leading != @canvas.gs.text_state.leading
264
+ @instructions << PDF::Instruction.new('TL', leading).render(@canvas)
265
+ end
264
266
 
265
- if leading != @canvas.gs.text_state.leading
266
- @instructions << PDF::Instruction.new('TL', leading).render(@canvas)
267
- end
267
+ self
268
+ end
268
269
 
269
- self
270
- end
270
+ def set_text_rendering(rendering)
271
+ load!
271
272
 
272
- def set_text_rendering(rendering)
273
- load!
273
+ if rendering != @canvas.gs.text_state.rendering_mode
274
+ @instructions << PDF::Instruction.new('Tr', rendering).render(@canvas)
275
+ end
274
276
 
275
- if rendering != @canvas.gs.text_state.rendering_mode
276
- @instructions << PDF::Instruction.new('Tr', rendering).render(@canvas)
277
- end
277
+ self
278
+ end
278
279
 
279
- self
280
- end
280
+ def set_text_rise(rise)
281
+ load!
281
282
 
282
- def set_text_rise(rise)
283
- load!
283
+ if rise != @canvas.gs.text_state.text_rise
284
+ @instructions << PDF::Instruction.new('Ts', rise).render(@canvas)
285
+ end
284
286
 
285
- if rise != @canvas.gs.text_state.text_rise
286
- @instructions << PDF::Instruction.new('Ts', rise).render(@canvas)
287
- end
287
+ self
288
+ end
288
289
 
289
- self
290
- end
290
+ def set_text_scale(scaling)
291
+ load!
291
292
 
292
- def set_text_scale(scaling)
293
- load!
293
+ if scaling != @canvas.gs.text_state.scaling
294
+ @instructions << PDF::Instruction.new('Tz', scaling).render(@canvas)
295
+ end
294
296
 
295
- if scaling != @canvas.gs.text_state.scaling
296
- @instructions << PDF::Instruction.new('Tz', scaling).render(@canvas)
297
- end
297
+ self
298
+ end
298
299
 
299
- self
300
- end
300
+ def set_text_word_spacing(word_spacing)
301
+ load!
301
302
 
302
- def set_text_word_spacing(word_spacing)
303
- load!
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
- if word_spacing != @canvas.gs.text_state.word_spacing
306
- @instructions << PDF::Instruction.new('Tw', word_spacing).render(@canvas)
307
- end
307
+ self
308
+ end
308
309
 
309
- self
310
- end
310
+ def set_text_char_spacing(char_spacing)
311
+ load!
311
312
 
312
- def set_text_char_spacing(char_spacing)
313
- load!
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
- if char_spacing != @canvas.gs.text_state.char_spacing
316
- @instructions << PDF::Instruction.new('Tc', char_spacing).render(@canvas)
317
- end
317
+ self
318
+ end
318
319
 
319
- self
320
- end
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
- def set_fill_color(color)
323
- load!
324
-
325
- @instructions << ( i =
326
- if (color.respond_to? :r and color.respond_to? :g and color.respond_to? :b) or (color.is_a?(::Array) and color.size == 3)
327
- r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
328
- g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
329
- b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
330
- PDF::Instruction.new('rg', r, g, b) if @canvas.gs.nonstroking_color != [r,g,b]
331
-
332
- 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)
333
- c = (color.respond_to?(:c) ? color.c : color[0]).to_f
334
- m = (color.respond_to?(:m) ? color.m : color[1]).to_f
335
- y = (color.respond_to?(:y) ? color.y : color[2]).to_f
336
- k = (color.respond_to?(:k) ? color.k : color[3]).to_f
337
- PDF::Instruction.new('k', c, m, y, k) if @canvas.gs.nonstroking_color != [c,m,y,k]
338
-
339
- elsif color.respond_to?(:g) or (0.0..1.0).include?(color)
340
- g = color.respond_to?(:g) ? color.g : color
341
- PDF::Instruction.new('g', g) if @canvas.gs.nonstroking_color != [ g ]
342
-
343
- else
344
- raise TypeError, "Invalid color : #{color}"
345
- end
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
- i.render(@canvas) if i
349
- self
350
- end
380
+ def set_dash_pattern(pattern)
381
+ load!
351
382
 
352
- def set_stroke_color(color)
353
- load!
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
- i.render(@canvas) if i
379
- self
380
- end
387
+ self
388
+ end
381
389
 
382
- def set_dash_pattern(pattern)
383
- load!
390
+ def set_line_width(width)
391
+ load!
384
392
 
385
- unless @canvas.gs.dash_pattern.eql? pattern
386
- @instructions << PDF::Instruction.new('d', pattern.array, pattern.phase).render(@canvas)
387
- end
393
+ if @canvas.gs.line_width != width
394
+ @instructions << PDF::Instruction.new('w', width).render(@canvas)
395
+ end
388
396
 
389
- self
390
- end
397
+ self
398
+ end
391
399
 
392
- def set_line_width(width)
393
- load!
400
+ def set_line_cap(cap)
401
+ load!
394
402
 
395
- if @canvas.gs.line_width != width
396
- @instructions << PDF::Instruction.new('w', width).render(@canvas)
397
- end
403
+ if @canvas.gs.line_cap != cap
404
+ @instructions << PDF::Instruction.new('J', cap).render(@canvas)
405
+ end
398
406
 
399
- self
400
- end
407
+ self
408
+ end
401
409
 
402
- def set_line_cap(cap)
403
- load!
410
+ def set_line_join(join)
411
+ load!
404
412
 
405
- if @canvas.gs.line_cap != cap
406
- @instructions << PDF::Instruction.new('J', cap).render(@canvas)
407
- end
413
+ if @canvas.gs.line_join != join
414
+ @instructions << PDF::Instruction.new('j', join).render(@canvas)
415
+ end
408
416
 
409
- self
410
- end
417
+ self
418
+ end
411
419
 
412
- def set_line_join(join)
413
- load!
420
+ private
414
421
 
415
- if @canvas.gs.line_join != join
416
- @instructions << PDF::Instruction.new('j', join).render(@canvas)
417
- end
422
+ def load!
423
+ return unless @instructions.nil?
418
424
 
419
- self
420
- end
425
+ decode!
421
426
 
422
- private
427
+ code = StringScanner.new data
428
+ @instructions = []
423
429
 
424
- def load!
425
- return unless @instructions.nil?
430
+ until code.eos?
431
+ insn = PDF::Instruction.parse(code)
432
+ @instructions << insn if insn
433
+ end
426
434
 
427
- decode!
435
+ self
436
+ end
428
437
 
429
- code = StringScanner.new self.data
430
- @instructions = []
438
+ def write_text_block(text)
439
+ lines = text.split("\n").map! { |line| line.to_s }
431
440
 
432
- until code.eos?
433
- insn = PDF::Instruction.parse(code)
434
- @instructions << insn if insn
435
- end
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
- self
438
- end
448
+ class Page < Dictionary
449
+ def render(engine) # :nodoc:
450
+ contents = self.Contents
451
+ contents = [contents] unless contents.is_a? Array
439
452
 
440
- def write_text_block(text)
441
- lines = text.split("\n").map!{|line| line.to_s}
453
+ contents.each do |stream|
454
+ stream = stream.cast_to(ContentStream) unless stream.is_a? ContentStream
442
455
 
443
- @instructions << PDF::Instruction.new('Tj', lines.slice!(0)).render(@canvas)
444
- lines.each do |line|
445
- @instructions << PDF::Instruction.new("'", line).render(@canvas)
446
- end
447
- end
448
- end #class ContentStream
456
+ stream.render(engine)
457
+ end
458
+ end
449
459
 
450
- class Page < Dictionary
460
+ # TODO :nodoc:
461
+ def draw_image
462
+ raise NotImplementedError
463
+ end
451
464
 
452
- def render(engine) #:nodoc:
453
- contents = self.Contents
454
- contents = [ contents ] unless contents.is_a? Array
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
- contents.each do |stream|
457
- stream = stream.cast_to(ContentStream) unless stream.is_a? ContentStream
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
- stream.render(engine)
460
- end
461
- end
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
- # TODO :nodoc:
464
- def draw_image
465
- raise NotImplementedError
466
- end
483
+ # See ContentStream#write.
484
+ def write(text, attr = {})
485
+ last_content_stream.write(text, attr)
486
+ self
487
+ end
467
488
 
468
- # See ContentStream#draw_line.
469
- def draw_line(from, to, attr = {})
470
- last_content_stream.draw_line(from, to, attr); self
471
- end
489
+ # TODO :nodoc:
490
+ def paint_shading(shade)
491
+ last_content_stream.paint_shading(shade)
492
+ end
472
493
 
473
- # See ContentStream#draw_polygon.
474
- def draw_polygon(coords = [], attr = {})
475
- last_content_stream.draw_polygon(coords, attr); self
476
- end
494
+ # TODO :nodoc:
495
+ def set_text_font(_font, _size)
496
+ raise NotImplementedError
497
+ end
477
498
 
478
- # See ContentStream#draw_rectangle.
479
- def draw_rectangle(x, y, width, height, attr = {})
480
- last_content_stream.draw_rectangle(x, y, width, height, attr); self
481
- end
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
- # See ContentStream#write.
484
- def write(text, attr = {})
485
- last_content_stream.write(text, attr); self
486
- end
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
- # TODO :nodoc:
489
- def paint_shading(shade)
490
- last_content_stream.paint_shading(shade)
491
- end
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
- # TODO :nodoc:
494
- def set_text_font(_font, _size)
495
- raise NotImplementedError
496
- end
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
- # See ContentStream#set_text_pos.
499
- def set_text_pos(tx, ty)
500
- last_content_stream.set_text_pos(tx, ty); self
501
- end
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
- # See ContentStream#set_text_leading.
504
- def set_text_leading(leading)
505
- last_content_stream.set_text_leading(leading); self
506
- end
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
- # See ContentStream#set_text_rendering.
509
- def set_text_rendering(rendering)
510
- last_content_stream.set_text_rendering(rendering); self
511
- end
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
- # See ContentStream#set_text_rise.
514
- def set_text_rise(rise)
515
- last_content_stream.set_text_rise(rise); self
516
- end
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
- # See ContentStream#set_text_scale.
519
- def set_text_scale(scaling)
520
- last_content_stream.set_text_scale(scaling); self
521
- end
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
- # See ContentStream#set_text_word_spacing.
524
- def set_text_word_spacing(word_spacing)
525
- last_content_stream.set_text_word_spacing(word_spacing); self
526
- end
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
- # See ContentStream#set_text_char_spacing.
529
- def set_text_char_spacing(char_spacing)
530
- last_content_stream.set_text_char_spacing(char_spacing); self
531
- end
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
- # See ContentStream#set_fill_color.
534
- def set_fill_color(color)
535
- last_content_stream.set_fill_color(color); self
536
- end
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
- # See ContentStream#set_stroke_color.
539
- def set_stroke_color(color)
540
- last_content_stream.set_stroke_color(color); self
541
- end
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
- # See ContentStream#set_dash_pattern.
544
- def set_dash_pattern(pattern)
545
- last_content_stream.set_dash_pattern(pattern); self
546
- end
577
+ private
547
578
 
548
- # See ContentStream#set_line_width.
549
- def set_line_width(width)
550
- last_content_stream.set_line_width(width); self
551
- end
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
- # See ContentStream#set_line_cap.
554
- def set_line_cap(cap)
555
- last_content_stream.set_line_cap(cap); self
556
- end
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
- # See ContentStream#set_line_join.
559
- def set_line_join(join)
560
- last_content_stream.set_line_join(join); self
561
- end
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
- private
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
- def last_content_stream #:nodoc:
566
- streams = self.content_streams
567
- if streams.empty?
568
- self.Contents = ContentStream.new
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
- streams.last
755
+ raise InvalidColorError,
756
+ "Invalid number of components in ICC profile: #{icc_profile.N}"
571
757
  end
572
- end
573
- end # class Page
574
-
575
- module Graphics
576
-
577
- module XObject
578
- def self.included(receiver)
579
- receiver.field :Type, :Type => Name, :Default => :XObject
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
- module Type
591
- TRANSPARENCY = :Transparency
592
- end
593
-
594
- field :Type, :Type => Name, :Default => :Group
595
- field :S, :Type => Name, :Default => Type::TRANSPARENCY, :Required => true
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
- class Reference < Dictionary
599
- include StandardObject
824
+ raise InvalidColorError, "Invalid base color space" unless cs_base
825
+ palette = ""
600
826
 
601
- field :F, :Type => FileSpec, :Required => true
602
- field :Page, :Type => [ Integer, String ], :Required => true
603
- field :ID, :Type => Array.of(String, length: 2)
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
- field :Subtype, :Type => Name, :Default => :Form, :Required => true
607
- field :FormType, :Type => Integer, :Default => 1
608
- field :BBox, :Type => Rectangle, :Required => true
609
- field :Matrix, :Type => Array.of(Number, length: 6), :Default => [1, 0, 0, 1, 0, 0]
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
- end
630
-
631
- class ImageXObject < Stream
632
- include XObject
633
-
634
- field :Subtype, :Type => Name, :Default => :Image, :Required => true
635
- field :Width, :Type => Integer, :Required => true
636
- field :Height, :Type => Integer, :Required => true
637
- field :ColorSpace, :Type => [ Name, Array ]
638
- field :BitsPerComponent, :Type => Integer
639
- field :Intent, :Type => Name, :Version => "1.1"
640
- field :ImageMask, :Type => Boolean, :Default => false
641
- field :Mask, :Type => [ ImageXObject, Array.of(Integer) ], :Version => "1.3"
642
- field :Decode, :Type => Array.of(Number)
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
- image = ImageXObject.new
666
-
667
- raise ArgumentError, "Missing file format" if format.nil?
668
- case format.downcase
669
- when 'jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi'
670
- image.setFilter :DCTDecode
671
- image.encoded_data = data
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
- end
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
- class ReferenceDictionary < Dictionary
910
- include StandardObject
924
+ class ReferenceDictionary < Dictionary
925
+ include StandardObject
911
926
 
912
- field :F, :Type => Dictionary, :Required => true
913
- field :Page, :Type => [Integer, String], :Required => true
914
- field :ID, :Tyoe => Array
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