fpdf 1.53

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/lib/bookmark.rb +99 -0
  2. data/lib/chinese.rb +445 -0
  3. data/lib/fpdf.rb +1536 -0
  4. data/lib/fpdf_eps.rb +139 -0
  5. data/lib/makefont.rb +1787 -0
  6. metadata +50 -0
@@ -0,0 +1,1536 @@
1
+ # Ruby FPDF 1.53d
2
+ # FPDF 1.53 by Olivier Plathey ported to Ruby by Brian Ollenberger
3
+ # Copyright 2005 Brian Ollenberger
4
+ # Please retain this entire copyright notice. If you distribute any
5
+ # modifications, place an additional comment here that clearly indicates
6
+ # that it was modified. You may (but are not send any useful modifications that you make
7
+ # back to me at http://zeropluszero.com/software/fpdf/
8
+
9
+ # Bug fixes, examples, external fonts, JPEG support, and upgrade to version
10
+ # 1.53 contributed by Kim Shrier.
11
+ #
12
+ # Bookmark support contributed by Sylvain Lafleur.
13
+ #
14
+ # EPS support contributed by Thiago Jackiw, ported from the PHP version by Valentin Schmidt.
15
+ #
16
+ # Many other bug reports and fixes contributed by many other people.
17
+
18
+ require 'date'
19
+ require 'zlib'
20
+
21
+ class FPDF
22
+ FPDF_VERSION = '1.53d'
23
+
24
+ Charwidths = {
25
+ 'courier'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
26
+
27
+ 'courierB'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
28
+
29
+ 'courierI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
30
+
31
+ 'courierBI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
32
+
33
+ 'helvetica'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
34
+
35
+ 'helveticaB'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
36
+
37
+ 'helveticaI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
38
+
39
+ 'helveticaBI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
40
+
41
+ 'times'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 408, 500, 500, 833, 778, 180, 333, 333, 500, 564, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722, 667, 667, 722, 611, 556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667, 556, 611, 722, 722, 944, 722, 722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444, 500, 444, 333, 500, 500, 278, 278, 500, 278, 778, 500, 500, 500, 500, 333, 389, 278, 500, 500, 722, 500, 500, 444, 480, 200, 480, 541, 350, 500, 350, 333, 500, 444, 1000, 500, 500, 333, 1000, 556, 333, 889, 350, 611, 350, 350, 333, 333, 444, 444, 350, 500, 1000, 333, 980, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 200, 500, 333, 760, 276, 500, 564, 333, 760, 333, 400, 564, 300, 300, 333, 500, 453, 250, 333, 300, 310, 500, 750, 750, 750, 444, 722, 722, 722, 722, 722, 722, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722, 722, 722, 722, 556, 500, 444, 444, 444, 444, 444, 444, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 564, 500, 500, 500, 500, 500, 500, 500, 500],
42
+
43
+ 'timesB'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 555, 500, 500, 1000, 833, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722, 667, 722, 722, 667, 611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722, 556, 667, 722, 722, 1000, 722, 722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444, 556, 444, 333, 500, 556, 278, 333, 556, 278, 833, 556, 500, 556, 556, 444, 389, 333, 556, 500, 722, 500, 500, 444, 394, 220, 394, 520, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 1000, 350, 667, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 220, 500, 333, 747, 300, 500, 570, 333, 747, 333, 400, 570, 300, 300, 333, 556, 540, 250, 333, 300, 330, 500, 750, 750, 750, 500, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722, 722, 722, 722, 611, 556, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 500, 556, 500],
44
+
45
+ 'timesI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 420, 500, 500, 833, 778, 214, 333, 333, 500, 675, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611, 611, 667, 722, 611, 611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611, 500, 556, 722, 611, 833, 611, 556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444, 500, 444, 278, 500, 500, 278, 278, 444, 278, 722, 500, 500, 500, 500, 389, 389, 278, 500, 444, 667, 444, 444, 389, 400, 275, 400, 541, 350, 500, 350, 333, 500, 556, 889, 500, 500, 333, 1000, 500, 333, 944, 350, 556, 350, 350, 333, 333, 556, 556, 350, 500, 889, 333, 980, 389, 333, 667, 350, 389, 556, 250, 389, 500, 500, 500, 500, 275, 500, 333, 760, 276, 500, 675, 333, 760, 333, 400, 675, 300, 300, 333, 500, 523, 250, 333, 300, 310, 500, 750, 750, 750, 500, 611, 611, 611, 611, 611, 611, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722, 722, 722, 556, 611, 500, 500, 500, 500, 500, 500, 500, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 675, 500, 500, 500, 500, 500, 444, 500, 444],
46
+
47
+ 'timesBI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 389, 555, 500, 500, 833, 778, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667, 667, 667, 722, 667, 667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667, 556, 611, 722, 667, 889, 667, 611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444, 500, 444, 333, 500, 556, 278, 278, 500, 278, 778, 556, 500, 500, 500, 389, 389, 278, 556, 444, 667, 500, 444, 389, 348, 220, 348, 570, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 944, 350, 611, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 389, 611, 250, 389, 500, 500, 500, 500, 220, 500, 333, 747, 266, 500, 606, 333, 747, 333, 400, 570, 300, 300, 333, 576, 500, 250, 333, 300, 300, 500, 750, 750, 750, 500, 667, 667, 667, 667, 667, 667, 944, 667, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722, 722, 722, 611, 611, 500, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 444, 500, 444],
48
+
49
+ 'symbol'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, 549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722, 768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500, 500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549, 549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, 400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, 823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, 768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603, 494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494, 0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0],
50
+
51
+ 'zapfdingbats'=>[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 278, 974, 961, 974, 980, 719, 789, 790, 791, 690, 960, 939, 549, 855, 911, 933, 911, 945, 974, 755, 846, 762, 761, 571, 677, 763, 760, 759, 754, 494, 552, 537, 577, 692, 786, 788, 788, 790, 793, 794, 816, 823, 789, 841, 823, 833, 816, 831, 923, 744, 723, 749, 790, 792, 695, 776, 768, 792, 759, 707, 708, 682, 701, 826, 815, 789, 789, 707, 687, 696, 689, 786, 787, 713, 791, 785, 791, 873, 761, 762, 762, 759, 759, 892, 892, 788, 784, 438, 138, 277, 415, 392, 392, 668, 668, 0, 390, 390, 317, 317, 276, 276, 509, 509, 410, 410, 234, 234, 334, 334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 732, 544, 544, 910, 667, 760, 760, 776, 595, 694, 626, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 894, 838, 1016, 458, 748, 924, 748, 918, 927, 928, 928, 834, 873, 828, 924, 924, 917, 930, 931, 463, 883, 836, 836, 867, 867, 696, 696, 874, 0, 874, 760, 946, 771, 865, 771, 888, 967, 888, 831, 873, 927, 970, 918, 0]
52
+ }
53
+
54
+ def initialize(orientation='P', unit='mm', format='A4')
55
+ # Initialization of properties
56
+ @page=0
57
+ @n=2
58
+ @buffer=''
59
+ @pages=[]
60
+ @OrientationChanges=[]
61
+ @state=0
62
+ @fonts={}
63
+ @FontFiles={}
64
+ @diffs=[]
65
+ @images={}
66
+ @links=[]
67
+ @PageLinks={}
68
+ @InFooter=false
69
+ @FontFamily=''
70
+ @FontStyle=''
71
+ @FontSizePt=12
72
+ @underline= false
73
+ @DrawColor='0 G'
74
+ @FillColor='0 g'
75
+ @TextColor='0 g'
76
+ @ColorFlag=false
77
+ @ws=0
78
+ @offsets=[]
79
+
80
+ # Standard fonts
81
+ @CoreFonts={}
82
+ @CoreFonts['courier']='Courier'
83
+ @CoreFonts['courierB']='Courier-Bold'
84
+ @CoreFonts['courierI']='Courier-Oblique'
85
+ @CoreFonts['courierBI']='Courier-BoldOblique'
86
+ @CoreFonts['helvetica']='Helvetica'
87
+ @CoreFonts['helveticaB']='Helvetica-Bold'
88
+ @CoreFonts['helveticaI']='Helvetica-Oblique'
89
+ @CoreFonts['helveticaBI']='Helvetica-BoldOblique'
90
+ @CoreFonts['times']='Times-Roman'
91
+ @CoreFonts['timesB']='Times-Bold'
92
+ @CoreFonts['timesI']='Times-Italic'
93
+ @CoreFonts['timesBI']='Times-BoldItalic'
94
+ @CoreFonts['symbol']='Symbol'
95
+ @CoreFonts['zapfdingbats']='ZapfDingbats'
96
+
97
+ # Scale factor
98
+ if unit=='pt'
99
+ @k=1
100
+ elsif unit=='mm'
101
+ @k=72/25.4
102
+ elsif unit=='cm'
103
+ @k=72/2.54;
104
+ elsif unit=='in'
105
+ @k=72
106
+ else
107
+ raise 'Incorrect unit: '+unit
108
+ end
109
+
110
+ # Page format
111
+ if format.is_a? String
112
+ format.downcase!
113
+ if format=='a3'
114
+ format=[841.89,1190.55]
115
+ elsif format=='a4'
116
+ format=[595.28,841.89]
117
+ elsif format=='a5'
118
+ format=[420.94,595.28]
119
+ elsif format=='letter'
120
+ format=[612,792]
121
+ elsif format=='legal'
122
+ format=[612,1008]
123
+ else
124
+ raise 'Unknown page format: '+format
125
+ end
126
+ @fwPt,@fhPt=format
127
+ else
128
+ @fwPt=format[0]*@k
129
+ @fhPt=format[1]*@k
130
+ end
131
+ @fw=@fwPt/@k;
132
+ @fh=@fhPt/@k;
133
+
134
+ # Page orientation
135
+ orientation.downcase!
136
+ if orientation=='p' or orientation=='portrait'
137
+ @DefOrientation='P'
138
+ @wPt=@fwPt
139
+ @hPt=@fhPt
140
+ elsif orientation=='l' or orientation=='landscape'
141
+ @DefOrientation='L'
142
+ @wPt=@fhPt
143
+ @hPt=@fwPt
144
+ else
145
+ raise 'Incorrect orientation: '+orientation
146
+ end
147
+ @CurOrientation=@DefOrientation
148
+ @w=@wPt/@k
149
+ @h=@hPt/@k
150
+
151
+ # Page margins (1 cm)
152
+ margin=28.35/@k
153
+ SetMargins(margin,margin)
154
+ # Interior cell margin (1 mm)
155
+ @cMargin=margin/10
156
+ # Line width (0.2 mm)
157
+ @LineWidth=0.567/@k
158
+ # Automatic page break
159
+ SetAutoPageBreak(true,2*margin)
160
+ # Full width display mode
161
+ SetDisplayMode('fullwidth')
162
+ # Enable compression
163
+ SetCompression(true)
164
+ # Set default PDF version number
165
+ @PDFVersion='1.3'
166
+ end
167
+
168
+ def SetMargins(left, top, right=-1)
169
+ # Set left, top and right margins
170
+ @lMargin=left
171
+ @tMargin=top
172
+ right=left if right==-1
173
+ @rMargin=right
174
+ end
175
+
176
+ def SetLeftMargin(margin)
177
+ # Set left margin
178
+ @lMargin=margin
179
+ @x=margin if @page>0 and @x<margin
180
+ end
181
+
182
+ def SetTopMargin(margin)
183
+ # Set top margin
184
+ @tMargin=margin
185
+ end
186
+
187
+ def SetRightMargin(margin)
188
+ #Set right margin
189
+ @rMargin=margin
190
+ end
191
+
192
+ def SetAutoPageBreak(auto, margin=0)
193
+ # Set auto page break mode and triggering margin
194
+ @AutoPageBreak=auto
195
+ @bMargin=margin
196
+ @PageBreakTrigger=@h-margin
197
+ end
198
+
199
+ def SetDisplayMode(zoom, layout='continuous')
200
+ # Set display mode in viewer
201
+ if zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or
202
+ zoom=='default' or not zoom.kind_of? String
203
+
204
+ @ZoomMode=zoom;
205
+ elsif zoom=='zoom'
206
+ @ZoomMode=layout
207
+ else
208
+ raise 'Incorrect zoom display mode: '+zoom
209
+ end
210
+ if layout=='single' or layout=='continuous' or layout=='two' or
211
+ layout=='default'
212
+
213
+ @LayoutMode=layout
214
+ elsif zoom!='zoom'
215
+ raise 'Incorrect layout display mode: '+layout
216
+ end
217
+ end
218
+
219
+ def SetCompression(compress)
220
+ # Set page compression
221
+ @compress = compress
222
+ end
223
+
224
+ def SetTitle(title)
225
+ # Title of document
226
+ @title=title
227
+ end
228
+
229
+ def SetSubject(subject)
230
+ # Subject of document
231
+ @subject=subject
232
+ end
233
+
234
+ def SetAuthor(author)
235
+ # Author of document
236
+ @author=author
237
+ end
238
+
239
+ def SetKeywords(keywords)
240
+ # Keywords of document
241
+ @keywords=keywords
242
+ end
243
+
244
+ def SetCreator(creator)
245
+ # Creator of document
246
+ @creator=creator
247
+ end
248
+
249
+ def AliasNbPages(aliasnb='{nb}')
250
+ # Define an alias for total number of pages
251
+ @AliasNbPages=aliasnb
252
+ end
253
+
254
+ def Error(msg)
255
+ raise 'FPDF error: '+msg
256
+ end
257
+
258
+ def Open
259
+ # Begin document
260
+ @state=1
261
+ end
262
+
263
+ def Close
264
+ # Terminate document
265
+ return if @state==3
266
+ self.AddPage if @page==0
267
+ # Page footer
268
+ @InFooter=true
269
+ self.Footer
270
+ @InFooter=false
271
+ # Close page
272
+ endpage
273
+ # Close document
274
+ enddoc
275
+ end
276
+
277
+ def AddPage(orientation='')
278
+ # Start a new page
279
+ self.Open if @state==0
280
+ family=@FontFamily
281
+ style=@FontStyle+(@underline ? 'U' : '')
282
+ size=@FontSizePt
283
+ lw=@LineWidth
284
+ dc=@DrawColor
285
+ fc=@FillColor
286
+ tc=@TextColor
287
+ cf=@ColorFlag
288
+ if @page>0
289
+ # Page footer
290
+ @InFooter=true
291
+ self.Footer
292
+ @InFooter=false
293
+ # Close page
294
+ endpage
295
+ end
296
+ # Start new page
297
+ beginpage(orientation)
298
+ # Set line cap style to square
299
+ out('2 J')
300
+ # Set line width
301
+ @LineWidth=lw
302
+ out(sprintf('%.2f w',lw*@k))
303
+ # Set font
304
+ SetFont(family,style,size) if family
305
+ # Set colors
306
+ @DrawColor=dc
307
+ out(dc) if dc!='0 G'
308
+ @FillColor=fc
309
+ out(fc) if fc!='0 g'
310
+ @TextColor=tc
311
+ @ColorFlag=cf
312
+ # Page header
313
+ self.Header
314
+ # Restore line width
315
+ if @LineWidth!=lw
316
+ @LineWidth=lw
317
+ out(sprintf('%.2f w',lw*@k))
318
+ end
319
+ # Restore font
320
+ self.SetFont(family,style,size) if family
321
+ # Restore colors
322
+ if @DrawColor!=dc
323
+ @DrawColor=dc
324
+ out(dc)
325
+ end
326
+ if @FillColor!=fc
327
+ @FillColor=fc
328
+ out(fc)
329
+ end
330
+ @TextColor=tc
331
+ @ColorFlag=cf
332
+ end
333
+
334
+ def Header
335
+ # To be implemented in your inherited class
336
+ end
337
+
338
+ def Footer
339
+ # To be implemented in your inherited class
340
+ end
341
+
342
+ def PageNo
343
+ # Get current page number
344
+ @page
345
+ end
346
+
347
+ def SetDrawColor(r,g=-1,b=-1)
348
+ # Set color for all stroking operations
349
+ if (r==0 and g==0 and b==0) or g==-1
350
+ @DrawColor=sprintf('%.3f G',r/255.0)
351
+ else
352
+ @DrawColor=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0)
353
+ end
354
+ out(@DrawColor) if(@page>0)
355
+ end
356
+
357
+ def SetFillColor(r,g=-1,b=-1)
358
+ # Set color for all filling operations
359
+ if (r==0 and g==0 and b==0) or g==-1
360
+ @FillColor=sprintf('%.3f g',r/255.0)
361
+ else
362
+ @FillColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
363
+ end
364
+ @ColorFlag=(@FillColor!=@TextColor)
365
+ out(@FillColor) if(@page>0)
366
+ end
367
+
368
+ def SetTextColor(r,g=-1,b=-1)
369
+ # Set color for text
370
+ if (r==0 and g==0 and b==0) or g==-1
371
+ @TextColor=sprintf('%.3f g',r/255.0)
372
+ else
373
+ @TextColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
374
+ end
375
+ @ColorFlag=(@FillColor!=@TextColor)
376
+ end
377
+
378
+ def GetStringWidth(s)
379
+ # Get width of a string in the current font
380
+ cw=@CurrentFont['cw']
381
+ w=0
382
+ s.each_byte do |c|
383
+ w=w+cw[c]
384
+ end
385
+ w*@FontSize/1000.0
386
+ end
387
+
388
+ def SetLineWidth(width)
389
+ # Set line width
390
+ @LineWidth=width
391
+ out(sprintf('%.2f w',width*@k)) if @page>0
392
+ end
393
+
394
+ def Line(x1, y1, x2, y2)
395
+ # Draw a line
396
+ out(sprintf('%.2f %.2f m %.2f %.2f l S',
397
+ x1*@k,(@h-y1)*@k,x2*@k,(@h-y2)*@k))
398
+ end
399
+
400
+ def Rect(x, y, w, h, style='')
401
+ # Draw a rectangle
402
+ if style=='F'
403
+ op='f'
404
+ elsif style=='FD' or style=='DF'
405
+ op='B'
406
+ else
407
+ op='S'
408
+ end
409
+ out(sprintf('%.2f %.2f %.2f %.2f re %s', x*@k,(@h-y)*@k,w*@k,-h*@k,op))
410
+ end
411
+
412
+ def AddFont(family, style='', file='')
413
+ # Add a TrueType or Type1 font
414
+ family = family.downcase
415
+ family = 'helvetica' if family == 'arial'
416
+
417
+ style = style.upcase
418
+ style = 'BI' if style == 'IB'
419
+
420
+ fontkey = family + style
421
+
422
+ if @fonts.has_key?(fontkey)
423
+ self.Error("Font already added: #{family} #{style}")
424
+ end
425
+
426
+ file = family.gsub(' ', '') + style.downcase + '.rb' if file == ''
427
+
428
+ if self.class.const_defined? 'FPDF_FONTPATH'
429
+ if FPDF_FONTPATH[-1,1] == '/'
430
+ file = FPDF_FONTPATH + file
431
+ else
432
+ file = FPDF_FONTPATH + '/' + file
433
+ end
434
+ end
435
+
436
+ # Changed from "require file" to fix bug reported by Hans Allis.
437
+ load file
438
+
439
+ if FontDef.desc.nil?
440
+ self.Error("Could not include font definition file #{file}")
441
+ end
442
+
443
+ i = @fonts.length + 1
444
+
445
+ @fonts[fontkey] = {'i' => i,
446
+ 'type' => FontDef.type,
447
+ 'name' => FontDef.name,
448
+ 'desc' => FontDef.desc,
449
+ 'up' => FontDef.up,
450
+ 'ut' => FontDef.ut,
451
+ 'cw' => FontDef.cw,
452
+ 'enc' => FontDef.enc,
453
+ 'file' => FontDef.file
454
+ }
455
+
456
+ if FontDef.diff
457
+ # Search existing encodings
458
+ idx = @diffs.index(FontDef.diff)
459
+
460
+ unless idx
461
+ @diffs.push(FontDef.diff)
462
+ @fonts[fontkey]['diff'] = @diffs.length
463
+ else
464
+ @fonts[fontkey]['diff'] = idx + 1
465
+ end
466
+ end
467
+
468
+ if FontDef.file
469
+ if FontDef.type == 'TrueType'
470
+ @FontFiles[FontDef.file] = {'length1' => FontDef.originalsize}
471
+ else
472
+ @FontFiles[FontDef.file] = {'length1' => FontDef.size1, 'length2' => FontDef.size2}
473
+ end
474
+ end
475
+
476
+ return self
477
+ end
478
+
479
+ def SetFont(family, style='', size=0)
480
+ # Select a font; size given in points
481
+ family.downcase!
482
+ family=@FontFamily if family==''
483
+ if family=='arial'
484
+ family='helvetica'
485
+ elsif family=='symbol' or family=='zapfdingbats'
486
+ style=''
487
+ end
488
+ style.upcase!
489
+ unless style.index('U').nil?
490
+ @underline=true
491
+ style.gsub!('U','')
492
+ else
493
+ @underline=false;
494
+ end
495
+ style='BI' if style=='IB'
496
+ size=@FontSizePt if size==0
497
+ # Test if font is already selected
498
+ return if @FontFamily==family and
499
+ @FontStyle==style and @FontSizePt==size
500
+ # Test if used for the first time
501
+ fontkey=family+style
502
+ unless @fonts.has_key?(fontkey)
503
+ if @CoreFonts.has_key?(fontkey)
504
+ unless Charwidths.has_key?(fontkey)
505
+ raise 'Font unavailable'
506
+ end
507
+ @fonts[fontkey]={
508
+ 'i'=>@fonts.size,
509
+ 'type'=>'core',
510
+ 'name'=>@CoreFonts[fontkey],
511
+ 'up'=>-100,
512
+ 'ut'=>50,
513
+ 'cw'=>Charwidths[fontkey]}
514
+ else
515
+ raise 'Font unavailable'
516
+ end
517
+ end
518
+
519
+ #Select it
520
+ @FontFamily=family
521
+ @FontStyle=style;
522
+ @FontSizePt=size
523
+ @FontSize=size/@k;
524
+ @CurrentFont=@fonts[fontkey]
525
+ if @page>0
526
+ out(sprintf('BT /F%d %.2f Tf ET', @CurrentFont['i'], @FontSizePt))
527
+ end
528
+ end
529
+
530
+ def SetFontSize(size)
531
+ # Set font size in points
532
+ return if @FontSizePt==size
533
+ @FontSizePt=size
534
+ @FontSize=size/@k
535
+ if @page>0
536
+ out(sprintf('BT /F%d %.2f Tf ET',@CurrentFont['i'],@FontSizePt))
537
+ end
538
+ end
539
+
540
+ def AddLink
541
+ # Create a new internal link
542
+ @links.push([0, 0])
543
+ @links.size
544
+ end
545
+
546
+ def SetLink(link, y=0, page=-1)
547
+ # Set destination of internal link
548
+ y=@y if y==-1
549
+ page=@page if page==-1
550
+ @links[link]=[page, y]
551
+ end
552
+
553
+ def Link(x, y, w, h, link)
554
+ # Put a link on the page
555
+ @PageLinks[@page]=Array.new unless @PageLinks.has_key?(@page)
556
+ @PageLinks[@page].push([x*@k,@hPt-y*@k,w*@k,h*@k,link])
557
+ end
558
+
559
+ def Text(x, y, txt)
560
+ # Output a string
561
+ txt.gsub!(')', '\\)')
562
+ txt.gsub!('(', '\\(')
563
+ txt.gsub!('\\', '\\\\')
564
+ s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*@k,(@h-y)*@k,txt);
565
+ s=s+' '+dounderline(x,y,txt) if @underline and txt!=''
566
+ s='q '+@TextColor+' '+s+' Q' if @ColorFlag
567
+ out(s)
568
+ end
569
+
570
+ def AcceptPageBreak
571
+ # Accept automatic page break or not
572
+ @AutoPageBreak
573
+ end
574
+
575
+ def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
576
+ # Output a cell
577
+ if @y+h>@PageBreakTrigger and !@InFooter and self.AcceptPageBreak
578
+ # Automatic page break
579
+ x=@x
580
+ ws=@ws
581
+ if ws>0
582
+ @ws=0
583
+ out('0 Tw')
584
+ end
585
+ self.AddPage(@CurOrientation)
586
+ @x=x
587
+ if ws>0
588
+ @ws=ws
589
+ out(sprintf('%.3f Tw',ws*@k))
590
+ end
591
+ end
592
+ w=@w-@rMargin-@x if w==0
593
+ s=''
594
+ if fill==1 or border==1
595
+ if fill==1
596
+ op=(border==1) ? 'B' : 'f'
597
+ else
598
+ op='S'
599
+ end
600
+ s=sprintf('%.2f %.2f %.2f %.2f re %s ',@x*@k,(@h-@y)*@k,w*@k,-h*@k,op)
601
+ end
602
+ if border.is_a? String
603
+ x=@x
604
+ y=@y
605
+ unless border.index('L').nil?
606
+ s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
607
+ x*@k,(@h-y)*@k,x*@k,(@h-(y+h))*@k)
608
+ end
609
+ unless border.index('T').nil?
610
+ s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
611
+ x*@k,(@h-y)*@k,(x+w)*@k,(@h-y)*@k)
612
+ end
613
+ unless border.index('R').nil?
614
+ s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
615
+ (x+w)*@k,(@h-y)*@k,(x+w)*@k,(@h-(y+h))*@k)
616
+ end
617
+ unless border.index('B').nil?
618
+ s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
619
+ x*@k,(@h-(y+h))*@k,(x+w)*@k,(@h-(y+h))*@k)
620
+ end
621
+ end
622
+ if txt!=''
623
+ if align=='R'
624
+ dx=w-@cMargin-self.GetStringWidth(txt)
625
+ elsif align=='C'
626
+ dx=(w-self.GetStringWidth(txt))/2
627
+ else
628
+ dx=@cMargin
629
+ end
630
+ txt = txt.gsub(')', '\\)')
631
+ txt.gsub!('(', '\\(')
632
+ txt.gsub!('\\', '\\\\')
633
+ if @ColorFlag
634
+ s=s+'q '+@TextColor+' '
635
+ end
636
+ s=s+sprintf('BT %.2f %.2f Td (%s) Tj ET',
637
+ (@x+dx)*@k,(@h-(@y+0.5*h+0.3*@FontSize))*@k,txt)
638
+ s=s+' '+dounderline(@x+dx,@y+0.5*h+0.3*@FontSize,txt) if @underline
639
+ s=s+' Q' if @ColorFlag
640
+ if link and link != ''
641
+ Link(@x+dx,@y+0.5*h-0.5*@FontSize,GetStringWidth(txt),@FontSize,link)
642
+ end
643
+ end
644
+ out(s) if s
645
+ @lasth=h
646
+ if ln>0
647
+ # Go to next line
648
+ @y=@y+h
649
+ @x=@lMargin if ln==1
650
+ else
651
+ @x=@x+w
652
+ end
653
+ end
654
+
655
+ def MultiCell(w,h,txt,border=0,align='J',fill=0)
656
+ # Output text with automatic or explicit line breaks
657
+ cw=@CurrentFont['cw']
658
+ w=@w-@rMargin-@x if w==0
659
+ wmax=(w-2*@cMargin)*1000/@FontSize
660
+ s=txt.gsub('\r','')
661
+ nb=s.length
662
+ nb=nb-1 if nb>0 and s[nb-1].chr=='\n'
663
+ b=0
664
+ if border!=0
665
+ if border==1
666
+ border='LTRB'
667
+ b='LRT'
668
+ b2='LR'
669
+ else
670
+ b2=''
671
+ b2='L' unless border.index('L').nil?
672
+ b2=b2+'R' unless border.index('R').nil?
673
+ b=(not border.index('T').nil?) ? (b2+'T') : b2
674
+ end
675
+ end
676
+ sep=-1
677
+ i=0
678
+ j=0
679
+ l=0
680
+ ns=0
681
+ nl=1
682
+ while i<nb
683
+ # Get next character
684
+ c=s[i].chr
685
+ if c=="\n"
686
+ # Explicit line break
687
+ if @ws>0
688
+ @ws=0
689
+ out('0 Tw')
690
+ end
691
+
692
+ # Changed from s[j..i] to fix bug reported by Hans Allis.
693
+ self.Cell(w,h,s[j..i-1],b,2,align,fill)
694
+
695
+ i=i+1
696
+ sep=-1
697
+ j=i
698
+ l=0
699
+ ns=0
700
+ nl=nl+1
701
+ b=b2 if border and nl==2
702
+ else
703
+ if c==' '
704
+ sep=i
705
+ ls=l
706
+ ns=ns+1
707
+ end
708
+ l=l+cw[c[0]]
709
+ if l>wmax
710
+ # Automatic line break
711
+ if sep==-1
712
+ i=i+1 if i==j
713
+ if @ws>0
714
+ @ws=0
715
+ out('0 Tw')
716
+ end
717
+ self.Cell(w,h,s[j..i],b,2,align,fill)
718
+ else
719
+ if align=='J'
720
+ @ws=(ns>1) ? (wmax-ls)/1000.0*@FontSize/(ns-1) : 0
721
+ out(sprintf('%.3f Tw',@ws*@k))
722
+ end
723
+ self.Cell(w,h,s[j..sep],b,2,align,fill)
724
+ i=sep+1
725
+ end
726
+ sep=-1
727
+ j=i
728
+ l=0
729
+ ns=0
730
+ nl=nl+1
731
+ b=b2 if border and nl==2
732
+ else
733
+ i=i+1
734
+ end
735
+ end
736
+ end
737
+
738
+ # Last chunk
739
+ if @ws>0
740
+ @ws=0
741
+ out('0 Tw')
742
+ end
743
+ b=b+'B' if border!=0 and not border.index('B').nil?
744
+ self.Cell(w,h,s[j..i],b,2,align,fill)
745
+ @x=@lMargin
746
+ end
747
+
748
+ def Write(h,txt,link='')
749
+ # Output text in flowing mode
750
+ cw=@CurrentFont['cw']
751
+ w=@w-@rMargin-@x
752
+ wmax=(w-2*@cMargin)*1000/@FontSize
753
+ s=txt.gsub("\r",'')
754
+ nb=s.length
755
+ sep=-1
756
+ i=0
757
+ j=0
758
+ l=0
759
+ nl=1
760
+ while i<nb
761
+ # Get next character
762
+ c=s[i]
763
+ if c=="\n"[0]
764
+ # Explicit line break
765
+ self.Cell(w,h,s[j,i-j],0,2,'',0,link)
766
+ i=i+1
767
+ sep=-1
768
+ j=i
769
+ l=0
770
+ if nl==1
771
+ @x=@lMargin
772
+ w=@w-@rMargin-@x
773
+ wmax=(w-2*@cMargin)*1000/@FontSize
774
+ end
775
+ nl=nl+1
776
+ next
777
+ end
778
+ if c==' '[0]
779
+ sep=i
780
+ ls=l
781
+ end
782
+ l=l+cw[c];
783
+ if l>wmax
784
+ # Automatic line break
785
+ if sep==-1
786
+ if @x>@lMargin
787
+ # Move to next line
788
+ @x=@lMargin
789
+ @y=@y+h
790
+ w=@w-@rMargin-@x
791
+ wmax=(w-2*@cMargin)*1000/@FontSize
792
+ i=i+1
793
+ nl=nl+1
794
+ next
795
+ end
796
+ i=i+1 if i==j
797
+ self.Cell(w,h,s[j,i-j],0,2,'',0,link)
798
+ else
799
+ self.Cell(w,h,s[j,sep-j],0,2,'',0,link)
800
+ i=sep+1
801
+ end
802
+ sep=-1
803
+ j=i
804
+ l=0
805
+ if nl==1
806
+ @x=@lMargin
807
+ w=@w-@rMargin-@x
808
+ wmax=(w-2*@cMargin)*1000/@FontSize
809
+ end
810
+ nl=nl+1
811
+ else
812
+ i=i+1
813
+ end
814
+ end
815
+ # Last chunk
816
+ self.Cell(l/1000.0*@FontSize,h,s[j,i],0,0,'',0,link) if i!=j
817
+ end
818
+
819
+ def Image(file,x,y,w,h=0,type='',link='')
820
+ # Put an image on the page
821
+ unless @images.has_key?(file)
822
+ # First use of image, get info
823
+ if type==''
824
+ pos=file.rindex('.')
825
+ if pos.nil?
826
+ self.Error('Image file has no extension and no type was '+
827
+ 'specified: '+file)
828
+ end
829
+ type=file[pos+1..-1]
830
+ end
831
+ type.downcase!
832
+ if type=='jpg' or type=='jpeg'
833
+ info=parsejpg(file)
834
+ elsif type=='png'
835
+ info=parsepng(file)
836
+ else
837
+ self.Error('Unsupported image file type: '+type)
838
+ end
839
+ info['i']=@images.length+1
840
+ @images[file]=info
841
+ else
842
+ info=@images[file]
843
+ end
844
+ # Automatic width or height calculation
845
+ w=h*info['w']/info['h'] if w==0
846
+ h=w*info['h']/info['w'] if h==0
847
+ out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',
848
+ w*@k,h*@k,x*@k,(@h-(y+h))*@k,info['i']))
849
+ Link(x,y,w,h,link) if link and link != ''
850
+ end
851
+
852
+ def Ln(h='')
853
+ # Line feed; default value is last cell height
854
+ @x=@lMargin
855
+ if h.kind_of?(String)
856
+ @y=@y+@lasth
857
+ else
858
+ @y=@y+h
859
+ end
860
+ end
861
+
862
+ def GetX
863
+ # Get x position
864
+ @x
865
+ end
866
+
867
+ def SetX(x)
868
+ # Set x position
869
+ if x>=0
870
+ @x=x
871
+ else
872
+ @x=@w+x
873
+ end
874
+ end
875
+
876
+ def GetY
877
+ # Get y position
878
+ @y
879
+ end
880
+
881
+ def SetY(y)
882
+ # Set y position and reset x
883
+ @x=@lMargin
884
+ if y>=0
885
+ @y=y
886
+ else
887
+ @y=@h+y
888
+ end
889
+ end
890
+
891
+ def SetXY(x,y)
892
+ # Set x and y positions
893
+ SetY(y)
894
+ SetX(x)
895
+ end
896
+
897
+ def Output(file=nil)
898
+ # Output PDF to file or return as a string
899
+
900
+ # Finish document if necessary
901
+ self.Close if(@state<3)
902
+
903
+ if file.nil?
904
+ # Return as a string
905
+ return @buffer
906
+ else
907
+ # Save file locally
908
+ open(file,'wb') do |f|
909
+ f.write(@buffer)
910
+ end
911
+ end
912
+ end
913
+
914
+ private
915
+
916
+ def putpages
917
+ nb=@page
918
+ unless @AliasNbPages.nil? or @AliasNbPages==''
919
+ # Replace number of pages
920
+ 1.upto(nb) do |n|
921
+ @pages[n].gsub!(@AliasNbPages,nb.to_s)
922
+ end
923
+ end
924
+ if @DefOrientation=='P'
925
+ wPt=@fwPt
926
+ hPt=@fhPt
927
+ else
928
+ wPt=@fhPt
929
+ hPt=@fwPt
930
+ end
931
+ filter=(@compress) ? '/Filter /FlateDecode ' : ''
932
+ 1.upto(nb) do |n|
933
+ # Page
934
+ newobj
935
+ out('<</Type /Page')
936
+ out('/Parent 1 0 R')
937
+ unless @OrientationChanges[n].nil?
938
+ out(sprintf('/MediaBox [0 0 %.2f %.2f]',hPt,wPt))
939
+ end
940
+ out('/Resources 2 0 R')
941
+ if @PageLinks[n]
942
+ # Links
943
+ annots='/Annots ['
944
+ @PageLinks[n].each do |pl|
945
+ rect=sprintf('%.2f %.2f %.2f %.2f',
946
+ pl[0],pl[1],pl[0]+pl[2],pl[1]-pl[3])
947
+ annots=annots+'<</Type /Annot /Subtype /Link /Rect ['+rect+
948
+ '] /Border [0 0 0] '
949
+ if pl[4].kind_of?(String)
950
+ annots=annots+'/A <</S /URI /URI '+textstring(pl[4])+
951
+ '>>>>'
952
+ else
953
+ l=@links[pl[4]]
954
+ h=@OrientationChanges[l[0]].nil? ? hPt : wPt
955
+ annots=annots+sprintf(
956
+ '/Dest [%d 0 R /XYZ 0 %.2f null]>>',
957
+ 1+2*l[0],h-l[1]*@k)
958
+ end
959
+ end
960
+ out(annots+']')
961
+ end
962
+ out('/Contents '+(@n+1).to_s+' 0 R>>')
963
+ out('endobj')
964
+ # Page content
965
+ p=(@compress) ? Zlib::Deflate.deflate(@pages[n]) : @pages[n]
966
+ newobj
967
+ out('<<'+filter+'/Length '+p.length.to_s+'>>')
968
+ putstream(p)
969
+ out('endobj')
970
+ end
971
+ # Pages root
972
+ @offsets[1]=@buffer.length
973
+ out('1 0 obj')
974
+ out('<</Type /Pages')
975
+ kids='/Kids ['
976
+ nb.times do |i|
977
+ kids=kids+(3+2*i).to_s+' 0 R '
978
+ end
979
+ out(kids+']')
980
+ out('/Count '+nb.to_s)
981
+ out(sprintf('/MediaBox [0 0 %.2f %.2f]',wPt,hPt))
982
+ out('>>')
983
+ out('endobj')
984
+ end
985
+
986
+ def putfonts
987
+ nf=@n
988
+ @diffs.each do |diff|
989
+ # Encodings
990
+ newobj
991
+ out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences '+
992
+ '['+diff+']>>')
993
+ out('endobj')
994
+ end
995
+
996
+ @FontFiles.each do |file, info|
997
+ # Font file embedding
998
+ newobj
999
+ @FontFiles[file]['n'] = @n
1000
+
1001
+ if self.class.const_defined? 'FPDF_FONTPATH' then
1002
+ if FPDF_FONTPATH[-1,1] == '/' then
1003
+ file = FPDF_FONTPATH + file
1004
+ else
1005
+ file = FPDF_FONTPATH + '/' + file
1006
+ end
1007
+ end
1008
+
1009
+ size = File.size(file)
1010
+ unless File.exists?(file)
1011
+ Error('Font file not found')
1012
+ end
1013
+
1014
+ out('<</Length ' + size.to_s)
1015
+
1016
+ if file[-2, 2] == '.z' then
1017
+ out('/Filter /FlateDecode')
1018
+ end
1019
+ out('/Length1 ' + info['length1'])
1020
+ out('/Length2 ' + info['length2'] + ' /Length3 0') if info['length2']
1021
+ out('>>')
1022
+ open(file, 'rb') do |f|
1023
+ putstream(f.read())
1024
+ end
1025
+ out('endobj')
1026
+ end
1027
+
1028
+ file = 0
1029
+ @fonts.each do |k, font|
1030
+ # Font objects
1031
+ @fonts[k]['n']=@n+1
1032
+ type=font['type']
1033
+ name=font['name']
1034
+ if type=='core'
1035
+ # Standard font
1036
+ newobj
1037
+ out('<</Type /Font')
1038
+ out('/BaseFont /'+name)
1039
+ out('/Subtype /Type1')
1040
+ if name!='Symbol' and name!='ZapfDingbats'
1041
+ out('/Encoding /WinAnsiEncoding')
1042
+ end
1043
+ out('>>')
1044
+ out('endobj')
1045
+ elsif type=='Type1' or type=='TrueType'
1046
+ # Additional Type1 or TrueType font
1047
+ newobj
1048
+ out('<</Type /Font')
1049
+ out('/BaseFont /'+name)
1050
+ out('/Subtype /'+type)
1051
+ out('/FirstChar 32 /LastChar 255')
1052
+ out('/Widths '+(@n+1).to_s+' 0 R')
1053
+ out('/FontDescriptor '+(@n+2).to_s+' 0 R')
1054
+ if font['enc'] and font['enc'] != ''
1055
+ unless font['diff'].nil?
1056
+ out('/Encoding '+(nf+font['diff']).to_s+' 0 R')
1057
+ else
1058
+ out('/Encoding /WinAnsiEncoding')
1059
+ end
1060
+ end
1061
+ out('>>')
1062
+ out('endobj')
1063
+ # Widths
1064
+ newobj
1065
+ cw=font['cw']
1066
+ s='['
1067
+ 32.upto(255) do |i|
1068
+ s << cw[i].to_s+' '
1069
+ end
1070
+ out(s+']')
1071
+ out('endobj')
1072
+ # Descriptor
1073
+ newobj
1074
+ s='<</Type /FontDescriptor /FontName /'+name
1075
+ font['desc'].each do |k, v|
1076
+ s << ' /'+k+' '+v
1077
+ end
1078
+ file=font['file']
1079
+ if file
1080
+ s << ' /FontFile'+(type=='Type1' ? '' : '2')+' '+
1081
+ @FontFiles[file]['n'].to_s+' 0 R'
1082
+ end
1083
+ out(s+'>>')
1084
+ out('endobj')
1085
+ else
1086
+ # Allow for additional types
1087
+ mtd='put'+type.downcase
1088
+ unless self.respond_to?(mtd)
1089
+ self.Error('Unsupported font type: '+type)
1090
+ end
1091
+ self.send(mtd, font)
1092
+ end
1093
+ end
1094
+ end
1095
+
1096
+ def putimages
1097
+ filter=(@compress) ? '/Filter /FlateDecode ' : ''
1098
+ @images.each do |file, info|
1099
+ newobj
1100
+ @images[file]['n']=@n
1101
+ out('<</Type /XObject')
1102
+ out('/Subtype /Image')
1103
+ out('/Width '+info['w'].to_s)
1104
+ out('/Height '+info['h'].to_s)
1105
+ if info['cs']=='Indexed'
1106
+ out("/ColorSpace [/Indexed /DeviceRGB #{info['pal'].length/3-1} #{(@n+1)} 0 R]")
1107
+ else
1108
+ out('/ColorSpace /'+info['cs'])
1109
+ if info['cs']=='DeviceCMYK'
1110
+ out('/Decode [1 0 1 0 1 0 1 0]')
1111
+ end
1112
+ end
1113
+ out('/BitsPerComponent '+info['bpc'].to_s)
1114
+ out('/Filter /'+info['f']) if info['f']
1115
+ unless info['parms'].nil?
1116
+ out(info['parms'])
1117
+ end
1118
+ if info['trns'] and info['trns'].kind_of?(Array)
1119
+ trns=''
1120
+ info['trns'].length.times do |i|
1121
+ trns=trns+info['trns'][i].to_s+' '+info['trns'][i].to_s+' '
1122
+ end
1123
+ out('/Mask ['+trns+']')
1124
+ end
1125
+ out('/Length '+info['data'].length.to_s+'>>')
1126
+ putstream(info['data'])
1127
+ @images[file]['data']=nil
1128
+ out('endobj')
1129
+ # Palette
1130
+ if info['cs']=='Indexed'
1131
+ newobj
1132
+ pal=(@compress) ? Zlib::Deflate.deflate(info['pal']) : info['pal']
1133
+ out('<<'+filter+'/Length '+pal.length.to_s+'>>')
1134
+ putstream(pal)
1135
+ out('endobj')
1136
+ end
1137
+ end
1138
+ end
1139
+
1140
+ def putxobjectdict
1141
+ @images.each_value do |image|
1142
+ out('/I'+image['i'].to_s+' '+image['n'].to_s+' 0 R')
1143
+ end
1144
+ end
1145
+
1146
+ def putresourcedict
1147
+ out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
1148
+ out('/Font <<')
1149
+ @fonts.each_value do |font|
1150
+ out('/F'+font['i'].to_s+' '+font['n'].to_s+' 0 R')
1151
+ end
1152
+ out('>>')
1153
+ out('/XObject <<')
1154
+ putxobjectdict
1155
+ out('>>')
1156
+ end
1157
+
1158
+ def putresources
1159
+ putfonts
1160
+ putimages
1161
+ # Resource dictionary
1162
+ @offsets[2]=@buffer.length
1163
+ out('2 0 obj')
1164
+ out('<<')
1165
+ putresourcedict
1166
+ out('>>')
1167
+ out('endobj')
1168
+ end
1169
+
1170
+ def putinfo
1171
+ out('/Producer '+textstring('Ruby FPDF '+FPDF_VERSION));
1172
+ unless @title.nil?
1173
+ out('/Title '+textstring(@title))
1174
+ end
1175
+ unless @subject.nil?
1176
+ out('/Subject '+textstring(@subject))
1177
+ end
1178
+ unless @author.nil?
1179
+ out('/Author '+textstring(@author))
1180
+ end
1181
+ unless @keywords.nil?
1182
+ out('/Keywords '+textstring(@keywords))
1183
+ end
1184
+ unless @creator.nil?
1185
+ out('/Creator '+textstring(@creator))
1186
+ end
1187
+ out('/CreationDate '+textstring('D: '+DateTime.now.to_s))
1188
+ end
1189
+
1190
+ def putcatalog
1191
+ out('/Type /Catalog')
1192
+ out('/Pages 1 0 R')
1193
+ if @ZoomMode=='fullpage'
1194
+ out('/OpenAction [3 0 R /Fit]')
1195
+ elsif @ZoomMode=='fullwidth'
1196
+ out('/OpenAction [3 0 R /FitH null]')
1197
+ elsif @ZoomMode=='real'
1198
+ out('/OpenAction [3 0 R /XYZ null null 1]')
1199
+ elsif not @ZoomMode.kind_of?(String)
1200
+ out('/OpenAction [3 0 R /XYZ null null '+(@ZoomMode/100)+']')
1201
+ end
1202
+
1203
+ if @LayoutMode=='single'
1204
+ out('/PageLayout /SinglePage')
1205
+ elsif @LayoutMode=='continuous'
1206
+ out('/PageLayout /OneColumn')
1207
+ elsif @LayoutMode=='two'
1208
+ out('/PageLayout /TwoColumnLeft')
1209
+ end
1210
+ end
1211
+
1212
+ def putheader
1213
+ out('%PDF-'+@PDFVersion)
1214
+ end
1215
+
1216
+ def puttrailer
1217
+ out('/Size '+(@n+1).to_s)
1218
+ out('/Root '+@n.to_s+' 0 R')
1219
+ out('/Info '+(@n-1).to_s+' 0 R')
1220
+ end
1221
+
1222
+ def enddoc
1223
+ putheader
1224
+ putpages
1225
+ putresources
1226
+ # Info
1227
+ newobj
1228
+ out('<<')
1229
+ putinfo
1230
+ out('>>')
1231
+ out('endobj')
1232
+ # Catalog
1233
+ newobj
1234
+ out('<<')
1235
+ putcatalog
1236
+ out('>>')
1237
+ out('endobj')
1238
+ # Cross-ref
1239
+ o=@buffer.length
1240
+ out('xref')
1241
+ out('0 '+(@n+1).to_s)
1242
+ out('0000000000 65535 f ')
1243
+ 1.upto(@n) do |i|
1244
+ out(sprintf('%010d 00000 n ',@offsets[i]))
1245
+ end
1246
+ # Trailer
1247
+ out('trailer')
1248
+ out('<<')
1249
+ puttrailer
1250
+ out('>>')
1251
+ out('startxref')
1252
+ out(o)
1253
+ out('%%EOF')
1254
+ state=3
1255
+ end
1256
+
1257
+ def beginpage(orientation)
1258
+ @page=@page+1
1259
+ @pages[@page]=''
1260
+ @state=2
1261
+ @x=@lMargin
1262
+ @y=@tMargin
1263
+ @lasth=0
1264
+ @FontFamily=''
1265
+ # Page orientation
1266
+ if orientation==''
1267
+ orientation=@DefOrientation
1268
+ else
1269
+ orientation=orientation[0].chr.upcase
1270
+ if orientation!=@DefOrientation
1271
+ @OrientationChanges[@page]=true
1272
+ end
1273
+ end
1274
+ if orientation!=@CurOrientation
1275
+ # Change orientation
1276
+ if orientation=='P'
1277
+ @wPt=@fwPt
1278
+ @hPt=@fhPt
1279
+ @w=@fw
1280
+ @h=@fh
1281
+ else
1282
+ @wPt=@fhPt
1283
+ @hPt=@fwPt
1284
+ @w=@fh
1285
+ @h=@fw
1286
+ end
1287
+ @PageBreakTrigger=@h-@bMargin
1288
+ @CurOrientation=orientation
1289
+ end
1290
+ end
1291
+
1292
+ def endpage
1293
+ # End of page contents
1294
+ @state=1
1295
+ end
1296
+
1297
+ def newobj
1298
+ # Begin a new object
1299
+ @n=@n+1
1300
+ @offsets[@n]=@buffer.length
1301
+ out(@n.to_s+' 0 obj')
1302
+ end
1303
+
1304
+ def dounderline(x,y,txt)
1305
+ # Underline text
1306
+ up=@CurrentFont['up']
1307
+ ut=@CurrentFont['ut']
1308
+ w=GetStringWidth(txt)+@ws*txt.count(' ')
1309
+ sprintf('%.2f %.2f %.2f %.2f re f',
1310
+ x*@k,(@h-(y-up/1000.0*@FontSize))*@k,w*@k,-ut/1000.0*@FontSizePt)
1311
+ end
1312
+
1313
+ def parsejpg(file)
1314
+ # Extract info from a JPEG file
1315
+ a=extractjpginfo(file)
1316
+ raise "Missing or incorrect JPEG file: #{file}" if a.nil?
1317
+
1318
+ if a['channels'].nil? || a['channels']==3 then
1319
+ colspace='DeviceRGB'
1320
+ elsif a['channels']==4 then
1321
+ colspace='DeviceCMYK'
1322
+ else
1323
+ colspace='DeviceGray'
1324
+ end
1325
+ bpc= a['bits'] ? a['bits'].to_i : 8
1326
+
1327
+ # Read whole file
1328
+ data = nil
1329
+ open(file, 'rb') do |f|
1330
+ data = f.read
1331
+ end
1332
+ return {'w'=>a['width'],'h'=>a['height'],'cs'=>colspace,'bpc'=>bpc,'f'=>'DCTDecode','data'=>data}
1333
+ end
1334
+
1335
+ def parsepng(file)
1336
+ # Extract info from a PNG file
1337
+ f=open(file,'rb')
1338
+ # Check signature
1339
+ unless f.read(8)==137.chr+'PNG'+13.chr+10.chr+26.chr+10.chr
1340
+ self.Error('Not a PNG file: '+file)
1341
+ end
1342
+ # Read header chunk
1343
+ f.read(4)
1344
+ if f.read(4)!='IHDR'
1345
+ self.Error('Incorrect PNG file: '+file)
1346
+ end
1347
+ w=freadint(f)
1348
+ h=freadint(f)
1349
+ bpc=f.read(1)[0]
1350
+ if bpc>8
1351
+ self.Error('16-bit depth not supported: '+file)
1352
+ end
1353
+ ct=f.read(1)[0]
1354
+ if ct==0
1355
+ colspace='DeviceGray'
1356
+ elsif ct==2
1357
+ colspace='DeviceRGB'
1358
+ elsif ct==3
1359
+ colspace='Indexed'
1360
+ else
1361
+ self.Error('Alpha channel not supported: '+file)
1362
+ end
1363
+ if f.read(1)[0]!=0
1364
+ self.Error('Unknown compression method: '+file)
1365
+ end
1366
+ if f.read(1)[0]!=0
1367
+ self.Error('Unknown filter method: '+file)
1368
+ end
1369
+ if f.read(1)[0]!=0
1370
+ self.Error('Interlacing not supported: '+file)
1371
+ end
1372
+ f.read(4)
1373
+ parms='/DecodeParms <</Predictor 15 /Colors '+(ct==2 ? '3' : '1')+
1374
+ ' /BitsPerComponent '+bpc.to_s+' /Columns '+w.to_s+'>>'
1375
+ # Scan chunks looking for palette, transparency and image data
1376
+ pal=''
1377
+ trns=''
1378
+ data=''
1379
+ begin
1380
+ n=freadint(f)
1381
+ type=f.read(4)
1382
+ if type=='PLTE'
1383
+ # Read palette
1384
+ pal=f.read(n)
1385
+ f.read(4)
1386
+ elsif type=='tRNS'
1387
+ # Read transparency info
1388
+ t=f.read(n)
1389
+ if ct==0
1390
+ trns=[t[1]]
1391
+ elsif ct==2
1392
+ trns=[t[1],t[3],t[5]]
1393
+ else
1394
+ pos=t.index(0)
1395
+ trns=[pos] unless pos.nil?
1396
+ end
1397
+ f.read(4)
1398
+ elsif type=='IDAT'
1399
+ # Read image data block
1400
+ data << f.read(n)
1401
+ f.read(4)
1402
+ elsif type=='IEND'
1403
+ break
1404
+ else
1405
+ f.read(n+4)
1406
+ end
1407
+ end while n
1408
+ if colspace=='Indexed' and pal==''
1409
+ self.Error('Missing palette in '+file)
1410
+ end
1411
+ f.close
1412
+ {'w'=>w,'h'=>h,'cs'=>colspace,'bpc'=>bpc,'f'=>'FlateDecode',
1413
+ 'parms'=>parms,'pal'=>pal,'trns'=>trns,'data'=>data}
1414
+ end
1415
+
1416
+ def freadint(f)
1417
+ # Read a 4-byte integer from file
1418
+ a = f.read(4).unpack('N')
1419
+ return a[0]
1420
+ end
1421
+
1422
+ def freadshort(f)
1423
+ a = f.read(2).unpack('n')
1424
+ return a[0]
1425
+ end
1426
+
1427
+ def freadbyte(f)
1428
+ a = f.read(1).unpack('C')
1429
+ return a[0]
1430
+ end
1431
+
1432
+ def textstring(s)
1433
+ # Format a text string
1434
+ '('+escape(s)+')'
1435
+ end
1436
+
1437
+ def escape(s)
1438
+ # Add \ before \, ( and )
1439
+ s.gsub('\\','\\\\').gsub('(','\\(').gsub(')','\\)')
1440
+ end
1441
+
1442
+ def putstream(s)
1443
+ out('stream')
1444
+ out(s)
1445
+ out('endstream')
1446
+ end
1447
+
1448
+ def out(s)
1449
+ # Add a line to the document
1450
+ if @state==2
1451
+ @pages[@page]=@pages[@page]+s+"\n"
1452
+ else
1453
+ @buffer=@buffer+s.to_s+"\n"
1454
+ end
1455
+ end
1456
+
1457
+ # jpeg marker codes
1458
+
1459
+ M_SOF0 = 0xc0
1460
+ M_SOF1 = 0xc1
1461
+ M_SOF2 = 0xc2
1462
+ M_SOF3 = 0xc3
1463
+
1464
+ M_SOF5 = 0xc5
1465
+ M_SOF6 = 0xc6
1466
+ M_SOF7 = 0xc7
1467
+
1468
+ M_SOF9 = 0xc9
1469
+ M_SOF10 = 0xca
1470
+ M_SOF11 = 0xcb
1471
+
1472
+ M_SOF13 = 0xcd
1473
+ M_SOF14 = 0xce
1474
+ M_SOF15 = 0xcf
1475
+
1476
+ M_SOI = 0xd8
1477
+ M_EOI = 0xd9
1478
+ M_SOS = 0xda
1479
+
1480
+ def extractjpginfo(file)
1481
+ result = nil
1482
+
1483
+ open(file, "rb") do |f|
1484
+ marker = jpegnextmarker(f)
1485
+
1486
+ if marker != M_SOI
1487
+ return nil
1488
+ end
1489
+
1490
+ while true
1491
+ marker = jpegnextmarker(f)
1492
+
1493
+ case marker
1494
+ when M_SOF0, M_SOF1, M_SOF2, M_SOF3,
1495
+ M_SOF5, M_SOF6, M_SOF7, M_SOF9,
1496
+ M_SOF10, M_SOF11, M_SOF13, M_SOF14,
1497
+ M_SOF15 then
1498
+
1499
+ length = freadshort(f)
1500
+
1501
+ if result.nil?
1502
+ result = {}
1503
+
1504
+ result['bits'] = freadbyte(f)
1505
+ result['height'] = freadshort(f)
1506
+ result['width'] = freadshort(f)
1507
+ result['channels'] = freadbyte(f)
1508
+
1509
+ f.seek(length - 8, IO::SEEK_CUR)
1510
+ else
1511
+ f.seek(length - 2, IO::SEEK_CUR)
1512
+ end
1513
+ when M_SOS, M_EOI then
1514
+ return result
1515
+ else
1516
+ length = freadshort(f)
1517
+ f.seek(length - 2, IO::SEEK_CUR)
1518
+ end
1519
+ end
1520
+ end
1521
+ end
1522
+
1523
+ def jpegnextmarker(f)
1524
+ while true
1525
+ # look for 0xff
1526
+ while (c = freadbyte(f)) != 0xff
1527
+ end
1528
+
1529
+ c = freadbyte(f)
1530
+
1531
+ if c != 0
1532
+ return c
1533
+ end
1534
+ end
1535
+ end
1536
+ end