Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve TrueType specificiation compliance for generated fonts #124

Merged
merged 9 commits into from
Jan 6, 2024
26 changes: 11 additions & 15 deletions src/FontLib/Table/DirectoryEntry.php
Original file line number Diff line number Diff line change
@@ -49,20 +49,8 @@ static function computeChecksum($data) {
$data = str_pad($data, $len + (4 - $mod), "\0");
}

$len = mb_strlen($data, '8bit');

$hi = 0x0000;
$lo = 0x0000;

for ($i = 0; $i < $len; $i += 4) {
$hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
$lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
$hi += $lo >> 16;
$lo = $lo & 0xFFFF;
$hi = $hi & 0xFFFF;
}

return ($hi << 8) + $lo;
$table = unpack("N*", $data);
return array_sum($table);
}

function __construct(File $font) {
@@ -93,6 +81,14 @@ function encode($entry_offset) {
$this->offset = $table_offset;
$table_length = $data->encode();

$font->seek($table_offset + $table_length);
$pad = 0;
$mod = $table_length % 4;
if ($mod != 0) {
$pad = 4 - $mod;
$font->write(str_pad("", $pad, "\0"), $pad);
}

$font->seek($table_offset);
$table_data = $font->read($table_length);

@@ -105,7 +101,7 @@ function encode($entry_offset) {

Font::d("Bytes written = $table_length");

$font->seek($table_offset + $table_length);
$font->seek($table_offset + $table_length + $pad);
}

/**
259 changes: 168 additions & 91 deletions src/FontLib/Table/Type/cmap.php
Original file line number Diff line number Diff line change
@@ -26,6 +26,18 @@ class cmap extends Table {
"offset" => self::uint32,
);

private static $subtable_v2_format = array(
"length" => self::uint16,
"language" => self::uint16
);

private static $subtable_v2_format_subheader = array(
"firstCode" => self::uint16,
"entryCount" => self::uint16,
"idDelta" => self::int16,
"idRangeOffset" => self::uint16
);

private static $subtable_v4_format = array(
"length" => self::uint16,
"language" => self::uint16,
@@ -38,7 +50,7 @@ class cmap extends Table {
private static $subtable_v12_format = array(
"length" => self::uint32,
"language" => self::uint32,
"ngroups" => self::uint32
"ngroups" => self::uint32
);

protected function _parse() {
@@ -60,105 +72,170 @@ protected function _parse() {

$subtable["format"] = $font->readUInt16();

// @todo Only CMAP version 4 and 12
if (($subtable["format"] != 4) && ($subtable["format"] != 12)) {
unset($data["subtables"][$i]);
$data["numberSubtables"]--;
continue;
}

if ($subtable["format"] == 12) {

$font->readUInt16();

$subtable += $font->unpack(self::$subtable_v12_format);

$glyphIndexArray = array();
$endCodes = array();
$startCodes = array();

for ($p = 0; $p < $subtable['ngroups']; $p++) {

$startCode = $startCodes[] = $font->readUInt32();
$endCode = $endCodes[] = $font->readUInt32();
$startGlyphCode = $font->readUInt32();

for ($c = $startCode; $c <= $endCode; $c++) {
$glyphIndexArray[$c] = $startGlyphCode;
$startGlyphCode++;
}
}

$subtable += array(
"startCode" => $startCodes,
"endCode" => $endCodes,
"glyphIndexArray" => $glyphIndexArray,
);

}
else if ($subtable["format"] == 4) {

$subtable += $font->unpack(self::$subtable_v4_format);

$segCount = $subtable["segCountX2"] / 2;
$subtable["segCount"] = $segCount;

$endCode = $font->readUInt16Many($segCount);

$font->readUInt16(); // reservedPad

$startCode = $font->readUInt16Many($segCount);
$idDelta = $font->readInt16Many($segCount);

$ro_start = $font->pos();
$idRangeOffset = $font->readUInt16Many($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);
switch ($subtable["format"]) {
case 0:
case 6:
case 8:
case 10:
case 13:
case 14:
unset($data["subtables"][$i]);
$data["numberSubtables"]--;
continue 2;

case 2:
$subtable += $font->unpack(self::$subtable_v2_format);

$subHeaderKeys = array_map(function($val) { return $val / 8; }, $font->readUInt16Many(256));
$subHeaders = array();

$glyphIdArray = array();
$maxSubHeaderIndex = max($subHeaderKeys);
for ($i = 0; $i <= $maxSubHeaderIndex; $i++) {
$subHeader = $font->unpack(self::$subtable_v2_format_subheader);
$offset = $font->pos();
$subHeader["glyphIdArrayOffset"] = $offset + $subHeader["idRangeOffset"] - 2;
$subHeaders[$i] = $subHeader;

if (!\array_key_exists($subHeader["glyphIdArrayOffset"], $glyphIdArray) || count($glyphIdArray[$subHeader["glyphIdArrayOffset"]]) < $subHeader["entryCount"]) {
$font->seek($subHeader["glyphIdArrayOffset"]);
$glyphIdArray[$subHeader["glyphIdArrayOffset"]] = $font->readUInt16Many($subHeader["entryCount"]);
$font->seek($offset);
}
}

for ($c = $c1; $c <= $c2; $c++) {
if ($c === 0xFFFF) {
continue;
$glyphIndexArray = array();
foreach ($subHeaderKeys as $highByte => $subHeaderKey) {
$subHeader = $subHeaders[$subHeaderKey];
if ($subHeaderKey === 0) {
$c = $highByte;
if ($c < $subHeader["firstCode"] || $c >= ($subHeader["firstCode"] + $subHeader["entryCount"])) {
$glyphIndexArray[$c] = 0;
continue;
}
$c = $highByte;
$index = $c - $subHeader["firstCode"];
$glyphId = $glyphIdArray[$subHeader["glyphIdArrayOffset"]][$index];
if ($glyphId === 0) {
$glyphIndexArray[$c] = 0;
} else {
$glyphIndexArray[$c] = ($glyphId + $subHeader["idDelta"]) & 0xFFFF;
}
} else {
for ($index = 0; $index < $subHeader["entryCount"]; $index++) {
$c = null;
$lowByte = $subHeader["firstCode"] + $index;
$c = (($highByte & 0xFF) << 8) | ($lowByte & 0xFF);
$glyphId = $glyphIdArray[$subHeader["glyphIdArrayOffset"]][$index];
if ($glyphId === 0) {
$glyphIndexArray[$c] = 0;
} else {
$glyphIndexArray[$c] = ($glyphId + $subHeader["idDelta"]) & 0xFFFF;
}
}
}
}

if ($ro == 0) {
$gid = ($c + $d) & 0xFFFF;
$subtable += array(
"subHeaderKeys" => $subHeaderKeys,
"subHeaders" => $subHeaders,
"glyphIdArray" => $glyphIdArray,
"glyphIndexArray" => $glyphIndexArray
);

break;

case 4:
$subtable += $font->unpack(self::$subtable_v4_format);

$segCount = $subtable["segCountX2"] / 2;
$subtable["segCount"] = $segCount;

$endCode = $font->readUInt16Many($segCount);

$font->readUInt16(); // reservedPad

$startCode = $font->readUInt16Many($segCount);
$idDelta = $font->readInt16Many($segCount);

$ro_start = $font->pos();
$idRangeOffset = $font->readUInt16Many($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);
}
else {
$offset = ($c - $c1) * 2 + $ro;
$offset = $ro_start + 2 * $i + $offset;

$gid = 0;
if ($font->seek($offset) === true) {
$gid = $font->readUInt16();

for ($c = $c1; $c <= $c2; $c++) {
if ($c === 0xFFFF) {
continue;
}

if ($gid != 0) {
$gid = ($gid + $d) & 0xFFFF;

if ($ro == 0) {
$gid = ($c + $d) & 0xFFFF;
}
else {
$offset = ($c - $c1) * 2 + $ro;
$offset = $ro_start + 2 * $i + $offset;

$gid = 0;
if ($font->seek($offset) === true) {
$gid = $font->readUInt16();
}

if ($gid != 0) {
$gid = ($gid + $d) & 0xFFFF;
}
}

if ($gid >= 0) {
$glyphIndexArray[$c] = $gid;
}
}

if ($gid >= 0) {
$glyphIndexArray[$c] = $gid;
}

$subtable += array(
"endCode" => $endCode,
"startCode" => $startCode,
"idDelta" => $idDelta,
"idRangeOffset" => $idRangeOffset,
"glyphIndexArray" => $glyphIndexArray
);
break;

case 12:
$font->readUInt16();

$subtable += $font->unpack(self::$subtable_v12_format);

$glyphIndexArray = array();
$endCodes = array();
$startCodes = array();

for ($p = 0; $p < $subtable['ngroups']; $p++) {

$startCode = $startCodes[] = $font->readUInt32();
$endCode = $endCodes[] = $font->readUInt32();
$startGlyphCode = $font->readUInt32();

for ($c = $startCode; $c <= $endCode; $c++) {
$glyphIndexArray[$c] = $startGlyphCode;
$startGlyphCode++;
}
}
}

$subtable += array(
"endCode" => $endCode,
"startCode" => $startCode,
"idDelta" => $idDelta,
"idRangeOffset" => $idRangeOffset,
"glyphIndexArray" => $glyphIndexArray,
);

$subtable += array(
"startCode" => $startCodes,
"endCode" => $endCodes,
"glyphIndexArray" => $glyphIndexArray,
);
break;
}
}

@@ -202,7 +279,7 @@ function _encode() {
$prevGid = $gid;
}

$segments[][] = array(0xFFFF, 0xFFFF);
$segments[][] = array(0xFFFF, null);

$startCode = array();
$endCode = array();
11 changes: 10 additions & 1 deletion src/FontLib/Table/Type/glyf.php
Original file line number Diff line number Diff line change
@@ -143,7 +143,16 @@ protected function _encode() {
$length = 0;
foreach ($subset as $gid) {
$loca[] = $length;
$length += $data[$gid]->encode();

$bytes = $data[$gid]->encode();

$pad = 0;
$mod = $bytes % 4;
if ($mod != 0) {
$pad = 4 - $mod;
$font->write(str_pad("", $pad, "\0"), $pad);
}
$length += $bytes + $pad;
}

$loca[] = $length; // dummy loca
5 changes: 5 additions & 0 deletions src/FontLib/Table/Type/head.php
Original file line number Diff line number Diff line change
@@ -43,4 +43,9 @@ protected function _parse() {
throw new Exception("Incorrect magic number (" . dechex($this->data["magicNumber"]) . ")");
}
}

function _encode() {
$this->data["checkSumAdjustment"] = 0;
return parent::_encode();
}
}
Loading