source: branches/version-2_11-dev/data/module/fpdf/makefont/ttfparser.php @ 20993

Revision 20993, 7.1 KB checked in by Seasoft, 15 years ago (diff)

#1374 (依存ライブラリのアップデート)

  • FPDF 1.6 -> 1.7
  • FPDI 1.4 -> 1.4.1 (配置パスをFPDFから分離)
Line 
1<?php
2/*******************************************************************************
3* Utility to parse TTF font files                                              *
4*                                                                              *
5* Version: 1.0                                                                 *
6* Date:    2011-06-18                                                          *
7* Author:  Olivier PLATHEY                                                     *
8*******************************************************************************/
9
10class TTFParser
11{
12    var $f;
13    var $tables;
14    var $unitsPerEm;
15    var $xMin, $yMin, $xMax, $yMax;
16    var $numberOfHMetrics;
17    var $numGlyphs;
18    var $widths;
19    var $chars;
20    var $postScriptName;
21    var $Embeddable;
22    var $Bold;
23    var $typoAscender;
24    var $typoDescender;
25    var $capHeight;
26    var $italicAngle;
27    var $underlinePosition;
28    var $underlineThickness;
29    var $isFixedPitch;
30
31    function Parse($file)
32    {
33        $this->f = fopen($file, 'rb');
34        if(!$this->f)
35            $this->Error('Can\'t open file: '.$file);
36
37        $version = $this->Read(4);
38        if($version=='OTTO')
39            $this->Error('OpenType fonts based on PostScript outlines are not supported');
40        if($version!="\x00\x01\x00\x00")
41            $this->Error('Unrecognized file format');
42        $numTables = $this->ReadUShort();
43        $this->Skip(3*2); // searchRange, entrySelector, rangeShift
44        $this->tables = array();
45        for($i=0;$i<$numTables;$i++)
46        {
47            $tag = $this->Read(4);
48            $this->Skip(4); // checkSum
49            $offset = $this->ReadULong();
50            $this->Skip(4); // length
51            $this->tables[$tag] = $offset;
52        }
53
54        $this->ParseHead();
55        $this->ParseHhea();
56        $this->ParseMaxp();
57        $this->ParseHmtx();
58        $this->ParseCmap();
59        $this->ParseName();
60        $this->ParseOS2();
61        $this->ParsePost();
62
63        fclose($this->f);
64    }
65
66    function ParseHead()
67    {
68        $this->Seek('head');
69        $this->Skip(3*4); // version, fontRevision, checkSumAdjustment
70        $magicNumber = $this->ReadULong();
71        if($magicNumber!=0x5F0F3CF5)
72            $this->Error('Incorrect magic number');
73        $this->Skip(2); // flags
74        $this->unitsPerEm = $this->ReadUShort();
75        $this->Skip(2*8); // created, modified
76        $this->xMin = $this->ReadShort();
77        $this->yMin = $this->ReadShort();
78        $this->xMax = $this->ReadShort();
79        $this->yMax = $this->ReadShort();
80    }
81
82    function ParseHhea()
83    {
84        $this->Seek('hhea');
85        $this->Skip(4+15*2);
86        $this->numberOfHMetrics = $this->ReadUShort();
87    }
88
89    function ParseMaxp()
90    {
91        $this->Seek('maxp');
92        $this->Skip(4);
93        $this->numGlyphs = $this->ReadUShort();
94    }
95
96    function ParseHmtx()
97    {
98        $this->Seek('hmtx');
99        $this->widths = array();
100        for($i=0;$i<$this->numberOfHMetrics;$i++)
101        {
102            $advanceWidth = $this->ReadUShort();
103            $this->Skip(2); // lsb
104            $this->widths[$i] = $advanceWidth;
105        }
106        if($this->numberOfHMetrics<$this->numGlyphs)
107        {
108            $lastWidth = $this->widths[$this->numberOfHMetrics-1];
109            $this->widths = array_pad($this->widths, $this->numGlyphs, $lastWidth);
110        }
111    }
112
113    function ParseCmap()
114    {
115        $this->Seek('cmap');
116        $this->Skip(2); // version
117        $numTables = $this->ReadUShort();
118        $offset31 = 0;
119        for($i=0;$i<$numTables;$i++)
120        {
121            $platformID = $this->ReadUShort();
122            $encodingID = $this->ReadUShort();
123            $offset = $this->ReadULong();
124            if($platformID==3 && $encodingID==1)
125                $offset31 = $offset;
126        }
127        if($offset31==0)
128            $this->Error('No Unicode encoding found');
129
130        $startCount = array();
131        $endCount = array();
132        $idDelta = array();
133        $idRangeOffset = array();
134        $this->chars = array();
135        fseek($this->f, $this->tables['cmap']+$offset31, SEEK_SET);
136        $format = $this->ReadUShort();
137        if($format!=4)
138            $this->Error('Unexpected subtable format: '.$format);
139        $this->Skip(2*2); // length, language
140        $segCount = $this->ReadUShort()/2;
141        $this->Skip(3*2); // searchRange, entrySelector, rangeShift
142        for($i=0;$i<$segCount;$i++)
143            $endCount[$i] = $this->ReadUShort();
144        $this->Skip(2); // reservedPad
145        for($i=0;$i<$segCount;$i++)
146            $startCount[$i] = $this->ReadUShort();
147        for($i=0;$i<$segCount;$i++)
148            $idDelta[$i] = $this->ReadShort();
149        $offset = ftell($this->f);
150        for($i=0;$i<$segCount;$i++)
151            $idRangeOffset[$i] = $this->ReadUShort();
152
153        for($i=0;$i<$segCount;$i++)
154        {
155            $c1 = $startCount[$i];
156            $c2 = $endCount[$i];
157            $d = $idDelta[$i];
158            $ro = $idRangeOffset[$i];
159            if($ro>0)
160                fseek($this->f, $offset+2*$i+$ro, SEEK_SET);
161            for($c=$c1;$c<=$c2;$c++)
162            {
163                if($c==0xFFFF)
164                    break;
165                if($ro>0)
166                {
167                    $gid = $this->ReadUShort();
168                    if($gid>0)
169                        $gid += $d;
170                }
171                else
172                    $gid = $c+$d;
173                if($gid>=65536)
174                    $gid -= 65536;
175                if($gid>0)
176                    $this->chars[$c] = $gid;
177            }
178        }
179    }
180
181    function ParseName()
182    {
183        $this->Seek('name');
184        $tableOffset = ftell($this->f);
185        $this->postScriptName = '';
186        $this->Skip(2); // format
187        $count = $this->ReadUShort();
188        $stringOffset = $this->ReadUShort();
189        for($i=0;$i<$count;$i++)
190        {
191            $this->Skip(3*2); // platformID, encodingID, languageID
192            $nameID = $this->ReadUShort();
193            $length = $this->ReadUShort();
194            $offset = $this->ReadUShort();
195            if($nameID==6)
196            {
197                // PostScript name
198                fseek($this->f, $tableOffset+$stringOffset+$offset, SEEK_SET);
199                $s = $this->Read($length);
200                $s = str_replace(chr(0), '', $s);
201                $s = preg_replace('|[ \[\](){}<>/%]|', '', $s);
202                $this->postScriptName = $s;
203                break;
204            }
205        }
206        if($this->postScriptName=='')
207            $this->Error('PostScript name not found');
208    }
209
210    function ParseOS2()
211    {
212        $this->Seek('OS/2');
213        $version = $this->ReadUShort();
214        $this->Skip(3*2); // xAvgCharWidth, usWeightClass, usWidthClass
215        $fsType = $this->ReadUShort();
216        $this->Embeddable = ($fsType!=2) && ($fsType & 0x200)==0;
217        $this->Skip(11*2+10+4*4+4);
218        $fsSelection = $this->ReadUShort();
219        $this->Bold = ($fsSelection & 32)!=0;
220        $this->Skip(2*2); // usFirstCharIndex, usLastCharIndex
221        $this->typoAscender = $this->ReadShort();
222        $this->typoDescender = $this->ReadShort();
223        if($version>=2)
224        {
225            $this->Skip(3*2+2*4+2);
226            $this->capHeight = $this->ReadShort();
227        }
228        else
229            $this->capHeight = 0;
230    }
231
232    function ParsePost()
233    {
234        $this->Seek('post');
235        $this->Skip(4); // version
236        $this->italicAngle = $this->ReadShort();
237        $this->Skip(2); // Skip decimal part
238        $this->underlinePosition = $this->ReadShort();
239        $this->underlineThickness = $this->ReadShort();
240        $this->isFixedPitch = ($this->ReadULong()!=0);
241    }
242
243    function Error($msg)
244    {
245        if(PHP_SAPI=='cli')
246            die("Error: $msg\n");
247        else
248            die("<b>Error</b>: $msg");
249    }
250
251    function Seek($tag)
252    {
253        if(!isset($this->tables[$tag]))
254            $this->Error('Table not found: '.$tag);
255        fseek($this->f, $this->tables[$tag], SEEK_SET);
256    }
257
258    function Skip($n)
259    {
260        fseek($this->f, $n, SEEK_CUR);
261    }
262
263    function Read($n)
264    {
265        return fread($this->f, $n);
266    }
267
268    function ReadUShort()
269    {
270        $a = unpack('nn', fread($this->f,2));
271        return $a['n'];
272    }
273
274    function ReadShort()
275    {
276        $a = unpack('nn', fread($this->f,2));
277        $v = $a['n'];
278        if($v>=0x8000)
279            $v -= 65536;
280        return $v;
281    }
282
283    function ReadULong()
284    {
285        $a = unpack('NN', fread($this->f,4));
286        return $a['N'];
287    }
288}
289?>
Note: See TracBrowser for help on using the repository browser.