# Ruby FPDF 1.53d # FPDF 1.53 by Olivier Plathey ported to Ruby by Brian Ollenberger # Copyright 2005 Brian Ollenberger # Please retain this entire copyright notice. If you distribute any # modifications, place an additional comment here that clearly indicates # that it was modified. You may (but are not send any useful modifications that you make # back to me at http://zeropluszero.com/software/fpdf/ # Bug fixes, examples, external fonts, JPEG support, and upgrade to version # 1.53 contributed by Kim Shrier. # # Bookmark support contributed by Sylvain Lafleur. # # EPS support contributed by Thiago Jackiw, ported from the PHP version by Valentin Schmidt. # # Many other bug reports and fixes contributed by many other people. require 'date' require 'zlib' class FPDF FPDF_VERSION = '1.53d' Charwidths = { '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], '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], '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], '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], '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], '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], '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], '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], '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], '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], '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], '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], '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], '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] } def initialize(orientation='P', unit='mm', format='A4') # Initialization of properties @page=0 @n=2 @buffer='' @pages=[] @OrientationChanges=[] @state=0 @fonts={} @FontFiles={} @diffs=[] @images={} @links=[] @PageLinks={} @InFooter=false @FontFamily='' @FontStyle='' @FontSizePt=12 @underline= false @DrawColor='0 G' @FillColor='0 g' @TextColor='0 g' @ColorFlag=false @ws=0 @offsets=[] # Standard fonts @CoreFonts={} @CoreFonts['courier']='Courier' @CoreFonts['courierB']='Courier-Bold' @CoreFonts['courierI']='Courier-Oblique' @CoreFonts['courierBI']='Courier-BoldOblique' @CoreFonts['helvetica']='Helvetica' @CoreFonts['helveticaB']='Helvetica-Bold' @CoreFonts['helveticaI']='Helvetica-Oblique' @CoreFonts['helveticaBI']='Helvetica-BoldOblique' @CoreFonts['times']='Times-Roman' @CoreFonts['timesB']='Times-Bold' @CoreFonts['timesI']='Times-Italic' @CoreFonts['timesBI']='Times-BoldItalic' @CoreFonts['symbol']='Symbol' @CoreFonts['zapfdingbats']='ZapfDingbats' # Scale factor if unit=='pt' @k=1 elsif unit=='mm' @k=72/25.4 elsif unit=='cm' @k=72/2.54; elsif unit=='in' @k=72 else raise 'Incorrect unit: '+unit end # Page format if format.is_a? String format.downcase! if format=='a3' format=[841.89,1190.55] elsif format=='a4' format=[595.28,841.89] elsif format=='a5' format=[420.94,595.28] elsif format=='letter' format=[612,792] elsif format=='legal' format=[612,1008] else raise 'Unknown page format: '+format end @fwPt,@fhPt=format else @fwPt=format[0]*@k @fhPt=format[1]*@k end @fw=@fwPt/@k; @fh=@fhPt/@k; # Page orientation orientation.downcase! if orientation=='p' or orientation=='portrait' @DefOrientation='P' @wPt=@fwPt @hPt=@fhPt elsif orientation=='l' or orientation=='landscape' @DefOrientation='L' @wPt=@fhPt @hPt=@fwPt else raise 'Incorrect orientation: '+orientation end @CurOrientation=@DefOrientation @w=@wPt/@k @h=@hPt/@k # Page margins (1 cm) margin=28.35/@k SetMargins(margin,margin) # Interior cell margin (1 mm) @cMargin=margin/10 # Line width (0.2 mm) @LineWidth=0.567/@k # Automatic page break SetAutoPageBreak(true,2*margin) # Full width display mode SetDisplayMode('fullwidth') # Enable compression SetCompression(true) # Set default PDF version number @PDFVersion='1.3' end def SetMargins(left, top, right=-1) # Set left, top and right margins @lMargin=left @tMargin=top right=left if right==-1 @rMargin=right end def SetLeftMargin(margin) # Set left margin @lMargin=margin @x=margin if @page>0 and @x0 # Page footer @InFooter=true self.Footer @InFooter=false # Close page endpage end # Start new page beginpage(orientation) # Set line cap style to square out('2 J') # Set line width @LineWidth=lw out(sprintf('%.2f w',lw*@k)) # Set font SetFont(family,style,size) if family # Set colors @DrawColor=dc out(dc) if dc!='0 G' @FillColor=fc out(fc) if fc!='0 g' @TextColor=tc @ColorFlag=cf # Page header self.Header # Restore line width if @LineWidth!=lw @LineWidth=lw out(sprintf('%.2f w',lw*@k)) end # Restore font self.SetFont(family,style,size) if family # Restore colors if @DrawColor!=dc @DrawColor=dc out(dc) end if @FillColor!=fc @FillColor=fc out(fc) end @TextColor=tc @ColorFlag=cf end def Header # To be implemented in your inherited class end def Footer # To be implemented in your inherited class end def PageNo # Get current page number @page end def SetDrawColor(r,g=-1,b=-1) # Set color for all stroking operations if (r==0 and g==0 and b==0) or g==-1 @DrawColor=sprintf('%.3f G',r/255.0) else @DrawColor=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0) end out(@DrawColor) if(@page>0) end def SetFillColor(r,g=-1,b=-1) # Set color for all filling operations if (r==0 and g==0 and b==0) or g==-1 @FillColor=sprintf('%.3f g',r/255.0) else @FillColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0) end @ColorFlag=(@FillColor!=@TextColor) out(@FillColor) if(@page>0) end def SetTextColor(r,g=-1,b=-1) # Set color for text if (r==0 and g==0 and b==0) or g==-1 @TextColor=sprintf('%.3f g',r/255.0) else @TextColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0) end @ColorFlag=(@FillColor!=@TextColor) end def GetStringWidth(s) # Get width of a string in the current font cw=@CurrentFont['cw'] w=0 s.each_byte do |c| w=w+cw[c] end w*@FontSize/1000.0 end def SetLineWidth(width) # Set line width @LineWidth=width out(sprintf('%.2f w',width*@k)) if @page>0 end def Line(x1, y1, x2, y2) # Draw a line out(sprintf('%.2f %.2f m %.2f %.2f l S', x1*@k,(@h-y1)*@k,x2*@k,(@h-y2)*@k)) end def Rect(x, y, w, h, style='') # Draw a rectangle if style=='F' op='f' elsif style=='FD' or style=='DF' op='B' else op='S' end out(sprintf('%.2f %.2f %.2f %.2f re %s', x*@k,(@h-y)*@k,w*@k,-h*@k,op)) end def AddFont(family, style='', file='') # Add a TrueType or Type1 font family = family.downcase family = 'helvetica' if family == 'arial' style = style.upcase style = 'BI' if style == 'IB' fontkey = family + style if @fonts.has_key?(fontkey) self.Error("Font already added: #{family} #{style}") end file = family.gsub(' ', '') + style.downcase + '.rb' if file == '' if self.class.const_defined? 'FPDF_FONTPATH' if FPDF_FONTPATH[-1,1] == '/' file = FPDF_FONTPATH + file else file = FPDF_FONTPATH + '/' + file end end # Changed from "require file" to fix bug reported by Hans Allis. load file if FontDef.desc.nil? self.Error("Could not include font definition file #{file}") end i = @fonts.length + 1 @fonts[fontkey] = {'i' => i, 'type' => FontDef.type, 'name' => FontDef.name, 'desc' => FontDef.desc, 'up' => FontDef.up, 'ut' => FontDef.ut, 'cw' => FontDef.cw, 'enc' => FontDef.enc, 'file' => FontDef.file } if FontDef.diff # Search existing encodings idx = @diffs.index(FontDef.diff) unless idx @diffs.push(FontDef.diff) @fonts[fontkey]['diff'] = @diffs.length else @fonts[fontkey]['diff'] = idx + 1 end end if FontDef.file if FontDef.type == 'TrueType' @FontFiles[FontDef.file] = {'length1' => FontDef.originalsize} else @FontFiles[FontDef.file] = {'length1' => FontDef.size1, 'length2' => FontDef.size2} end end return self end def SetFont(family, style='', size=0) # Select a font; size given in points family.downcase! family=@FontFamily if family=='' if family=='arial' family='helvetica' elsif family=='symbol' or family=='zapfdingbats' style='' end style.upcase! unless style.index('U').nil? @underline=true style.gsub!('U','') else @underline=false; end style='BI' if style=='IB' size=@FontSizePt if size==0 # Test if font is already selected return if @FontFamily==family and @FontStyle==style and @FontSizePt==size # Test if used for the first time fontkey=family+style unless @fonts.has_key?(fontkey) if @CoreFonts.has_key?(fontkey) unless Charwidths.has_key?(fontkey) raise 'Font unavailable' end @fonts[fontkey]={ 'i'=>@fonts.size, 'type'=>'core', 'name'=>@CoreFonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw'=>Charwidths[fontkey]} else raise 'Font unavailable' end end #Select it @FontFamily=family @FontStyle=style; @FontSizePt=size @FontSize=size/@k; @CurrentFont=@fonts[fontkey] if @page>0 out(sprintf('BT /F%d %.2f Tf ET', @CurrentFont['i'], @FontSizePt)) end end def SetFontSize(size) # Set font size in points return if @FontSizePt==size @FontSizePt=size @FontSize=size/@k if @page>0 out(sprintf('BT /F%d %.2f Tf ET',@CurrentFont['i'],@FontSizePt)) end end def AddLink # Create a new internal link @links.push([0, 0]) @links.size end def SetLink(link, y=0, page=-1) # Set destination of internal link y=@y if y==-1 page=@page if page==-1 @links[link]=[page, y] end def Link(x, y, w, h, link) # Put a link on the page @PageLinks[@page]=Array.new unless @PageLinks.has_key?(@page) @PageLinks[@page].push([x*@k,@hPt-y*@k,w*@k,h*@k,link]) end def Text(x, y, txt) # Output a string txt.gsub!(')', '\\)') txt.gsub!('(', '\\(') txt.gsub!('\\', '\\\\') s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*@k,(@h-y)*@k,txt); s=s+' '+dounderline(x,y,txt) if @underline and txt!='' s='q '+@TextColor+' '+s+' Q' if @ColorFlag out(s) end def AcceptPageBreak # Accept automatic page break or not @AutoPageBreak end def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='') # Output a cell if @y+h>@PageBreakTrigger and !@InFooter and self.AcceptPageBreak # Automatic page break x=@x ws=@ws if ws>0 @ws=0 out('0 Tw') end self.AddPage(@CurOrientation) @x=x if ws>0 @ws=ws out(sprintf('%.3f Tw',ws*@k)) end end w=@w-@rMargin-@x if w==0 s='' if fill==1 or border==1 if fill==1 op=(border==1) ? 'B' : 'f' else op='S' end s=sprintf('%.2f %.2f %.2f %.2f re %s ',@x*@k,(@h-@y)*@k,w*@k,-h*@k,op) end if border.is_a? String x=@x y=@y unless border.index('L').nil? s=s+sprintf('%.2f %.2f m %.2f %.2f l S ', x*@k,(@h-y)*@k,x*@k,(@h-(y+h))*@k) end unless border.index('T').nil? s=s+sprintf('%.2f %.2f m %.2f %.2f l S ', x*@k,(@h-y)*@k,(x+w)*@k,(@h-y)*@k) end unless border.index('R').nil? s=s+sprintf('%.2f %.2f m %.2f %.2f l S ', (x+w)*@k,(@h-y)*@k,(x+w)*@k,(@h-(y+h))*@k) end unless border.index('B').nil? s=s+sprintf('%.2f %.2f m %.2f %.2f l S ', x*@k,(@h-(y+h))*@k,(x+w)*@k,(@h-(y+h))*@k) end end if txt!='' if align=='R' dx=w-@cMargin-self.GetStringWidth(txt) elsif align=='C' dx=(w-self.GetStringWidth(txt))/2 else dx=@cMargin end txt = txt.gsub(')', '\\)') txt.gsub!('(', '\\(') txt.gsub!('\\', '\\\\') if @ColorFlag s=s+'q '+@TextColor+' ' end s=s+sprintf('BT %.2f %.2f Td (%s) Tj ET', (@x+dx)*@k,(@h-(@y+0.5*h+0.3*@FontSize))*@k,txt) s=s+' '+dounderline(@x+dx,@y+0.5*h+0.3*@FontSize,txt) if @underline s=s+' Q' if @ColorFlag if link and link != '' Link(@x+dx,@y+0.5*h-0.5*@FontSize,GetStringWidth(txt),@FontSize,link) end end out(s) if s @lasth=h if ln>0 # Go to next line @y=@y+h @x=@lMargin if ln==1 else @x=@x+w end end def MultiCell(w,h,txt,border=0,align='J',fill=0) # Output text with automatic or explicit line breaks cw=@CurrentFont['cw'] w=@w-@rMargin-@x if w==0 wmax=(w-2*@cMargin)*1000/@FontSize s=txt.gsub('\r','') nb=s.length nb=nb-1 if nb>0 and s[nb-1].chr=='\n' b=0 if border!=0 if border==1 border='LTRB' b='LRT' b2='LR' else b2='' b2='L' unless border.index('L').nil? b2=b2+'R' unless border.index('R').nil? b=(not border.index('T').nil?) ? (b2+'T') : b2 end end sep=-1 i=0 j=0 l=0 ns=0 nl=1 while i0 @ws=0 out('0 Tw') end # Changed from s[j..i] to fix bug reported by Hans Allis. self.Cell(w,h,s[j..i-1],b,2,align,fill) i=i+1 sep=-1 j=i l=0 ns=0 nl=nl+1 b=b2 if border and nl==2 else if c==' ' sep=i ls=l ns=ns+1 end l=l+cw[c[0]] if l>wmax # Automatic line break if sep==-1 i=i+1 if i==j if @ws>0 @ws=0 out('0 Tw') end self.Cell(w,h,s[j..i],b,2,align,fill) else if align=='J' @ws=(ns>1) ? (wmax-ls)/1000.0*@FontSize/(ns-1) : 0 out(sprintf('%.3f Tw',@ws*@k)) end self.Cell(w,h,s[j..sep],b,2,align,fill) i=sep+1 end sep=-1 j=i l=0 ns=0 nl=nl+1 b=b2 if border and nl==2 else i=i+1 end end end # Last chunk if @ws>0 @ws=0 out('0 Tw') end b=b+'B' if border!=0 and not border.index('B').nil? self.Cell(w,h,s[j..i],b,2,align,fill) @x=@lMargin end def Write(h,txt,link='') # Output text in flowing mode cw=@CurrentFont['cw'] w=@w-@rMargin-@x wmax=(w-2*@cMargin)*1000/@FontSize s=txt.gsub("\r",'') nb=s.length sep=-1 i=0 j=0 l=0 nl=1 while iwmax # Automatic line break if sep==-1 if @x>@lMargin # Move to next line @x=@lMargin @y=@y+h w=@w-@rMargin-@x wmax=(w-2*@cMargin)*1000/@FontSize i=i+1 nl=nl+1 next end i=i+1 if i==j self.Cell(w,h,s[j,i-j],0,2,'',0,link) else self.Cell(w,h,s[j,sep-j],0,2,'',0,link) i=sep+1 end sep=-1 j=i l=0 if nl==1 @x=@lMargin w=@w-@rMargin-@x wmax=(w-2*@cMargin)*1000/@FontSize end nl=nl+1 else i=i+1 end end # Last chunk self.Cell(l/1000.0*@FontSize,h,s[j,i],0,0,'',0,link) if i!=j end def Image(file,x,y,w,h=0,type='',link='') # Put an image on the page unless @images.has_key?(file) # First use of image, get info if type=='' pos=file.rindex('.') if pos.nil? self.Error('Image file has no extension and no type was '+ 'specified: '+file) end type=file[pos+1..-1] end type.downcase! if type=='jpg' or type=='jpeg' info=parsejpg(file) elsif type=='png' info=parsepng(file) else self.Error('Unsupported image file type: '+type) end info['i']=@images.length+1 @images[file]=info else info=@images[file] end # Automatic width or height calculation w=h*info['w']/info['h'] if w==0 h=w*info['h']/info['w'] if h==0 out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k,h*@k,x*@k,(@h-(y+h))*@k,info['i'])) Link(x,y,w,h,link) if link and link != '' end def Ln(h='') # Line feed; default value is last cell height @x=@lMargin if h.kind_of?(String) @y=@y+@lasth else @y=@y+h end end def GetX # Get x position @x end def SetX(x) # Set x position if x>=0 @x=x else @x=@w+x end end def GetY # Get y position @y end def SetY(y) # Set y position and reset x @x=@lMargin if y>=0 @y=y else @y=@h+y end end def SetXY(x,y) # Set x and y positions SetY(y) SetX(x) end def Output(file=nil) # Output PDF to file or return as a string # Finish document if necessary self.Close if(@state<3) if file.nil? # Return as a string return @buffer else # Save file locally open(file,'wb') do |f| f.write(@buffer) end end end private def putpages nb=@page unless @AliasNbPages.nil? or @AliasNbPages=='' # Replace number of pages 1.upto(nb) do |n| @pages[n].gsub!(@AliasNbPages,nb.to_s) end end if @DefOrientation=='P' wPt=@fwPt hPt=@fhPt else wPt=@fhPt hPt=@fwPt end filter=(@compress) ? '/Filter /FlateDecode ' : '' 1.upto(nb) do |n| # Page newobj out('<>>>' else l=@links[pl[4]] h=@OrientationChanges[l[0]].nil? ? hPt : wPt annots=annots+sprintf( '/Dest [%d 0 R /XYZ 0 %.2f null]>>', 1+2*l[0],h-l[1]*@k) end end out(annots+']') end out('/Contents '+(@n+1).to_s+' 0 R>>') out('endobj') # Page content p=(@compress) ? Zlib::Deflate.deflate(@pages[n]) : @pages[n] newobj out('<<'+filter+'/Length '+p.length.to_s+'>>') putstream(p) out('endobj') end # Pages root @offsets[1]=@buffer.length out('1 0 obj') out('<>') out('endobj') end def putfonts nf=@n @diffs.each do |diff| # Encodings newobj out('<>') out('endobj') end @FontFiles.each do |file, info| # Font file embedding newobj @FontFiles[file]['n'] = @n if self.class.const_defined? 'FPDF_FONTPATH' then if FPDF_FONTPATH[-1,1] == '/' then file = FPDF_FONTPATH + file else file = FPDF_FONTPATH + '/' + file end end size = File.size(file) unless File.exists?(file) Error('Font file not found') end out('<>') open(file, 'rb') do |f| putstream(f.read()) end out('endobj') end file = 0 @fonts.each do |k, font| # Font objects @fonts[k]['n']=@n+1 type=font['type'] name=font['name'] if type=='core' # Standard font newobj out('<>') out('endobj') elsif type=='Type1' or type=='TrueType' # Additional Type1 or TrueType font newobj out('<>') out('endobj') # Widths newobj cw=font['cw'] s='[' 32.upto(255) do |i| s << cw[i].to_s+' ' end out(s+']') out('endobj') # Descriptor newobj s='<>') out('endobj') else # Allow for additional types mtd='put'+type.downcase unless self.respond_to?(mtd) self.Error('Unsupported font type: '+type) end self.send(mtd, font) end end end def putimages filter=(@compress) ? '/Filter /FlateDecode ' : '' @images.each do |file, info| newobj @images[file]['n']=@n out('<>') putstream(info['data']) @images[file]['data']=nil out('endobj') # Palette if info['cs']=='Indexed' newobj pal=(@compress) ? Zlib::Deflate.deflate(info['pal']) : info['pal'] out('<<'+filter+'/Length '+pal.length.to_s+'>>') putstream(pal) out('endobj') end end end def putxobjectdict @images.each_value do |image| out('/I'+image['i'].to_s+' '+image['n'].to_s+' 0 R') end end def putresourcedict out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]') out('/Font <<') @fonts.each_value do |font| out('/F'+font['i'].to_s+' '+font['n'].to_s+' 0 R') end out('>>') out('/XObject <<') putxobjectdict out('>>') end def putresources putfonts putimages # Resource dictionary @offsets[2]=@buffer.length out('2 0 obj') out('<<') putresourcedict out('>>') out('endobj') end def putinfo out('/Producer '+textstring('Ruby FPDF '+FPDF_VERSION)); unless @title.nil? out('/Title '+textstring(@title)) end unless @subject.nil? out('/Subject '+textstring(@subject)) end unless @author.nil? out('/Author '+textstring(@author)) end unless @keywords.nil? out('/Keywords '+textstring(@keywords)) end unless @creator.nil? out('/Creator '+textstring(@creator)) end out('/CreationDate '+textstring('D: '+DateTime.now.to_s)) end def putcatalog out('/Type /Catalog') out('/Pages 1 0 R') if @ZoomMode=='fullpage' out('/OpenAction [3 0 R /Fit]') elsif @ZoomMode=='fullwidth' out('/OpenAction [3 0 R /FitH null]') elsif @ZoomMode=='real' out('/OpenAction [3 0 R /XYZ null null 1]') elsif not @ZoomMode.kind_of?(String) out('/OpenAction [3 0 R /XYZ null null '+(@ZoomMode/100)+']') end if @LayoutMode=='single' out('/PageLayout /SinglePage') elsif @LayoutMode=='continuous' out('/PageLayout /OneColumn') elsif @LayoutMode=='two' out('/PageLayout /TwoColumnLeft') end end def putheader out('%PDF-'+@PDFVersion) end def puttrailer out('/Size '+(@n+1).to_s) out('/Root '+@n.to_s+' 0 R') out('/Info '+(@n-1).to_s+' 0 R') end def enddoc putheader putpages putresources # Info newobj out('<<') putinfo out('>>') out('endobj') # Catalog newobj out('<<') putcatalog out('>>') out('endobj') # Cross-ref o=@buffer.length out('xref') out('0 '+(@n+1).to_s) out('0000000000 65535 f ') 1.upto(@n) do |i| out(sprintf('%010d 00000 n ',@offsets[i])) end # Trailer out('trailer') out('<<') puttrailer out('>>') out('startxref') out(o) out('%%EOF') state=3 end def beginpage(orientation) @page=@page+1 @pages[@page]='' @state=2 @x=@lMargin @y=@tMargin @lasth=0 @FontFamily='' # Page orientation if orientation=='' orientation=@DefOrientation else orientation=orientation[0].chr.upcase if orientation!=@DefOrientation @OrientationChanges[@page]=true end end if orientation!=@CurOrientation # Change orientation if orientation=='P' @wPt=@fwPt @hPt=@fhPt @w=@fw @h=@fh else @wPt=@fhPt @hPt=@fwPt @w=@fh @h=@fw end @PageBreakTrigger=@h-@bMargin @CurOrientation=orientation end end def endpage # End of page contents @state=1 end def newobj # Begin a new object @n=@n+1 @offsets[@n]=@buffer.length out(@n.to_s+' 0 obj') end def dounderline(x,y,txt) # Underline text up=@CurrentFont['up'] ut=@CurrentFont['ut'] w=GetStringWidth(txt)+@ws*txt.count(' ') sprintf('%.2f %.2f %.2f %.2f re f', x*@k,(@h-(y-up/1000.0*@FontSize))*@k,w*@k,-ut/1000.0*@FontSizePt) end def parsejpg(file) # Extract info from a JPEG file a=extractjpginfo(file) raise "Missing or incorrect JPEG file: #{file}" if a.nil? if a['channels'].nil? || a['channels']==3 then colspace='DeviceRGB' elsif a['channels']==4 then colspace='DeviceCMYK' else colspace='DeviceGray' end bpc= a['bits'] ? a['bits'].to_i : 8 # Read whole file data = nil open(file, 'rb') do |f| data = f.read end return {'w'=>a['width'],'h'=>a['height'],'cs'=>colspace,'bpc'=>bpc,'f'=>'DCTDecode','data'=>data} end def parsepng(file) # Extract info from a PNG file f=open(file,'rb') # Check signature unless f.read(8)==137.chr+'PNG'+13.chr+10.chr+26.chr+10.chr self.Error('Not a PNG file: '+file) end # Read header chunk f.read(4) if f.read(4)!='IHDR' self.Error('Incorrect PNG file: '+file) end w=freadint(f) h=freadint(f) bpc=f.read(1)[0] if bpc>8 self.Error('16-bit depth not supported: '+file) end ct=f.read(1)[0] if ct==0 colspace='DeviceGray' elsif ct==2 colspace='DeviceRGB' elsif ct==3 colspace='Indexed' else self.Error('Alpha channel not supported: '+file) end if f.read(1)[0]!=0 self.Error('Unknown compression method: '+file) end if f.read(1)[0]!=0 self.Error('Unknown filter method: '+file) end if f.read(1)[0]!=0 self.Error('Interlacing not supported: '+file) end f.read(4) parms='/DecodeParms <>' # Scan chunks looking for palette, transparency and image data pal='' trns='' data='' begin n=freadint(f) type=f.read(4) if type=='PLTE' # Read palette pal=f.read(n) f.read(4) elsif type=='tRNS' # Read transparency info t=f.read(n) if ct==0 trns=[t[1]] elsif ct==2 trns=[t[1],t[3],t[5]] else pos=t.index(0) trns=[pos] unless pos.nil? end f.read(4) elsif type=='IDAT' # Read image data block data << f.read(n) f.read(4) elsif type=='IEND' break else f.read(n+4) end end while n if colspace=='Indexed' and pal=='' self.Error('Missing palette in '+file) end f.close {'w'=>w,'h'=>h,'cs'=>colspace,'bpc'=>bpc,'f'=>'FlateDecode', 'parms'=>parms,'pal'=>pal,'trns'=>trns,'data'=>data} end def freadint(f) # Read a 4-byte integer from file a = f.read(4).unpack('N') return a[0] end def freadshort(f) a = f.read(2).unpack('n') return a[0] end def freadbyte(f) a = f.read(1).unpack('C') return a[0] end def textstring(s) # Format a text string '('+escape(s)+')' end def escape(s) # Add \ before \, ( and ) s.gsub('\\','\\\\').gsub('(','\\(').gsub(')','\\)') end def putstream(s) out('stream') out(s) out('endstream') end def out(s) # Add a line to the document if @state==2 @pages[@page]=@pages[@page]+s+"\n" else @buffer=@buffer+s.to_s+"\n" end end # jpeg marker codes M_SOF0 = 0xc0 M_SOF1 = 0xc1 M_SOF2 = 0xc2 M_SOF3 = 0xc3 M_SOF5 = 0xc5 M_SOF6 = 0xc6 M_SOF7 = 0xc7 M_SOF9 = 0xc9 M_SOF10 = 0xca M_SOF11 = 0xcb M_SOF13 = 0xcd M_SOF14 = 0xce M_SOF15 = 0xcf M_SOI = 0xd8 M_EOI = 0xd9 M_SOS = 0xda def extractjpginfo(file) result = nil open(file, "rb") do |f| marker = jpegnextmarker(f) if marker != M_SOI return nil end while true marker = jpegnextmarker(f) case marker when M_SOF0, M_SOF1, M_SOF2, M_SOF3, M_SOF5, M_SOF6, M_SOF7, M_SOF9, M_SOF10, M_SOF11, M_SOF13, M_SOF14, M_SOF15 then length = freadshort(f) if result.nil? result = {} result['bits'] = freadbyte(f) result['height'] = freadshort(f) result['width'] = freadshort(f) result['channels'] = freadbyte(f) f.seek(length - 8, IO::SEEK_CUR) else f.seek(length - 2, IO::SEEK_CUR) end when M_SOS, M_EOI then return result else length = freadshort(f) f.seek(length - 2, IO::SEEK_CUR) end end end end def jpegnextmarker(f) while true # look for 0xff while (c = freadbyte(f)) != 0xff end c = freadbyte(f) if c != 0 return c end end end end