fpdf 1.53

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