253 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * @package php-font-lib
 | |
|  * @link    https://github.com/PhenX/php-font-lib
 | |
|  * @author  Fabien Ménager <fabien.menager@gmail.com>
 | |
|  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * `cmap` font table.
 | |
|  * 
 | |
|  * @package php-font-lib
 | |
|  */
 | |
| class Font_Table_cmap extends Font_Table {
 | |
|   private static $header_format = array(
 | |
|     "version"         => self::uint16,
 | |
|     "numberSubtables" => self::uint16,
 | |
|   );
 | |
|   
 | |
|   private static $subtable_header_format = array(
 | |
|     "platformID"         => self::uint16,
 | |
|     "platformSpecificID" => self::uint16,
 | |
|     "offset"             => self::uint32,
 | |
|   );
 | |
|   
 | |
|   private static $subtable_v4_format = array(
 | |
|     "length"        => self::uint16, 
 | |
|     "language"      => self::uint16, 
 | |
|     "segCountX2"    => self::uint16, 
 | |
|     "searchRange"   => self::uint16, 
 | |
|     "entrySelector" => self::uint16, 
 | |
|     "rangeShift"    => self::uint16,
 | |
|   );
 | |
|   
 | |
|   protected function _parse(){
 | |
|     $font = $this->getFont();
 | |
|     
 | |
|     $cmap_offset = $font->pos();
 | |
|     
 | |
|     $data = $font->unpack(self::$header_format);
 | |
|     
 | |
|     $subtables = array();
 | |
|     for($i = 0; $i < $data["numberSubtables"]; $i++){
 | |
|       $subtables[] = $font->unpack(self::$subtable_header_format);
 | |
|     }
 | |
|     $data["subtables"] = $subtables;
 | |
|     
 | |
|     foreach($data["subtables"] as $i => &$subtable) {
 | |
|       $font->seek($cmap_offset + $subtable["offset"]);
 | |
|       
 | |
|       $subtable["format"] = $font->readUInt16();
 | |
|       
 | |
|       // @todo Only CMAP version 4
 | |
|       if($subtable["format"] != 4) {
 | |
|         unset($data["subtables"][$i]);
 | |
|         $data["numberSubtables"]--;
 | |
|         continue;
 | |
|       }
 | |
|       
 | |
|       $subtable += $font->unpack(self::$subtable_v4_format);
 | |
|       $segCount = $subtable["segCountX2"] / 2;
 | |
|       $subtable["segCount"] = $segCount;
 | |
|       
 | |
|       $endCode       = $font->r(array(self::uint16, $segCount));
 | |
|       
 | |
|       $font->readUInt16(); // reservedPad
 | |
|       
 | |
|       $startCode     = $font->r(array(self::uint16, $segCount));
 | |
|       $idDelta       = $font->r(array(self::int16, $segCount));
 | |
|       
 | |
|       $ro_start      = $font->pos();
 | |
|       $idRangeOffset = $font->r(array(self::uint16, $segCount));
 | |
|       
 | |
|       $glyphIndexArray = array();
 | |
|       for($i = 0; $i < $segCount; $i++) {
 | |
|         $c1 = $startCode[$i];
 | |
|         $c2 = $endCode[$i];
 | |
|         $d  = $idDelta[$i];
 | |
|         $ro = $idRangeOffset[$i];
 | |
|         
 | |
|         if($ro > 0)
 | |
|           $font->seek($subtable["offset"] + 2 * $i + $ro);
 | |
|           
 | |
|         for($c = $c1; $c <= $c2; $c++) {
 | |
|           if ($ro == 0)
 | |
|             $gid = ($c + $d) & 0xFFFF;
 | |
|           else {
 | |
|             $offset = ($c - $c1) * 2 + $ro;
 | |
|             $offset = $ro_start + 2 * $i + $offset;
 | |
|             
 | |
|             $font->seek($offset);
 | |
|             $gid = $font->readUInt16();
 | |
|             
 | |
|             if ($gid != 0)
 | |
|                $gid = ($gid + $d) & 0xFFFF;
 | |
|           }
 | |
|           
 | |
|           if($gid > 0) {
 | |
|             $glyphIndexArray[$c] = $gid;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       $subtable += array(
 | |
|         "endCode"         => $endCode,
 | |
|         "startCode"       => $startCode,
 | |
|         "idDelta"         => $idDelta,
 | |
|         "idRangeOffset"   => $idRangeOffset,
 | |
|         "glyphIndexArray" => $glyphIndexArray,
 | |
|       );
 | |
|     }
 | |
|     
 | |
|     $this->data = $data;
 | |
|   }
 | |
|   
 | |
|   function _encode(){
 | |
|     $font = $this->getFont();
 | |
| 
 | |
|     $subset = $font->getSubset();
 | |
|     $glyphIndexArray = $font->getUnicodeCharMap();
 | |
| 
 | |
|     $newGlyphIndexArray = array();
 | |
|     foreach ($glyphIndexArray as $code => $gid) {
 | |
|       $new_gid = array_search($gid, $subset);
 | |
|       if ($new_gid !== false) {
 | |
|         $newGlyphIndexArray[$code] = $new_gid;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     ksort($newGlyphIndexArray); // Sort by char code
 | |
|     
 | |
|     $segments = array();
 | |
| 
 | |
|     $i = -1;
 | |
|     $prevCode = 0xFFFF;
 | |
|     $prevGid = 0xFFFF;
 | |
| 
 | |
|     foreach($newGlyphIndexArray as $code => $gid) {
 | |
|       if (
 | |
|         $prevCode + 1 != $code ||
 | |
|         $prevGid + 1 != $gid
 | |
|       ) {
 | |
|         $i++;
 | |
|         $segments[$i] = array();
 | |
|       }
 | |
|       
 | |
|       $segments[$i][] = array($code, $gid);
 | |
| 
 | |
|       $prevCode = $code;
 | |
|       $prevGid = $gid;
 | |
|     }
 | |
|     
 | |
|     $segments[][] = array(0xFFFF, 0xFFFF);
 | |
|     
 | |
|     $startCode = array();
 | |
|     $endCode = array();
 | |
|     $idDelta = array();
 | |
|     
 | |
|     foreach($segments as $codes){
 | |
|       $start = reset($codes);
 | |
|       $end   = end($codes);
 | |
|       
 | |
|       $startCode[] = $start[0];
 | |
|       $endCode[]   = $end[0];
 | |
|       $idDelta[]   = $start[1] - $start[0];
 | |
|     }
 | |
|     
 | |
|     $segCount = count($startCode);
 | |
|     $idRangeOffset = array_fill(0, $segCount, 0);
 | |
|     
 | |
|     $searchRange = 1;
 | |
|     $entrySelector = 0;
 | |
|     while ($searchRange * 2 <= $segCount) {
 | |
|       $searchRange *= 2;
 | |
|       $entrySelector++;
 | |
|     }
 | |
|     $searchRange *= 2;
 | |
|     $rangeShift = $segCount * 2 - $searchRange;
 | |
|     
 | |
|     $subtables = array(
 | |
|       array(
 | |
|         // header
 | |
|         "platformID"         => 3, // Unicode
 | |
|         "platformSpecificID" => 1,
 | |
|         "offset"        => null,
 | |
|       
 | |
|         // subtable
 | |
|         "format"        => 4, 
 | |
|         "length"        => null, 
 | |
|         "language"      => 0, 
 | |
|         "segCount"      => $segCount, 
 | |
|         "segCountX2"    => $segCount * 2, 
 | |
|         "searchRange"   => $searchRange, 
 | |
|         "entrySelector" => $entrySelector, 
 | |
|         "rangeShift"    => $rangeShift,
 | |
|         "startCode"     => $startCode,
 | |
|         "endCode"       => $endCode,
 | |
|         "idDelta"       => $idDelta,
 | |
|         "idRangeOffset" => $idRangeOffset, 
 | |
|         "glyphIndexArray" => $newGlyphIndexArray,
 | |
|       )
 | |
|     );
 | |
|     
 | |
|     $data = array(
 | |
|       "version"         => 0,
 | |
|       "numberSubtables" => count($subtables),
 | |
|       "subtables"       => $subtables,
 | |
|     );
 | |
| 
 | |
|     $length = $font->pack(self::$header_format, $data);
 | |
|     
 | |
|     $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format
 | |
|     $subtable_headers_offset = $font->pos();
 | |
|     
 | |
|     $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size);
 | |
|     
 | |
|     // write subtables data
 | |
|     foreach($data["subtables"] as $i => $subtable) {
 | |
|       $length_before = $length;
 | |
|       $data["subtables"][$i]["offset"] = $length;
 | |
|       
 | |
|       $length += $font->writeUInt16($subtable["format"]);
 | |
|       
 | |
|       $before_subheader = $font->pos();
 | |
|       $length += $font->pack(self::$subtable_v4_format, $subtable);
 | |
| 
 | |
|       $segCount = $subtable["segCount"];
 | |
|       $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]);
 | |
|       $length += $font->writeUInt16(0); // reservedPad
 | |
|       $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]);
 | |
|       $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]);
 | |
|       $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]);
 | |
|       $length += $font->w(array(self::uint16, $segCount), array_values($subtable["glyphIndexArray"]));
 | |
|       
 | |
|       $after_subtable = $font->pos();
 | |
|       
 | |
|       $subtable["length"] = $length - $length_before;
 | |
|       $font->seek($before_subheader);
 | |
|       $length += $font->pack(self::$subtable_v4_format, $subtable);
 | |
|       
 | |
|       $font->seek($after_subtable);
 | |
|     }
 | |
|     
 | |
|     // write subtables headers
 | |
|     $font->seek($subtable_headers_offset);
 | |
|     foreach($data["subtables"] as $subtable) {
 | |
|       $font->pack(self::$subtable_header_format, $subtable);
 | |
|     }
 | |
|     
 | |
|     return $length;
 | |
|   }
 | |
| }
 | 
