origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
-