--- /dev/null
+<?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;
+
+/**
+ * Generic font file binary stream.
+ *
+ * @package php-font-lib
+ */
+class BinaryStream {
+ /**
+ * @var resource The file pointer
+ */
+ protected $f;
+
+ const uint8 = 1;
+ const int8 = 2;
+ const uint16 = 3;
+ const int16 = 4;
+ const uint32 = 5;
+ const int32 = 6;
+ const shortFrac = 7;
+ const Fixed = 8;
+ const FWord = 9;
+ const uFWord = 10;
+ const F2Dot14 = 11;
+ const longDateTime = 12;
+ const char = 13;
+
+ const modeRead = "rb";
+ const modeWrite = "wb";
+ const modeReadWrite = "rb+";
+
+ static function backtrace() {
+ var_dump(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
+ }
+
+ /**
+ * Open a font file in read mode
+ *
+ * @param string $filename The file name of the font to open
+ *
+ * @return bool
+ */
+ public function load($filename) {
+ return $this->open($filename, self::modeRead);
+ }
+
+ /**
+ * Open a font file in a chosen mode
+ *
+ * @param string $filename The file name of the font to open
+ * @param string $mode The opening mode
+ *
+ * @throws \Exception
+ * @return bool
+ */
+ public function open($filename, $mode = self::modeRead) {
+ if (!in_array($mode, array(self::modeRead, self::modeWrite, self::modeReadWrite))) {
+ throw new \Exception("Unkown file open mode");
+ }
+
+ $this->f = fopen($filename, $mode);
+
+ return $this->f != false;
+ }
+
+ /**
+ * Close the internal file pointer
+ */
+ public function close() {
+ return fclose($this->f) != false;
+ }
+
+ /**
+ * Change the internal file pointer
+ *
+ * @param resource $fp
+ *
+ * @throws \Exception
+ */
+ public function setFile($fp) {
+ if (!is_resource($fp)) {
+ throw new \Exception('$fp is not a valid resource');
+ }
+
+ $this->f = $fp;
+ }
+
+ /**
+ * Create a temporary file in write mode
+ *
+ * @param bool $allow_memory Allow in-memory files
+ *
+ * @return resource the temporary file pointer resource
+ */
+ public static function getTempFile($allow_memory = true) {
+ $f = null;
+
+ if ($allow_memory) {
+ $f = fopen("php://temp", "rb+");
+ }
+ else {
+ $f = fopen(tempnam(sys_get_temp_dir(), "fnt"), "rb+");
+ }
+
+ return $f;
+ }
+
+ /**
+ * Move the internal file pinter to $offset bytes
+ *
+ * @param int $offset
+ *
+ * @return bool True if the $offset position exists in the file
+ */
+ public function seek($offset) {
+ return fseek($this->f, $offset, SEEK_SET) == 0;
+ }
+
+ /**
+ * Gives the current position in the file
+ *
+ * @return int The current position
+ */
+ public function pos() {
+ return ftell($this->f);
+ }
+
+ public function skip($n) {
+ fseek($this->f, $n, SEEK_CUR);
+ }
+
+ public function read($n) {
+ if ($n < 1) {
+ return "";
+ }
+
+ return fread($this->f, $n);
+ }
+
+ public function write($data, $length = null) {
+ if ($data === null || $data === "" || $data === false) {
+ return 0;
+ }
+
+ return fwrite($this->f, $data, $length);
+ }
+
+ public function readUInt8() {
+ return ord($this->read(1));
+ }
+
+ public function readUInt8Many($count) {
+ return array_values(unpack("C*", $this->read($count)));
+ }
+
+ public function writeUInt8($data) {
+ return $this->write(chr($data), 1);
+ }
+
+ public function readInt8() {
+ $v = $this->readUInt8();
+
+ if ($v >= 0x80) {
+ $v -= 0x100;
+ }
+
+ return $v;
+ }
+
+ public function readInt8Many($count) {
+ return array_values(unpack("c*", $this->read($count)));
+ }
+
+ public function writeInt8($data) {
+ if ($data < 0) {
+ $data += 0x100;
+ }
+
+ return $this->writeUInt8($data);
+ }
+
+ public function readUInt16() {
+ $a = unpack("nn", $this->read(2));
+
+ return $a["n"];
+ }
+
+ public function readUInt16Many($count) {
+ return array_values(unpack("n*", $this->read($count * 2)));
+ }
+
+ public function readUFWord() {
+ return $this->readUInt16();
+ }
+
+ public function writeUInt16($data) {
+ return $this->write(pack("n", $data), 2);
+ }
+
+ public function writeUFWord($data) {
+ return $this->writeUInt16($data);
+ }
+
+ public function readInt16() {
+ $a = unpack("nn", $this->read(2));
+ $v = $a["n"];
+
+ if ($v >= 0x8000) {
+ $v -= 0x10000;
+ }
+
+ return $v;
+ }
+
+ public function readInt16Many($count) {
+ $vals = array_values(unpack("n*", $this->read($count * 2)));
+ foreach ($vals as &$v) {
+ if ($v >= 0x8000) {
+ $v -= 0x10000;
+ }
+ }
+
+ return $vals;
+ }
+
+ public function readFWord() {
+ return $this->readInt16();
+ }
+
+ public function writeInt16($data) {
+ if ($data < 0) {
+ $data += 0x10000;
+ }
+
+ return $this->writeUInt16($data);
+ }
+
+ public function writeFWord($data) {
+ return $this->writeInt16($data);
+ }
+
+ public function readUInt32() {
+ $a = unpack("NN", $this->read(4));
+
+ return $a["N"];
+ }
+
+ public function writeUInt32($data) {
+ return $this->write(pack("N", $data), 4);
+ }
+
+ public function readFixed() {
+ $d = $this->readInt16();
+ $d2 = $this->readUInt16();
+
+ return round($d + $d2 / 0x10000, 4);
+ }
+
+ public function writeFixed($data) {
+ $left = floor($data);
+ $right = ($data - $left) * 0x10000;
+
+ return $this->writeInt16($left) + $this->writeUInt16($right);
+ }
+
+ public function readLongDateTime() {
+ $this->readUInt32(); // ignored
+ $date = $this->readUInt32() - 2082844800;
+
+ # PHP_INT_MIN isn't defined in PHP < 7.0
+ $php_int_min = defined("PHP_INT_MIN") ? PHP_INT_MIN : ~PHP_INT_MAX;
+
+ if (is_string($date) || $date > PHP_INT_MAX || $date < $php_int_min) {
+ $date = 0;
+ }
+
+ return strftime("%Y-%m-%d %H:%M:%S", $date);
+ }
+
+ public function writeLongDateTime($data) {
+ $date = strtotime($data);
+ $date += 2082844800;
+
+ return $this->writeUInt32(0) + $this->writeUInt32($date);
+ }
+
+ public function unpack($def) {
+ $d = array();
+ foreach ($def as $name => $type) {
+ $d[$name] = $this->r($type);
+ }
+
+ return $d;
+ }
+
+ public function pack($def, $data) {
+ $bytes = 0;
+ foreach ($def as $name => $type) {
+ $bytes += $this->w($type, $data[$name]);
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Read a data of type $type in the file from the current position
+ *
+ * @param mixed $type The data type to read
+ *
+ * @return mixed The data that was read
+ */
+ public function r($type) {
+ switch ($type) {
+ case self::uint8:
+ return $this->readUInt8();
+ case self::int8:
+ return $this->readInt8();
+ case self::uint16:
+ return $this->readUInt16();
+ case self::int16:
+ return $this->readInt16();
+ case self::uint32:
+ return $this->readUInt32();
+ case self::int32:
+ return $this->readUInt32();
+ case self::shortFrac:
+ return $this->readFixed();
+ case self::Fixed:
+ return $this->readFixed();
+ case self::FWord:
+ return $this->readInt16();
+ case self::uFWord:
+ return $this->readUInt16();
+ case self::F2Dot14:
+ return $this->readInt16();
+ case self::longDateTime:
+ return $this->readLongDateTime();
+ case self::char:
+ return $this->read(1);
+ default:
+ if (is_array($type)) {
+ if ($type[0] == self::char) {
+ return $this->read($type[1]);
+ }
+ if ($type[0] == self::uint16) {
+ return $this->readUInt16Many($type[1]);
+ }
+ if ($type[0] == self::int16) {
+ return $this->readInt16Many($type[1]);
+ }
+ if ($type[0] == self::uint8) {
+ return $this->readUInt8Many($type[1]);
+ }
+ if ($type[0] == self::int8) {
+ return $this->readInt8Many($type[1]);
+ }
+
+ $ret = array();
+ for ($i = 0; $i < $type[1]; $i++) {
+ $ret[] = $this->r($type[0]);
+ }
+
+ return $ret;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Write $data of type $type in the file from the current position
+ *
+ * @param mixed $type The data type to write
+ * @param mixed $data The data to write
+ *
+ * @return int The number of bytes read
+ */
+ public function w($type, $data) {
+ switch ($type) {
+ case self::uint8:
+ return $this->writeUInt8($data);
+ case self::int8:
+ return $this->writeInt8($data);
+ case self::uint16:
+ return $this->writeUInt16($data);
+ case self::int16:
+ return $this->writeInt16($data);
+ case self::uint32:
+ return $this->writeUInt32($data);
+ case self::int32:
+ return $this->writeUInt32($data);
+ case self::shortFrac:
+ return $this->writeFixed($data);
+ case self::Fixed:
+ return $this->writeFixed($data);
+ case self::FWord:
+ return $this->writeInt16($data);
+ case self::uFWord:
+ return $this->writeUInt16($data);
+ case self::F2Dot14:
+ return $this->writeInt16($data);
+ case self::longDateTime:
+ return $this->writeLongDateTime($data);
+ case self::char:
+ return $this->write($data, 1);
+ default:
+ if (is_array($type)) {
+ if ($type[0] == self::char) {
+ return $this->write($data, $type[1]);
+ }
+
+ $ret = 0;
+ for ($i = 0; $i < $type[1]; $i++) {
+ if (isset($data[$i])) {
+ $ret += $this->w($type[0], $data[$i]);
+ }
+ }
+
+ return $ret;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Converts a Uint32 value to string
+ *
+ * @param int $uint32
+ *
+ * @return string The string
+ */
+ public function convertUInt32ToStr($uint32) {
+ return chr(($uint32 >> 24) & 0xFF) . chr(($uint32 >> 16) & 0xFF) . chr(($uint32 >> 8) & 0xFF) . chr($uint32 & 0xFF);
+ }
+}