origami 1.2.7 → 2.0.0

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