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