<?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 WPUM\FontLib\TrueType;

use WPUM\FontLib\AdobeFontMetrics;
use WPUM\FontLib\Font;
use WPUM\FontLib\BinaryStream;
use WPUM\FontLib\Table\Table;
use WPUM\FontLib\Table\DirectoryEntry;
use WPUM\FontLib\Table\Type\glyf;
use WPUM\FontLib\Table\Type\name;
use WPUM\FontLib\Table\Type\nameRecord;
/**
 * TrueType font file.
 *
 * @package php-font-lib
 */
class File extends BinaryStream
{
    /**
     * @var Header
     */
    public $header = array();
    private $tableOffset = 0;
    // Used for TTC
    private static $raw = \false;
    protected $directory = array();
    protected $data = array();
    protected $glyph_subset = array();
    public $glyph_all = array();
    static $macCharNames = array(".notdef", ".null", "CR", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "increment", "guillemotleft", "guillemotright", "ellipsis", "nbspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "applelogo", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idot", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dmacron");
    function getTable()
    {
        $this->parseTableEntries();
        return $this->directory;
    }
    function setTableOffset($offset)
    {
        $this->tableOffset = $offset;
    }
    function parse()
    {
        $this->parseTableEntries();
        $this->data = array();
        foreach ($this->directory as $tag => $table) {
            if (empty($this->data[$tag])) {
                $this->readTable($tag);
            }
        }
    }
    function utf8toUnicode($str)
    {
        $len = \mb_strlen($str, '8bit');
        $out = array();
        for ($i = 0; $i < $len; $i++) {
            $uni = -1;
            $h = \ord($str[$i]);
            if ($h <= 0x7f) {
                $uni = $h;
            } elseif ($h >= 0xc2) {
                if ($h <= 0xdf && $i < $len - 1) {
                    $uni = ($h & 0x1f) << 6 | \ord($str[++$i]) & 0x3f;
                } elseif ($h <= 0xef && $i < $len - 2) {
                    $uni = ($h & 0xf) << 12 | (\ord($str[++$i]) & 0x3f) << 6 | \ord($str[++$i]) & 0x3f;
                } elseif ($h <= 0xf4 && $i < $len - 3) {
                    $uni = ($h & 0xf) << 18 | (\ord($str[++$i]) & 0x3f) << 12 | (\ord($str[++$i]) & 0x3f) << 6 | \ord($str[++$i]) & 0x3f;
                }
            }
            if ($uni >= 0) {
                $out[] = $uni;
            }
        }
        return $out;
    }
    function getUnicodeCharMap()
    {
        $subtable = null;
        foreach ($this->getData("cmap", "subtables") as $_subtable) {
            if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
                $subtable = $_subtable;
                break;
            }
        }
        if ($subtable) {
            return $subtable["glyphIndexArray"];
        }
        return null;
    }
    function setSubset($subset)
    {
        if (!\is_array($subset)) {
            $subset = $this->utf8toUnicode($subset);
        }
        $subset = \array_unique($subset);
        $glyphIndexArray = $this->getUnicodeCharMap();
        if (!$glyphIndexArray) {
            return;
        }
        $gids = array(
            0,
            // .notdef
            1,
        );
        foreach ($subset as $code) {
            if (!isset($glyphIndexArray[$code])) {
                continue;
            }
            $gid = $glyphIndexArray[$code];
            $gids[$gid] = $gid;
        }
        /** @var glyf $glyf */
        $glyf = $this->getTableObject("glyf");
        $gids = $glyf->getGlyphIDs($gids);
        \sort($gids);
        $this->glyph_subset = $gids;
        $this->glyph_all = \array_values($glyphIndexArray);
        // FIXME
    }
    function getSubset()
    {
        if (empty($this->glyph_subset)) {
            return $this->glyph_all;
        }
        return $this->glyph_subset;
    }
    function encode($tags = array())
    {
        if (!self::$raw) {
            $tags = \array_merge(array("head", "hhea", "cmap", "hmtx", "maxp", "glyf", "loca", "name", "post"), $tags);
        } else {
            $tags = \array_keys($this->directory);
        }
        $num_tables = \count($tags);
        $n = 16;
        // @todo
        Font::d("Tables : " . \implode(", ", $tags));
        /** @var DirectoryEntry[] $entries */
        $entries = array();
        foreach ($tags as $tag) {
            if (!isset($this->directory[$tag])) {
                Font::d("  >> '{$tag}' table doesn't exist");
                continue;
            }
            $entries[$tag] = $this->directory[$tag];
        }
        $this->header->data["numTables"] = $num_tables;
        $this->header->encode();
        $directory_offset = $this->pos();
        $offset = $directory_offset + $num_tables * $n;
        $this->seek($offset);
        $i = 0;
        foreach ($entries as $entry) {
            $entry->encode($directory_offset + $i * $n);
            $i++;
        }
    }
    function parseHeader()
    {
        if (!empty($this->header)) {
            return;
        }
        $this->seek($this->tableOffset);
        $this->header = new Header($this);
        $this->header->parse();
    }
    function getFontType()
    {
        $class_parts = \explode("\\", \get_class($this));
        return $class_parts[1];
    }
    function parseTableEntries()
    {
        $this->parseHeader();
        if (!empty($this->directory)) {
            return;
        }
        if (empty($this->header->data["numTables"])) {
            return;
        }
        $type = $this->getFontType();
        $class = "FontLib\\{$type}\\TableDirectoryEntry";
        for ($i = 0; $i < $this->header->data["numTables"]; $i++) {
            /** @var TableDirectoryEntry $entry */
            $entry = new $class($this);
            $entry->parse();
            $this->directory[$entry->tag] = $entry;
        }
    }
    function normalizeFUnit($value, $base = 1000)
    {
        return \round($value * ($base / $this->getData("head", "unitsPerEm")));
    }
    protected function readTable($tag)
    {
        $this->parseTableEntries();
        if (!self::$raw) {
            $name_canon = \preg_replace("/[^a-z0-9]/", "", \strtolower($tag));
            $class = "FontLib\\Table\\Type\\{$name_canon}";
            if (!isset($this->directory[$tag]) || !@\class_exists($class)) {
                return;
            }
        } else {
            $class = "WPUM\\FontLib\\Table\\Table";
        }
        /** @var Table $table */
        $table = new $class($this->directory[$tag]);
        $table->parse();
        $this->data[$tag] = $table;
    }
    /**
     * @param $name
     *
     * @return Table
     */
    public function getTableObject($name)
    {
        return $this->data[$name];
    }
    public function setTableObject($name, Table $data)
    {
        $this->data[$name] = $data;
    }
    public function getData($name, $key = null)
    {
        $this->parseTableEntries();
        if (empty($this->data[$name])) {
            $this->readTable($name);
        }
        if (!isset($this->data[$name])) {
            return null;
        }
        if (!$key) {
            return $this->data[$name]->data;
        } else {
            return $this->data[$name]->data[$key];
        }
    }
    function addDirectoryEntry(DirectoryEntry $entry)
    {
        $this->directory[$entry->tag] = $entry;
    }
    function saveAdobeFontMetrics($file, $encoding = null)
    {
        $afm = new AdobeFontMetrics($this);
        $afm->write($file, $encoding);
    }
    /**
     * Get a specific name table string value from its ID
     *
     * @param int $nameID The name ID
     *
     * @return string|null
     */
    function getNameTableString($nameID)
    {
        /** @var nameRecord[] $records */
        $records = $this->getData("name", "records");
        if (!isset($records[$nameID])) {
            return null;
        }
        return $records[$nameID]->string;
    }
    /**
     * Get font copyright
     *
     * @return string|null
     */
    function getFontCopyright()
    {
        return $this->getNameTableString(name::NAME_COPYRIGHT);
    }
    /**
     * Get font name
     *
     * @return string|null
     */
    function getFontName()
    {
        return $this->getNameTableString(name::NAME_NAME);
    }
    /**
     * Get font subfamily
     *
     * @return string|null
     */
    function getFontSubfamily()
    {
        return $this->getNameTableString(name::NAME_SUBFAMILY);
    }
    /**
     * Get font subfamily ID
     *
     * @return string|null
     */
    function getFontSubfamilyID()
    {
        return $this->getNameTableString(name::NAME_SUBFAMILY_ID);
    }
    /**
     * Get font full name
     *
     * @return string|null
     */
    function getFontFullName()
    {
        return $this->getNameTableString(name::NAME_FULL_NAME);
    }
    /**
     * Get font version
     *
     * @return string|null
     */
    function getFontVersion()
    {
        return $this->getNameTableString(name::NAME_VERSION);
    }
    /**
     * Get font weight
     *
     * @return string|null
     */
    function getFontWeight()
    {
        return $this->getTableObject("OS/2")->data["usWeightClass"];
    }
    /**
     * Get font Postscript name
     *
     * @return string|null
     */
    function getFontPostscriptName()
    {
        return $this->getNameTableString(name::NAME_POSTSCRIPT_NAME);
    }
    function reduce()
    {
        $names_to_keep = array(name::NAME_COPYRIGHT, name::NAME_NAME, name::NAME_SUBFAMILY, name::NAME_SUBFAMILY_ID, name::NAME_FULL_NAME, name::NAME_VERSION, name::NAME_POSTSCRIPT_NAME);
        foreach ($this->data["name"]->data["records"] as $id => $rec) {
            if (!\in_array($id, $names_to_keep)) {
                unset($this->data["name"]->data["records"][$id]);
            }
        }
    }
}
