Commit | Line | Data |
---|---|---|
7f254ad8 AE |
1 | <?php |
2 | /** | |
3 | * @package php-font-lib | |
4 | * @link https://github.com/PhenX/php-font-lib | |
5 | * @author Fabien Ménager <fabien.menager@gmail.com> | |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License | |
7 | */ | |
8 | ||
9 | namespace FontLib\Table\Type; | |
10 | use FontLib\Table\Table; | |
11 | ||
12 | /** | |
13 | * `cmap` font table. | |
14 | * | |
15 | * @package php-font-lib | |
16 | */ | |
17 | class cmap extends Table { | |
18 | private static $header_format = array( | |
19 | "version" => self::uint16, | |
20 | "numberSubtables" => self::uint16, | |
21 | ); | |
22 | ||
23 | private static $subtable_header_format = array( | |
24 | "platformID" => self::uint16, | |
25 | "platformSpecificID" => self::uint16, | |
26 | "offset" => self::uint32, | |
27 | ); | |
28 | ||
29 | private static $subtable_v4_format = array( | |
30 | "length" => self::uint16, | |
31 | "language" => self::uint16, | |
32 | "segCountX2" => self::uint16, | |
33 | "searchRange" => self::uint16, | |
34 | "entrySelector" => self::uint16, | |
35 | "rangeShift" => self::uint16, | |
36 | ); | |
37 | ||
38 | protected function _parse() { | |
39 | $font = $this->getFont(); | |
40 | ||
41 | $cmap_offset = $font->pos(); | |
42 | ||
43 | $data = $font->unpack(self::$header_format); | |
44 | ||
45 | $subtables = array(); | |
46 | for ($i = 0; $i < $data["numberSubtables"]; $i++) { | |
47 | $subtables[] = $font->unpack(self::$subtable_header_format); | |
48 | } | |
49 | $data["subtables"] = $subtables; | |
50 | ||
51 | foreach ($data["subtables"] as $i => &$subtable) { | |
52 | $font->seek($cmap_offset + $subtable["offset"]); | |
53 | ||
54 | $subtable["format"] = $font->readUInt16(); | |
55 | ||
56 | // @todo Only CMAP version 4 | |
57 | if ($subtable["format"] != 4) { | |
58 | unset($data["subtables"][$i]); | |
59 | $data["numberSubtables"]--; | |
60 | continue; | |
61 | } | |
62 | ||
63 | $subtable += $font->unpack(self::$subtable_v4_format); | |
64 | $segCount = $subtable["segCountX2"] / 2; | |
65 | $subtable["segCount"] = $segCount; | |
66 | ||
67 | $endCode = $font->r(array(self::uint16, $segCount)); | |
68 | ||
69 | $font->readUInt16(); // reservedPad | |
70 | ||
71 | $startCode = $font->r(array(self::uint16, $segCount)); | |
72 | $idDelta = $font->r(array(self::int16, $segCount)); | |
73 | ||
74 | $ro_start = $font->pos(); | |
75 | $idRangeOffset = $font->r(array(self::uint16, $segCount)); | |
76 | ||
77 | $glyphIndexArray = array(); | |
78 | for ($i = 0; $i < $segCount; $i++) { | |
79 | $c1 = $startCode[$i]; | |
80 | $c2 = $endCode[$i]; | |
81 | $d = $idDelta[$i]; | |
82 | $ro = $idRangeOffset[$i]; | |
83 | ||
84 | if ($ro > 0) { | |
85 | $font->seek($subtable["offset"] + 2 * $i + $ro); | |
86 | } | |
87 | ||
88 | for ($c = $c1; $c <= $c2; $c++) { | |
89 | if ($ro == 0) { | |
90 | $gid = ($c + $d) & 0xFFFF; | |
91 | } | |
92 | else { | |
93 | $offset = ($c - $c1) * 2 + $ro; | |
94 | $offset = $ro_start + 2 * $i + $offset; | |
95 | ||
96 | $font->seek($offset); | |
97 | $gid = $font->readUInt16(); | |
98 | ||
99 | if ($gid != 0) { | |
100 | $gid = ($gid + $d) & 0xFFFF; | |
101 | } | |
102 | } | |
103 | ||
104 | if ($gid > 0) { | |
105 | $glyphIndexArray[$c] = $gid; | |
106 | } | |
107 | } | |
108 | } | |
109 | ||
110 | $subtable += array( | |
111 | "endCode" => $endCode, | |
112 | "startCode" => $startCode, | |
113 | "idDelta" => $idDelta, | |
114 | "idRangeOffset" => $idRangeOffset, | |
115 | "glyphIndexArray" => $glyphIndexArray, | |
116 | ); | |
117 | } | |
118 | ||
119 | $this->data = $data; | |
120 | } | |
121 | ||
122 | function _encode() { | |
123 | $font = $this->getFont(); | |
124 | ||
125 | $subset = $font->getSubset(); | |
126 | $glyphIndexArray = $font->getUnicodeCharMap(); | |
127 | ||
128 | $newGlyphIndexArray = array(); | |
129 | foreach ($glyphIndexArray as $code => $gid) { | |
130 | $new_gid = array_search($gid, $subset); | |
131 | if ($new_gid !== false) { | |
132 | $newGlyphIndexArray[$code] = $new_gid; | |
133 | } | |
134 | } | |
135 | ||
136 | ksort($newGlyphIndexArray); // Sort by char code | |
137 | ||
138 | $segments = array(); | |
139 | ||
140 | $i = -1; | |
141 | $prevCode = 0xFFFF; | |
142 | $prevGid = 0xFFFF; | |
143 | ||
144 | foreach ($newGlyphIndexArray as $code => $gid) { | |
145 | if ( | |
146 | $prevCode + 1 != $code || | |
147 | $prevGid + 1 != $gid | |
148 | ) { | |
149 | $i++; | |
150 | $segments[$i] = array(); | |
151 | } | |
152 | ||
153 | $segments[$i][] = array($code, $gid); | |
154 | ||
155 | $prevCode = $code; | |
156 | $prevGid = $gid; | |
157 | } | |
158 | ||
159 | $segments[][] = array(0xFFFF, 0xFFFF); | |
160 | ||
161 | $startCode = array(); | |
162 | $endCode = array(); | |
163 | $idDelta = array(); | |
164 | ||
165 | foreach ($segments as $codes) { | |
166 | $start = reset($codes); | |
167 | $end = end($codes); | |
168 | ||
169 | $startCode[] = $start[0]; | |
170 | $endCode[] = $end[0]; | |
171 | $idDelta[] = $start[1] - $start[0]; | |
172 | } | |
173 | ||
174 | $segCount = count($startCode); | |
175 | $idRangeOffset = array_fill(0, $segCount, 0); | |
176 | ||
177 | $searchRange = 1; | |
178 | $entrySelector = 0; | |
179 | while ($searchRange * 2 <= $segCount) { | |
180 | $searchRange *= 2; | |
181 | $entrySelector++; | |
182 | } | |
183 | $searchRange *= 2; | |
184 | $rangeShift = $segCount * 2 - $searchRange; | |
185 | ||
186 | $subtables = array( | |
187 | array( | |
188 | // header | |
189 | "platformID" => 3, // Unicode | |
190 | "platformSpecificID" => 1, | |
191 | "offset" => null, | |
192 | ||
193 | // subtable | |
194 | "format" => 4, | |
195 | "length" => null, | |
196 | "language" => 0, | |
197 | "segCount" => $segCount, | |
198 | "segCountX2" => $segCount * 2, | |
199 | "searchRange" => $searchRange, | |
200 | "entrySelector" => $entrySelector, | |
201 | "rangeShift" => $rangeShift, | |
202 | "startCode" => $startCode, | |
203 | "endCode" => $endCode, | |
204 | "idDelta" => $idDelta, | |
205 | "idRangeOffset" => $idRangeOffset, | |
206 | "glyphIndexArray" => $newGlyphIndexArray, | |
207 | ) | |
208 | ); | |
209 | ||
210 | $data = array( | |
211 | "version" => 0, | |
212 | "numberSubtables" => count($subtables), | |
213 | "subtables" => $subtables, | |
214 | ); | |
215 | ||
216 | $length = $font->pack(self::$header_format, $data); | |
217 | ||
218 | $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format | |
219 | $subtable_headers_offset = $font->pos(); | |
220 | ||
221 | $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size); | |
222 | ||
223 | // write subtables data | |
224 | foreach ($data["subtables"] as $i => $subtable) { | |
225 | $length_before = $length; | |
226 | $data["subtables"][$i]["offset"] = $length; | |
227 | ||
228 | $length += $font->writeUInt16($subtable["format"]); | |
229 | ||
230 | $before_subheader = $font->pos(); | |
231 | $length += $font->pack(self::$subtable_v4_format, $subtable); | |
232 | ||
233 | $segCount = $subtable["segCount"]; | |
234 | $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]); | |
235 | $length += $font->writeUInt16(0); // reservedPad | |
236 | $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]); | |
237 | $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]); | |
238 | $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]); | |
239 | $length += $font->w(array(self::uint16, $segCount), array_values($subtable["glyphIndexArray"])); | |
240 | ||
241 | $after_subtable = $font->pos(); | |
242 | ||
243 | $subtable["length"] = $length - $length_before; | |
244 | $font->seek($before_subheader); | |
245 | $length += $font->pack(self::$subtable_v4_format, $subtable); | |
246 | ||
247 | $font->seek($after_subtable); | |
248 | } | |
249 | ||
250 | // write subtables headers | |
251 | $font->seek($subtable_headers_offset); | |
252 | foreach ($data["subtables"] as $subtable) { | |
253 | $font->pack(self::$subtable_header_format, $subtable); | |
254 | } | |
255 | ||
256 | return $length; | |
257 | } | |
258 | } |