eideticpdf 0.9.9

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 (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