<?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
 */

namespace FontLib\Table\Type;
use FontLib\Table\Table;

/**
 * `cmap` font table.
 *
 * @package php-font-lib
 */
class cmap extends 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;
  }
}
