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
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -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);
}

/**
Expand Down
259 changes: 168 additions & 91 deletions src/FontLib/Table/Type/cmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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() {
Expand All @@ -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;
}
}

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

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

$startCode = array();
$endCode = array();
Expand Down
11 changes: 10 additions & 1 deletion src/FontLib/Table/Type/glyf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/FontLib/Table/Type/head.php
Original file line number Diff line number Diff line change
Expand Up @@ -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