Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
 
10
class 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
?>