eideticpdf 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. data/fonts/AntiqueOlive-Bold.afm +406 -0
  2. data/fonts/AntiqueOlive-Bold.inf +27 -0
  3. data/fonts/AntiqueOlive-Compact.afm +401 -0
  4. data/fonts/AntiqueOlive-Compact.inf +27 -0
  5. data/fonts/AntiqueOlive-Italic.afm +401 -0
  6. data/fonts/AntiqueOlive-Italic.inf +27 -0
  7. data/fonts/AntiqueOlive-Roman.afm +409 -0
  8. data/fonts/AntiqueOlive-Roman.inf +27 -0
  9. data/fonts/AvantGarde-Book.afm +667 -0
  10. data/fonts/AvantGarde-Book.inf +26 -0
  11. data/fonts/AvantGarde-BookOblique.afm +667 -0
  12. data/fonts/AvantGarde-BookOblique.inf +26 -0
  13. data/fonts/AvantGarde-Demi.afm +673 -0
  14. data/fonts/AvantGarde-Demi.inf +26 -0
  15. data/fonts/AvantGarde-DemiOblique.afm +673 -0
  16. data/fonts/AvantGarde-DemiOblique.inf +26 -0
  17. data/fonts/Bookman-Demi.afm +669 -0
  18. data/fonts/Bookman-Demi.inf +25 -0
  19. data/fonts/Bookman-DemiItalic.afm +669 -0
  20. data/fonts/Bookman-DemiItalic.inf +25 -0
  21. data/fonts/Bookman-Light.afm +643 -0
  22. data/fonts/Bookman-Light.inf +25 -0
  23. data/fonts/Bookman-LightItalic.afm +620 -0
  24. data/fonts/Bookman-LightItalic.inf +25 -0
  25. data/fonts/Clarendon-Bold.afm +412 -0
  26. data/fonts/Clarendon-Bold.inf +27 -0
  27. data/fonts/Clarendon-Light.afm +410 -0
  28. data/fonts/Clarendon-Light.inf +27 -0
  29. data/fonts/Clarendon.afm +410 -0
  30. data/fonts/Clarendon.inf +27 -0
  31. data/fonts/CooperBlack-Italic.afm +421 -0
  32. data/fonts/CooperBlack-Italic.inf +26 -0
  33. data/fonts/CooperBlack.afm +427 -0
  34. data/fonts/CooperBlack.inf +26 -0
  35. data/fonts/Coronet-Regular.afm +327 -0
  36. data/fonts/Coronet-Regular.inf +25 -0
  37. data/fonts/Courier-Bold.afm +342 -0
  38. data/fonts/Courier-Bold.inf +26 -0
  39. data/fonts/Courier-BoldOblique.afm +342 -0
  40. data/fonts/Courier-BoldOblique.inf +26 -0
  41. data/fonts/Courier-Oblique.afm +342 -0
  42. data/fonts/Courier-Oblique.inf +26 -0
  43. data/fonts/Courier.afm +342 -0
  44. data/fonts/Courier.inf +26 -0
  45. data/fonts/Eurostile.afm +419 -0
  46. data/fonts/Eurostile.inf +27 -0
  47. data/fonts/Goudy-ExtraBold.afm +412 -0
  48. data/fonts/Goudy-ExtraBold.inf +27 -0
  49. data/fonts/Helvetica-Bold.afm +2827 -0
  50. data/fonts/Helvetica-Bold.inf +26 -0
  51. data/fonts/Helvetica-BoldOblique.afm +2827 -0
  52. data/fonts/Helvetica-BoldOblique.inf +26 -0
  53. data/fonts/Helvetica-Condensed-Bold.afm +623 -0
  54. data/fonts/Helvetica-Condensed-Bold.inf +25 -0
  55. data/fonts/Helvetica-Condensed-BoldObl.afm +623 -0
  56. data/fonts/Helvetica-Condensed-BoldObl.inf +25 -0
  57. data/fonts/Helvetica-Condensed-Oblique.afm +528 -0
  58. data/fonts/Helvetica-Condensed-Oblique.inf +25 -0
  59. data/fonts/Helvetica-Condensed.afm +528 -0
  60. data/fonts/Helvetica-Condensed.inf +25 -0
  61. data/fonts/Helvetica-Narrow-Bold.afm +1439 -0
  62. data/fonts/Helvetica-Narrow-Bold.inf +26 -0
  63. data/fonts/Helvetica-Narrow-BoldOblique.afm +1439 -0
  64. data/fonts/Helvetica-Narrow-BoldOblique.inf +26 -0
  65. data/fonts/Helvetica-Narrow-Oblique.afm +1556 -0
  66. data/fonts/Helvetica-Narrow-Oblique.inf +26 -0
  67. data/fonts/Helvetica-Narrow.afm +1556 -0
  68. data/fonts/Helvetica-Narrow.inf +26 -0
  69. data/fonts/Helvetica-Oblique.afm +3051 -0
  70. data/fonts/Helvetica-Oblique.inf +26 -0
  71. data/fonts/Helvetica.afm +3051 -0
  72. data/fonts/Helvetica.inf +26 -0
  73. data/fonts/LetterGothic-Bold.afm +443 -0
  74. data/fonts/LetterGothic-Bold.inf +26 -0
  75. data/fonts/LetterGothic-BoldSlanted.afm +443 -0
  76. data/fonts/LetterGothic-BoldSlanted.inf +26 -0
  77. data/fonts/LetterGothic-Slanted.afm +443 -0
  78. data/fonts/LetterGothic-Slanted.inf +26 -0
  79. data/fonts/LetterGothic.afm +443 -0
  80. data/fonts/LetterGothic.inf +26 -0
  81. data/fonts/LubalinGraph-Book.afm +351 -0
  82. data/fonts/LubalinGraph-Book.inf +25 -0
  83. data/fonts/LubalinGraph-BookOblique.afm +351 -0
  84. data/fonts/LubalinGraph-BookOblique.inf +25 -0
  85. data/fonts/LubalinGraph-Demi.afm +351 -0
  86. data/fonts/LubalinGraph-Demi.inf +25 -0
  87. data/fonts/LubalinGraph-DemiOblique.afm +351 -0
  88. data/fonts/LubalinGraph-DemiOblique.inf +25 -0
  89. data/fonts/Marigold.afm +491 -0
  90. data/fonts/Marigold.inf +27 -0
  91. data/fonts/MonaLisa-Recut.afm +663 -0
  92. data/fonts/MonaLisa-Recut.inf +27 -0
  93. data/fonts/NewCenturySchlbk-Bold.afm +886 -0
  94. data/fonts/NewCenturySchlbk-Bold.inf +25 -0
  95. data/fonts/NewCenturySchlbk-BoldItalic.afm +1521 -0
  96. data/fonts/NewCenturySchlbk-BoldItalic.inf +26 -0
  97. data/fonts/NewCenturySchlbk-Italic.afm +1113 -0
  98. data/fonts/NewCenturySchlbk-Italic.inf +26 -0
  99. data/fonts/NewCenturySchlbk-Roman.afm +1067 -0
  100. data/fonts/NewCenturySchlbk-Roman.inf +26 -0
  101. data/fonts/Optima-Bold.afm +515 -0
  102. data/fonts/Optima-Bold.inf +27 -0
  103. data/fonts/Optima-BoldItalic.afm +712 -0
  104. data/fonts/Optima-BoldItalic.inf +26 -0
  105. data/fonts/Optima-Italic.afm +737 -0
  106. data/fonts/Optima-Italic.inf +26 -0
  107. data/fonts/Optima.afm +501 -0
  108. data/fonts/Optima.inf +27 -0
  109. data/fonts/Palatino-Bold.afm +675 -0
  110. data/fonts/Palatino-Bold.inf +26 -0
  111. data/fonts/Palatino-BoldItalic.afm +702 -0
  112. data/fonts/Palatino-BoldItalic.inf +26 -0
  113. data/fonts/Palatino-Italic.afm +700 -0
  114. data/fonts/Palatino-Italic.inf +26 -0
  115. data/fonts/Palatino-Roman.afm +718 -0
  116. data/fonts/Palatino-Roman.inf +26 -0
  117. data/fonts/StempelGaramond-Bold.afm +412 -0
  118. data/fonts/StempelGaramond-Bold.inf +27 -0
  119. data/fonts/StempelGaramond-BoldItalic.afm +413 -0
  120. data/fonts/StempelGaramond-BoldItalic.inf +27 -0
  121. data/fonts/StempelGaramond-Italic.afm +413 -0
  122. data/fonts/StempelGaramond-Italic.inf +27 -0
  123. data/fonts/StempelGaramond-Roman.afm +412 -0
  124. data/fonts/StempelGaramond-Roman.inf +27 -0
  125. data/fonts/Symbol.afm +213 -0
  126. data/fonts/Symbol.inf +27 -0
  127. data/fonts/Times-Bold.afm +2588 -0
  128. data/fonts/Times-Bold.inf +26 -0
  129. data/fonts/Times-BoldItalic.afm +2384 -0
  130. data/fonts/Times-BoldItalic.inf +26 -0
  131. data/fonts/Times-Italic.afm +2667 -0
  132. data/fonts/Times-Italic.inf +26 -0
  133. data/fonts/Times-Roman.afm +2419 -0
  134. data/fonts/Times-Roman.inf +26 -0
  135. data/fonts/Univers-Bold.afm +712 -0
  136. data/fonts/Univers-Bold.inf +27 -0
  137. data/fonts/Univers-BoldOblique.afm +712 -0
  138. data/fonts/Univers-BoldOblique.inf +27 -0
  139. data/fonts/Univers-Condensed.afm +403 -0
  140. data/fonts/Univers-Condensed.inf +27 -0
  141. data/fonts/Univers-CondensedOblique.afm +403 -0
  142. data/fonts/Univers-CondensedOblique.inf +27 -0
  143. data/fonts/Univers-Light.afm +737 -0
  144. data/fonts/Univers-Light.inf +27 -0
  145. data/fonts/Univers-LightOblique.afm +737 -0
  146. data/fonts/Univers-LightOblique.inf +27 -0
  147. data/fonts/Univers-Oblique.afm +656 -0
  148. data/fonts/Univers-Oblique.inf +27 -0
  149. data/fonts/Univers.afm +656 -0
  150. data/fonts/Univers.inf +27 -0
  151. data/fonts/ZapfChancery-MediumItalic.afm +842 -0
  152. data/fonts/ZapfChancery-MediumItalic.inf +26 -0
  153. data/fonts/ZapfDingbats.afm +225 -0
  154. data/fonts/ZapfDingbats.inf +26 -0
  155. data/lib/epdfafm.rb +334 -0
  156. data/lib/epdfdw.rb +710 -0
  157. data/lib/epdfk.rb +3142 -0
  158. data/lib/epdfo.rb +882 -0
  159. data/lib/epdfpw.rb +1749 -0
  160. data/lib/epdfs.rb +101 -0
  161. data/lib/epdfsw.rb +267 -0
  162. data/lib/epdft.rb +145 -0
  163. data/lib/epdftt.rb +458 -0
  164. data/test/pdf_tests.rb +16 -0
  165. data/test/test.rb +816 -0
  166. data/test/test_epdfafm.rb +202 -0
  167. data/test/test_epdfdw.rb +369 -0
  168. data/test/test_epdfk.rb +47 -0
  169. data/test/test_epdfo.rb +762 -0
  170. data/test/test_epdfpw.rb +129 -0
  171. data/test/test_epdfs.rb +54 -0
  172. data/test/test_epdfsw.rb +314 -0
  173. data/test/test_epdft.rb +198 -0
  174. data/test/test_epdftt.rb +34 -0
  175. data/test/test_helpers.rb +18 -0
  176. data/test/testimg.jpg +0 -0
  177. metadata +247 -0
data/lib/epdfo.rb ADDED
@@ -0,0 +1,882 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+ #
4
+ # Created by Brent Rowland on 2007-07-13.
5
+ # Copyright (c) 2007, Eidetic Software. All rights reserved.
6
+
7
+ require 'epdfk'
8
+
9
+ module EideticPDF
10
+ module PdfObjects # :nodoc: all
11
+ class Header
12
+ VERSIONS = {
13
+ 1.0 => "%PDF-1.0\n".freeze,
14
+ 1.1 => "%PDF-1.1\n".freeze,
15
+ 1.2 => "%PDF-1.2\n".freeze,
16
+ 1.3 => "%PDF-1.3\n".freeze,
17
+ 1.4 => "%PDF-1.4\n".freeze
18
+ }
19
+
20
+ def initialize(version=1.3)
21
+ @version = version
22
+ end
23
+
24
+ def to_s
25
+ VERSIONS[@version].dup
26
+ end
27
+ end
28
+
29
+ class IndirectObjectRef
30
+ attr_reader :indirect_object
31
+
32
+ def initialize(indirect_object)
33
+ @indirect_object = indirect_object
34
+ end
35
+
36
+ def to_s
37
+ @indirect_object.reference_string
38
+ end
39
+
40
+ def eql?(other)
41
+ self.indirect_object.eql?(other.indirect_object)
42
+ end
43
+
44
+ def ==(other)
45
+ other.respond_to?(:indirect_object) && self.indirect_object == other.indirect_object
46
+ end
47
+ end
48
+
49
+ class IndirectObject
50
+ attr_reader :seq, :gen
51
+
52
+ def initialize(seq, gen, obj=nil)
53
+ @seq, @gen, @obj = seq, gen, obj
54
+ end
55
+
56
+ def header
57
+ "#{@seq} #{@gen} obj\n"
58
+ end
59
+
60
+ def body
61
+ @obj ? "#{@obj}\n" : ''
62
+ end
63
+
64
+ def footer
65
+ "endobj\n"
66
+ end
67
+
68
+ def to_s
69
+ header + body + footer
70
+ end
71
+
72
+ def reference_string
73
+ "#{@seq} #{@gen} R "
74
+ end
75
+
76
+ def reference_object
77
+ IndirectObjectRef.new(self)
78
+ end
79
+ end
80
+
81
+ # direct objects
82
+ class PdfBoolean
83
+ attr_reader :value
84
+
85
+ def initialize(value)
86
+ @value = value
87
+ end
88
+
89
+ def to_s
90
+ value ? 'true ' : 'false '
91
+ end
92
+
93
+ def ==(other)
94
+ other.respond_to?(:value) && self.value == other.value
95
+ end
96
+ end
97
+
98
+ class PdfNumber
99
+ attr_reader :value
100
+
101
+ def initialize(value)
102
+ @value = value
103
+ end
104
+
105
+ def to_s
106
+ "#{value} "
107
+ end
108
+
109
+ def eql?(other)
110
+ self.value.eql?(other.value)
111
+ end
112
+
113
+ def ==(other)
114
+ other.respond_to?(:value) && self.value == other.value
115
+ end
116
+ end
117
+
118
+ class PdfInteger < PdfNumber
119
+ def initialize(value)
120
+ @value = value.to_i
121
+ end
122
+
123
+ def self.ary(int_ary)
124
+ p_ary = int_ary.map do |i|
125
+ if i.respond_to?(:to_i)
126
+ PdfInteger.new(i.to_i)
127
+ elsif i.respond_to?(:to_ary)
128
+ PdfInteger.ary(i.to_ary)
129
+ else
130
+ i
131
+ end
132
+ end
133
+ PdfArray.new p_ary
134
+ end
135
+ end
136
+
137
+ class PdfReal < PdfNumber
138
+ def initialize(value)
139
+ @value = value.to_f
140
+ end
141
+
142
+ def self.ary(float_ary)
143
+ p_ary = float_ary.map do |f|
144
+ if f.respond_to?(:to_f)
145
+ PdfReal.new(f.to_f)
146
+ elsif f.respond_to?(:to_ary)
147
+ PdfReal.ary(f.to_ary)
148
+ else
149
+ f
150
+ end
151
+ end
152
+ PdfArray.new p_ary
153
+ end
154
+ end
155
+
156
+ # text written between ()'s with '(', ')', and '\' escaped with '\'
157
+ class PdfString
158
+ attr_reader :value
159
+
160
+ def initialize(value)
161
+ @value = value.to_s
162
+ end
163
+
164
+ def eql?(other)
165
+ self.value.eql?(other.value)
166
+ end
167
+
168
+ def ==(other)
169
+ other.respond_to?(:value) && self.value == other.value
170
+ end
171
+
172
+ def to_s
173
+ "(#{PdfString.escape(value)}) "
174
+ end
175
+
176
+ def self.escape(string)
177
+ string.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)')
178
+ end
179
+
180
+ # TODO: What kind of changes are needed here?
181
+ def self.escape_wide(string)
182
+ string.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)')
183
+ end
184
+
185
+ def self.ary(string_ary)
186
+ p_ary = string_ary.map do |s|
187
+ if s.respond_to?(:to_str)
188
+ PdfString.new(s.to_str)
189
+ elsif s.respond_to?(:to_ary)
190
+ PdfString.ary(s.to_ary)
191
+ else
192
+ s
193
+ end
194
+ end
195
+ PdfArray.new p_ary
196
+ end
197
+ end
198
+
199
+ # name of PDF entity, written as /Name
200
+ class PdfName
201
+ attr_reader :name
202
+
203
+ def initialize(name)
204
+ @name = name.to_s
205
+ end
206
+
207
+ def to_s
208
+ "/#{name} "
209
+ end
210
+
211
+ def hash
212
+ self.to_s.hash
213
+ end
214
+
215
+ def eql?(other)
216
+ self.name.eql?(other.name)
217
+ end
218
+
219
+ def ==(other)
220
+ other.respond_to?(:name) && self.name == other.name
221
+ end
222
+
223
+ def self.ary(string_ary)
224
+ p_ary = string_ary.map do |s|
225
+ if s.respond_to?(:to_str)
226
+ PdfName.new(s.to_str)
227
+ elsif s.respond_to?(:to_ary)
228
+ PdfName.ary(s.to_ary)
229
+ else
230
+ s
231
+ end
232
+ end
233
+ PdfArray.new p_ary
234
+ end
235
+ end
236
+
237
+ class PdfArray
238
+ include Enumerable
239
+
240
+ attr_reader :values
241
+ attr_accessor :wrap
242
+
243
+ def initialize(values=[], wrap=0)
244
+ @values, @wrap = values, wrap
245
+ end
246
+
247
+ def to_s
248
+ "[#{wrapped_values}] "
249
+ end
250
+
251
+ def each
252
+ if wrap.zero?
253
+ yield values
254
+ else
255
+ 0.step(values.size, wrap) { |i| yield values[i, wrap] }
256
+ end
257
+ end
258
+
259
+ def eql?(other)
260
+ self.values.eql?(other.values) && self.wrap.eql?(other.wrap)
261
+ end
262
+
263
+ def ==(other)
264
+ (other.respond_to?(:values) && self.values == other.values) &&
265
+ (other.respond_to?(:wrap) && self.wrap == other.wrap)
266
+ end
267
+
268
+ private
269
+ def wrapped_values
270
+ # return values.join if @wrap.nil? or @wrap.zero?
271
+ self.map { |segment| segment.join }.join("\n")
272
+ end
273
+ end
274
+
275
+ class PdfDictionary
276
+ def initialize(hash={})
277
+ @hash = {}
278
+ update(hash)
279
+ end
280
+
281
+ def [](key)
282
+ @hash[name_from_key(key)]
283
+ end
284
+
285
+ def []=(key, value)
286
+ @hash[name_from_key(key)] = value
287
+ end
288
+
289
+ def update(other)
290
+ other.each_pair { |key, value| self[key] = value }
291
+ self
292
+ end
293
+
294
+ def to_s
295
+ s = "<<\n"
296
+ # Sort the results consistently to be test-friendly.
297
+ s << @hash.keys.sort { |a,b| a.to_s <=> b.to_s }.map { |key| "#{key}#{@hash[key]}\n" }.join
298
+ s << ">>\n"
299
+ end
300
+
301
+ private
302
+ def name_from_key(key)
303
+ key.is_a?(PdfName) ? key : PdfName.new(key)
304
+ end
305
+ end
306
+
307
+ class PdfDictionaryObject < IndirectObject
308
+ def initialize(seq, gen, hash={})
309
+ super(seq, gen, PdfDictionary.new(hash))
310
+ end
311
+
312
+ def dictionary
313
+ @obj
314
+ end
315
+
316
+ def body
317
+ dictionary.to_s
318
+ end
319
+ end
320
+
321
+ class PdfStream < PdfDictionaryObject
322
+ attr_reader :stream
323
+
324
+ def initialize(seq, gen, stream=nil)
325
+ super(seq, gen)
326
+ @stream = (stream || '').dup
327
+ dictionary['Length'] = PdfInteger.new(length)
328
+ end
329
+
330
+ def length
331
+ stream.length
332
+ end
333
+
334
+ def filter=(filter)
335
+ if filter.is_a?(PdfArray)
336
+ dictionary['Filter'] = filter
337
+ else
338
+ dictionary['Filter'] = PdfName.new(filter)
339
+ end
340
+ end
341
+
342
+ def body
343
+ dictionary['Length'] = PdfInteger.new(length)
344
+ "#{super}stream\n#{stream}endstream\n"
345
+ end
346
+ end
347
+
348
+ class PdfNull
349
+ def to_s
350
+ "null "
351
+ end
352
+ end
353
+
354
+ class InUseXRefEntry
355
+ def initialize(byte_offset, gen)
356
+ @byte_offset, @gen = byte_offset, gen
357
+ end
358
+
359
+ def to_s
360
+ "%.10d %.5d n\n" % [@byte_offset, @gen]
361
+ end
362
+ end
363
+
364
+ class FreeXRefEntry < IndirectObject
365
+ attr_reader :seq, :gen
366
+
367
+ def to_s
368
+ "%.10d %.5d f\n" % [seq, gen]
369
+ end
370
+ end
371
+
372
+ # sub-section of a cross-reference table
373
+ class XRefSubSection < Array
374
+ def initialize
375
+ self << FreeXRefEntry.new(0,65535)
376
+ end
377
+
378
+ def to_s
379
+ "#{self.first.seq} #{self.size}\n" << join
380
+ end
381
+ end
382
+
383
+ # cross-reference table, allows quick access to any object in body
384
+ class XRefTable < Array
385
+ def size
386
+ self.inject(0) { |size, ary| size + ary.size }
387
+ end
388
+
389
+ def to_s
390
+ "xref\n" << self.map { |entry| entry.to_s }.join
391
+ end
392
+ end
393
+
394
+ # list of indirect objects
395
+ class Body < Array
396
+ def write_and_xref(s, xref_sub_section)
397
+ self.each do |indirect_object|
398
+ xref_sub_section << InUseXRefEntry.new(s.length, indirect_object.gen)
399
+ s << indirect_object.to_s
400
+ end
401
+ s
402
+ end
403
+
404
+ def to_s
405
+ join
406
+ end
407
+ end
408
+
409
+ class Trailer < PdfDictionary
410
+ attr_accessor :xref_table_start
411
+ attr_reader :xref_table_size
412
+
413
+ def xref_table_size=(size)
414
+ @xref_table_size = size
415
+ self['Size'] = PdfInteger.new(size)
416
+ end
417
+
418
+ def root=(root)
419
+ self['Root'] = root.reference_object
420
+ end
421
+
422
+ def to_s
423
+ s = "trailer\n"
424
+ s << super
425
+ s << "startxref\n"
426
+ s << "#{xref_table_start}\n"
427
+ s << "%%EOF\n"
428
+ end
429
+ end
430
+
431
+ class Rectangle < PdfArray
432
+ attr_reader :x1, :y1, :x2, :y2
433
+
434
+ def initialize(x1, y1, x2, y2)
435
+ super([x1, y1, x2, y2].map { |i| PdfInteger.new(i) })
436
+ @x1, @y1, @x2, @y2 = x1, y1, x2, y2
437
+ end
438
+ end
439
+
440
+ # defines Type1, TrueType, etc font
441
+ class PdfFont < PdfDictionaryObject
442
+ def encoding=(encoding)
443
+ return if encoding == 'StandardEncoding'
444
+ encoding = PdfName.new(encoding) if encoding.is_a?(String)
445
+ dictionary['Encoding'] = encoding
446
+ end
447
+
448
+ def widths=(widths)
449
+ dictionary['Widths'] = widths
450
+ end
451
+
452
+ def font_descriptor=(font_descriptor)
453
+ dictionary['FontDescriptor'] = font_descriptor
454
+ end
455
+
456
+ def initialize(seq, gen, sub_type, base_font, first_char, last_char, widths, font_descriptor)
457
+ super(seq, gen)
458
+ dictionary['Type'] = PdfName.new('Font')
459
+ dictionary['Subtype'] = PdfName.new(sub_type)
460
+ dictionary['BaseFont'] = PdfName.new(base_font)
461
+ dictionary['FirstChar'] = PdfInteger.new(first_char)
462
+ dictionary['LastChar'] = PdfInteger.new(last_char)
463
+ dictionary['Widths'] = widths.reference_object unless widths.nil?
464
+ dictionary['FontDescriptor'] = font_descriptor.reference_object unless font_descriptor.nil?
465
+ end
466
+
467
+ def self.standard_encoding?(encoding)
468
+ PdfK::STANDARD_ENCODINGS.include?(encoding)
469
+ end
470
+ end
471
+
472
+ class PdfFontDescriptor < PdfDictionaryObject
473
+ def initialize(seq, gen,
474
+ font_name, flags, font_b_box, missing_width, stem_v, stem_h, italic_angle,
475
+ cap_height, x_height,
476
+ ascent, descent, leading,
477
+ max_width, avg_width)
478
+ super(seq, gen)
479
+ dictionary['Type'] = PdfName.new('FontDescriptor')
480
+ dictionary['FontName'] = PdfName.new(font_name)
481
+ dictionary['Flags'] = PdfInteger.new(flags)
482
+ dictionary['FontBBox'] = PdfArray.new(font_b_box.map { |i| PdfInteger.new(i) })
483
+ dictionary['MissingWidth'] = PdfInteger.new(missing_width)
484
+ dictionary['StemV'] = PdfInteger.new(stem_v) if stem_v
485
+ dictionary['StemH'] = PdfInteger.new(stem_h) if stem_h
486
+ dictionary['ItalicAngle'] = PdfReal.new(italic_angle)
487
+ dictionary['CapHeight'] = PdfInteger.new(cap_height)
488
+ dictionary['XHeight'] = PdfInteger.new(x_height)
489
+ dictionary['Ascent'] = PdfInteger.new(ascent)
490
+ dictionary['Descent'] = PdfInteger.new(descent)
491
+ dictionary['Leading'] = PdfInteger.new(leading)
492
+ dictionary['MaxWidth'] = PdfInteger.new(max_width)
493
+ dictionary['AvgWidth'] = PdfInteger.new(avg_width)
494
+ end
495
+ end
496
+
497
+ class PdfFontEncoding < PdfDictionaryObject
498
+ def initialize(seq, gen, base_encoding, differences)
499
+ super(seq, gen)
500
+ dictionary['Type'] = PdfName.new('Encoding')
501
+ dictionary['BaseEncoding'] = PdfName.new(base_encoding)
502
+ dictionary['Differences'] = differences
503
+ end
504
+ end
505
+
506
+ # images and forms
507
+ class PdfXObject < PdfStream
508
+ def initialize(seq, gen, stream=nil)
509
+ super(seq, gen, stream)
510
+ dictionary['Type'] = PdfName.new('XObject')
511
+ end
512
+ end
513
+
514
+ class PdfImage < PdfXObject
515
+ attr_reader :width, :height
516
+
517
+ def initialize(seq, gen, stream=nil)
518
+ super(seq, gen, stream)
519
+ dictionary['Subtype'] = PdfName.new('Image')
520
+ end
521
+
522
+ def body
523
+ dictionary['Length'] = PdfInteger.new(stream.length)
524
+ super
525
+ end
526
+
527
+ def filter=(filter)
528
+ dictionary['Filter'] = PdfName.new(filter)
529
+ end
530
+
531
+ def filters=(filters)
532
+ dictionary['Filter'] = filters
533
+ end
534
+
535
+ def width=(width)
536
+ @width = width
537
+ dictionary['Width'] = PdfInteger.new(width)
538
+ end
539
+
540
+ def height=(height)
541
+ @height = height
542
+ dictionary['Height'] = PdfInteger.new(height)
543
+ end
544
+
545
+ def bits_per_component=(bits)
546
+ dictionary['BitsPerComponent'] = PdfInteger.new(bits)
547
+ end
548
+
549
+ def color_space=(color_space)
550
+ if color_space.is_a?(String)
551
+ dictionary['ColorSpace'] = PdfName.new(color_space)
552
+ else
553
+ # array or dictionary
554
+ dictionary['ColorSpace'] = color_space
555
+ end
556
+ end
557
+
558
+ def decode=(decode)
559
+ dictionary['Decode'] = decode
560
+ end
561
+
562
+ def interpolate=(interpolate)
563
+ dictionary['Interpolate'] = PdfBoolean.new(interpolate)
564
+ end
565
+
566
+ def image_mask=(image_mask)
567
+ dictionary['ImageMask'] = PdfBoolean.new(image_mask)
568
+ end
569
+
570
+ def intent=(intent)
571
+ dictionary['Intent'] = PdfName.new(intent)
572
+ end
573
+ end
574
+
575
+ class PdfAnnot < PdfDictionaryObject
576
+ def initialize(seq, gen, sub_type, rect)
577
+ super(seq, gen)
578
+ dictionary['Type'] = PdfName.new('Annot')
579
+ dictionary['Subtype'] = PdfName.new(sub_type)
580
+ dictionary['Rect'] = rect
581
+ end
582
+
583
+ def border=(border)
584
+ dictionary['Border'] = PdfInteger.ary(border)
585
+ end
586
+
587
+ def color=(color)
588
+ dictionary['C'] = PdfReal.ary(color)
589
+ end
590
+
591
+ def title=(title)
592
+ dictionary['T'] = PdfString.new(title)
593
+ end
594
+
595
+ def mod_date=(mod_date)
596
+ dictionary['M'] = PdfString.new(mod_date.strftime("%Y%m%d%H%M%S"))
597
+ end
598
+
599
+ def flags=(flags)
600
+ dictionary['F'] = PdfInteger.new(flags)
601
+ end
602
+
603
+ def highlight=(highlight)
604
+ dictionary['H'] = PdfName.new(highlights[highlight] || highlight)
605
+ end
606
+
607
+ def border_style=(border_style)
608
+ dictionary['BS'] = PdfDictionary.new(border_style)
609
+ end
610
+
611
+ def appearance_dictionary=(appearance)
612
+ dictionary['AP'] = PdfDictionary.new(appearance)
613
+ end
614
+
615
+ def appearance_state=(state)
616
+ dictionary['AS'] = PdfName.new(state)
617
+ end
618
+
619
+ private
620
+ def highlights
621
+ @highlights ||= {
622
+ :none => 'N',
623
+ :invert => 'I',
624
+ :outline => 'O',
625
+ :push => 'P'
626
+ }
627
+ end
628
+ end
629
+
630
+ class PdfTextAnnot < PdfAnnot
631
+ def initialize(seq, gen, rect, contents)
632
+ super(seq, gen, 'Text', rect)
633
+ dictionary['Contents'] = PdfString.new(contents)
634
+ end
635
+
636
+ def open=(open)
637
+ dictionary['Open'] = PdfBoolean.new(open)
638
+ end
639
+ end
640
+
641
+ class PdfLinkAnnot < PdfAnnot
642
+ def initialize(seq, gen, rect)
643
+ super(seq, gen, 'Link', rect)
644
+ end
645
+
646
+ def dest=(dest)
647
+ value = if dist.is_a?(String)
648
+ PdfName.new(dest)
649
+ elsif dist.is_a?(Array)
650
+ PdfArray.new(dest)
651
+ else
652
+ dest
653
+ end
654
+ dictionary['Dest'] = value
655
+ end
656
+
657
+ def action=(action)
658
+ dictionary['A'] = action.is_a?(Hash) ? PdfDictionary.new(action) : action
659
+ end
660
+ end
661
+
662
+ class PdfMovieAnnot < PdfAnnot
663
+ def initialize(seq, gen, rect, movie)
664
+ # movie: Hash
665
+ super(seq, gen, 'Movie', rect)
666
+ dictionary['Movie'] = PdfDictionary.new(movie)
667
+ end
668
+
669
+ def activation=(activation)
670
+ # activation: Hash or boolean
671
+ dictionary['A'] = activation.is_a?(Hash) ? PdfDictionary.new(activation) : PdfBoolean.new(activation)
672
+ end
673
+ end
674
+
675
+ class PdfSoundAnnot < PdfAnnot
676
+ def initialize(seq, gen, rect, sound)
677
+ # sound: PdfStream
678
+ super(seq, gen, 'Sound', rect)
679
+ dictionary['Sound'] = sound
680
+ end
681
+ end
682
+
683
+ class PdfURIAction < PdfDictionary
684
+ def initialize(uri)
685
+ end
686
+ end
687
+
688
+ class PdfAnnotBorder < PdfDictionary
689
+ def initialize(sub_type)
690
+ end
691
+ end
692
+
693
+ # defines resources used by a page or collection of pages
694
+ class PdfResources < PdfDictionaryObject
695
+ def proc_set=(pdf_object)
696
+ # pdf_object: PdfArray or IndirectObjectRef
697
+ dictionary['ProcSet'] = pdf_object
698
+ end
699
+
700
+ def fonts
701
+ @fonts ||= PdfDictionary.new
702
+ dictionary['Font'] ||= @fonts
703
+ end
704
+
705
+ def x_objects
706
+ @x_objects ||= PdfDictionary.new
707
+ dictionary['XObject'] ||= @x_objects
708
+ end
709
+ end
710
+
711
+ # common elements between a page and a collection of pages
712
+ class PdfPageBase < PdfDictionaryObject
713
+ def initialize(seq, gen, parent=nil)
714
+ # parent: IndirectObjectRef
715
+ super(seq, gen)
716
+ dictionary['Parent'] = parent.reference_object unless parent.nil?
717
+ end
718
+
719
+ def media_box=(media_box)
720
+ # media_box: Rectangle
721
+ dictionary['MediaBox'] = media_box
722
+ end
723
+
724
+ def resources=(resources)
725
+ # resources: IndirectObjectRef
726
+ dictionary['Resources'] = resources.reference_object
727
+ end
728
+
729
+ def crop_box=(crop_box)
730
+ # crop_box: Rectangle
731
+ dictionary['CropBox'] = crop_box
732
+ end
733
+
734
+ def rotate=(rotate)
735
+ # rotate: integer
736
+ dictionary['Rotate'] = PdfInteger.new(rotate)
737
+ end
738
+
739
+ def duration=(duration)
740
+ # duration: integer or float
741
+ dictionary['Dur'] = PdfNumber.new(duration)
742
+ end
743
+
744
+ def hidden=(hidden)
745
+ # hidden: boolean
746
+ dictionary['Hid'] = PdfBoolean.new(hidden)
747
+ end
748
+
749
+ def transition=(transition)
750
+ # transition: hash
751
+ dictionary['Trans'] = PdfDictionary.new(transition)
752
+ end
753
+
754
+ def additional_actions=(additional_actions)
755
+ # additional_actions: hash
756
+ dictionary['AA'] = PdfDictionary.new(additional_actions)
757
+ end
758
+ end
759
+
760
+ # one page of a PDF document, not counting resources defined in a parent
761
+ class PdfPage < PdfPageBase
762
+ def initialize(seq, gen, parent)
763
+ super(seq, gen, parent)
764
+ dictionary['Type'] = PdfName.new('Page')
765
+ end
766
+
767
+ def body
768
+ if contents.size > 1
769
+ dictionary['Contents'] = PdfArray.new(contents.map { |stream| stream.reference_object })
770
+ elsif contents.size == 1
771
+ dictionary['Contents'] = contents.first.reference_object
772
+ end
773
+ super
774
+ end
775
+
776
+ # PdfStream's
777
+ def contents
778
+ @contents ||= []
779
+ end
780
+
781
+ def thumb=(thumb)
782
+ # thumb: stream
783
+ dictionary['Thumb'] = thumb.reference_object
784
+ end
785
+
786
+ def annots=(annots)
787
+ # annots: array of dictionary objects
788
+ (@annots ||= []).concat(annots)
789
+ dictionary['Annots'] = PdfArray.new(@annots.map { |annot| annot.reference_object })
790
+ end
791
+
792
+ def beads=(beads)
793
+ # beads: array of dictionary objects
794
+ dictionary['B'] = PdfArray.new(beads.map { |bead| bead.reference_object })
795
+ end
796
+ end
797
+
798
+ # collection of pages
799
+ class PdfPages < PdfPageBase
800
+ attr_reader :kids # array of refs to PdfPageBase
801
+
802
+ def initialize(seq, gen, parent=nil)
803
+ super(seq, gen, parent)
804
+ @kids = []
805
+ dictionary['Type'] = PdfName.new('Pages')
806
+ end
807
+
808
+ def to_s
809
+ dictionary['Count'] = PdfInteger.new(@kids.size)
810
+ dictionary['Kids'] = PdfArray.new(@kids.map { |page| page.reference_object })
811
+ super
812
+ end
813
+ end
814
+
815
+ class PdfOutlines < PdfDictionaryObject
816
+ def initialize(seq, gen)
817
+ super(seq, gen)
818
+ dictionary['Type'] = PdfName.new('Outlines')
819
+ end
820
+
821
+ def to_s
822
+ dictionary['Count'] = PdfInteger.new(0)
823
+ super
824
+ end
825
+ end
826
+
827
+ # root object of a PDF document, with pointers to other top-level objects
828
+ class PdfCatalog < PdfDictionaryObject
829
+ attr_reader :page_mode, :pages, :outlines
830
+
831
+ def initialize(seq, gen, page_mode=:use_none, pages=nil, outlines=nil)
832
+ super(seq, gen)
833
+ dictionary['Type'] = PdfName.new('Catalog')
834
+ @page_mode = page_mode
835
+ dictionary['PageMode'] = PdfName.new(PAGE_MODES[page_mode])
836
+ if pages
837
+ @pages = pages
838
+ dictionary['Pages'] = pages.reference_object
839
+ end
840
+ if outlines
841
+ @outlines = outlines
842
+ dictionary['Outlines'] = outlines.reference_object
843
+ end
844
+ end
845
+
846
+ def to_s
847
+ super
848
+ end
849
+
850
+ PAGE_MODES = {
851
+ :use_none => 'UseNone',
852
+ :use_outlines => 'UseOutlines',
853
+ :use_thumbs => 'UseThumbs',
854
+ :full_screen => 'FullScreen'
855
+ }.freeze
856
+ end
857
+
858
+ # root of object tree representing a PDF document
859
+ class PdfFile
860
+ attr_reader :header, :body, :trailer
861
+
862
+ def initialize
863
+ @header = Header.new
864
+ @body = Body.new
865
+ @trailer = Trailer.new
866
+ end
867
+
868
+ def to_s
869
+ xref_table = XRefTable.new
870
+ xref_sub_section = XRefSubSection.new
871
+ xref_table << xref_sub_section
872
+
873
+ s = @header.to_s
874
+ @body.write_and_xref(s, xref_sub_section)
875
+ @trailer.xref_table_start = s.length
876
+ @trailer.xref_table_size = xref_sub_section.size
877
+ s << xref_table.to_s
878
+ s << @trailer.to_s
879
+ end
880
+ end
881
+ end
882
+ end