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