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 9namespace FontLib\Table\Type; 10use FontLib\Table\Table; 11 12/** 13 * `cmap` font table. 14 * 15 * @package php-font-lib 16 */ 17class 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 private static $subtable_v12_format = array( 39 "length" => self::uint32, 40 "language" => self::uint32, 41 "ngroups" => self::uint32 42 ); 43 44 protected function _parse() { 45 $font = $this->getFont(); 46 47 $cmap_offset = $font->pos(); 48 49 $data = $font->unpack(self::$header_format); 50 51 $subtables = array(); 52 for ($i = 0; $i < $data["numberSubtables"]; $i++) { 53 $subtables[] = $font->unpack(self::$subtable_header_format); 54 } 55 56 $data["subtables"] = $subtables; 57 58 foreach ($data["subtables"] as $i => &$subtable) { 59 $font->seek($cmap_offset + $subtable["offset"]); 60 61 $subtable["format"] = $font->readUInt16(); 62 63 // @todo Only CMAP version 4 and 12 64 if (($subtable["format"] != 4) && ($subtable["format"] != 12)) { 65 unset($data["subtables"][$i]); 66 $data["numberSubtables"]--; 67 continue; 68 } 69 70 if ($subtable["format"] == 12) { 71 72 $font->readUInt16(); 73 74 $subtable += $font->unpack(self::$subtable_v12_format); 75 76 $glyphIndexArray = array(); 77 $endCodes = array(); 78 $startCodes = array(); 79 80 for ($p = 0; $p < $subtable['ngroups']; $p++) { 81 82 $startCode = $startCodes[] = $font->readUInt32(); 83 $endCode = $endCodes[] = $font->readUInt32(); 84 $startGlyphCode = $font->readUInt32(); 85 86 for ($c = $startCode; $c <= $endCode; $c++) { 87 $glyphIndexArray[$c] = $startGlyphCode; 88 $startGlyphCode++; 89 } 90 } 91 92 $subtable += array( 93 "startCode" => $startCodes, 94 "endCode" => $endCodes, 95 "glyphIndexArray" => $glyphIndexArray, 96 ); 97 98 } 99 else if ($subtable["format"] == 4) { 100 101 $subtable += $font->unpack(self::$subtable_v4_format); 102 103 $segCount = $subtable["segCountX2"] / 2; 104 $subtable["segCount"] = $segCount; 105 106 $endCode = $font->readUInt16Many($segCount); 107 108 $font->readUInt16(); // reservedPad 109 110 $startCode = $font->readUInt16Many($segCount); 111 $idDelta = $font->readInt16Many($segCount); 112 113 $ro_start = $font->pos(); 114 $idRangeOffset = $font->readUInt16Many($segCount); 115 116 $glyphIndexArray = array(); 117 for ($i = 0; $i < $segCount; $i++) { 118 $c1 = $startCode[$i]; 119 $c2 = $endCode[$i]; 120 $d = $idDelta[$i]; 121 $ro = $idRangeOffset[$i]; 122 123 if ($ro > 0) { 124 $font->seek($subtable["offset"] + 2 * $i + $ro); 125 } 126 127 for ($c = $c1; $c <= $c2; $c++) { 128 if ($ro == 0) { 129 $gid = ($c + $d) & 0xFFFF; 130 } 131 else { 132 $offset = ($c - $c1) * 2 + $ro; 133 $offset = $ro_start + 2 * $i + $offset; 134 135 $font->seek($offset); 136 $gid = $font->readUInt16(); 137 138 if ($gid != 0) { 139 $gid = ($gid + $d) & 0xFFFF; 140 } 141 } 142 143 if ($gid > 0) { 144 $glyphIndexArray[$c] = $gid; 145 } 146 } 147 } 148 149 $subtable += array( 150 "endCode" => $endCode, 151 "startCode" => $startCode, 152 "idDelta" => $idDelta, 153 "idRangeOffset" => $idRangeOffset, 154 "glyphIndexArray" => $glyphIndexArray, 155 ); 156 } 157 } 158 159 $this->data = $data; 160 } 161 162 function _encode() { 163 $font = $this->getFont(); 164 165 $subset = $font->getSubset(); 166 $glyphIndexArray = $font->getUnicodeCharMap(); 167 168 $newGlyphIndexArray = array(); 169 foreach ($glyphIndexArray as $code => $gid) { 170 $new_gid = array_search($gid, $subset); 171 if ($new_gid !== false) { 172 $newGlyphIndexArray[$code] = $new_gid; 173 } 174 } 175 176 ksort($newGlyphIndexArray); // Sort by char code 177 178 $segments = array(); 179 180 $i = -1; 181 $prevCode = 0xFFFF; 182 $prevGid = 0xFFFF; 183 184 foreach ($newGlyphIndexArray as $code => $gid) { 185 if ( 186 $prevCode + 1 != $code || 187 $prevGid + 1 != $gid 188 ) { 189 $i++; 190 $segments[$i] = array(); 191 } 192 193 $segments[$i][] = array($code, $gid); 194 195 $prevCode = $code; 196 $prevGid = $gid; 197 } 198 199 $segments[][] = array(0xFFFF, 0xFFFF); 200 201 $startCode = array(); 202 $endCode = array(); 203 $idDelta = array(); 204 205 foreach ($segments as $codes) { 206 $start = reset($codes); 207 $end = end($codes); 208 209 $startCode[] = $start[0]; 210 $endCode[] = $end[0]; 211 $idDelta[] = $start[1] - $start[0]; 212 } 213 214 $segCount = count($startCode); 215 $idRangeOffset = array_fill(0, $segCount, 0); 216 217 $searchRange = 1; 218 $entrySelector = 0; 219 while ($searchRange * 2 <= $segCount) { 220 $searchRange *= 2; 221 $entrySelector++; 222 } 223 $searchRange *= 2; 224 $rangeShift = $segCount * 2 - $searchRange; 225 226 $subtables = array( 227 array( 228 // header 229 "platformID" => 3, // Unicode 230 "platformSpecificID" => 1, 231 "offset" => null, 232 233 // subtable 234 "format" => 4, 235 "length" => null, 236 "language" => 0, 237 "segCount" => $segCount, 238 "segCountX2" => $segCount * 2, 239 "searchRange" => $searchRange, 240 "entrySelector" => $entrySelector, 241 "rangeShift" => $rangeShift, 242 "startCode" => $startCode, 243 "endCode" => $endCode, 244 "idDelta" => $idDelta, 245 "idRangeOffset" => $idRangeOffset, 246 "glyphIndexArray" => $newGlyphIndexArray, 247 ) 248 ); 249 250 $data = array( 251 "version" => 0, 252 "numberSubtables" => count($subtables), 253 "subtables" => $subtables, 254 ); 255 256 $length = $font->pack(self::$header_format, $data); 257 258 $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format 259 $subtable_headers_offset = $font->pos(); 260 261 $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size); 262 263 // write subtables data 264 foreach ($data["subtables"] as $i => $subtable) { 265 $length_before = $length; 266 $data["subtables"][$i]["offset"] = $length; 267 268 $length += $font->writeUInt16($subtable["format"]); 269 270 $before_subheader = $font->pos(); 271 $length += $font->pack(self::$subtable_v4_format, $subtable); 272 273 $segCount = $subtable["segCount"]; 274 $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]); 275 $length += $font->writeUInt16(0); // reservedPad 276 $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]); 277 $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]); 278 $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]); 279 $length += $font->w(array(self::uint16, $segCount), array_values($subtable["glyphIndexArray"])); 280 281 $after_subtable = $font->pos(); 282 283 $subtable["length"] = $length - $length_before; 284 $font->seek($before_subheader); 285 $length += $font->pack(self::$subtable_v4_format, $subtable); 286 287 $font->seek($after_subtable); 288 } 289 290 // write subtables headers 291 $font->seek($subtable_headers_offset); 292 foreach ($data["subtables"] as $subtable) { 293 $font->pack(self::$subtable_header_format, $subtable); 294 } 295 296 return $length; 297 } 298} 299