<?php
use FreePDF\TTFParser;
require('../TTFParser.php');
function Message($txt, $severity = ''): void
{
if (PHP_SAPI === 'cli') {
if ($severity) {
echo "$severity: ";
}
echo "$txt\n";
} else {
if ($severity) {
echo "<b>$severity</b>: ";
}
echo "$txt<br>";
}
}
function Notice($txt): void
{
Message($txt, 'Notice');
}
function Warning($txt): void
{
Message($txt, 'Warning');
}
function Error(string $txt): void
{
Message($txt, 'Error');
exit;
}
function LoadMap($enc): array
{
$file = __DIR__ . '/' . strtolower($enc) . '.map';
$a = file($file);
if (empty($a)) {
Error('Encoding not found: ' . $enc);
}
$map = array_fill(0, 256, array('uv' => -1, 'name' => '.notdef'));
foreach ($a as $line) {
$e = explode(' ', rtrim($line));
$c = hexdec(substr($e[0], 1));
$uv = hexdec(substr($e[1], 2));
$name = $e[2];
$map[$c] = array('uv' => $uv, 'name' => $name);
}
return $map;
}
function GetInfoFromTrueType($file, $embed, $subset, $map): array
{
// Return information from a TrueType font
try {
$ttf = new TTFParser($file);
$ttf->Parse();
} catch (Exception $e) {
Error($e->getMessage());
}
if ($embed) {
if (!$ttf->embeddable) {
Error('Font license does not allow embedding');
}
if ($subset) {
$chars = array();
foreach ($map as $v) {
if ($v['name'] !== '.notdef') {
$chars[] = $v['uv'];
}
}
$ttf->Subset($chars);
$info['Data'] = $ttf->Build();
} else {
$info['Data'] = file_get_contents($file);
}
$info['OriginalSize'] = strlen($info['Data']);
}
$k = 1000 / $ttf->unitsPerEm;
$info['FontName'] = $ttf->postScriptName;
$info['Bold'] = $ttf->bold;
$info['ItalicAngle'] = $ttf->italicAngle;
$info['IsFixedPitch'] = $ttf->isFixedPitch;
$info['Ascender'] = round($k * $ttf->typoAscender);
$info['Descender'] = round($k * $ttf->typoDescender);
$info['UnderlineThickness'] = round($k * $ttf->underlineThickness);
$info['UnderlinePosition'] = round($k * $ttf->underlinePosition);
$info['FontBBox'] = array(
round($k * $ttf->xMin),
round($k * $ttf->yMin),
round($k * $ttf->xMax),
round($k * $ttf->yMax)
);
$info['CapHeight'] = round($k * $ttf->capHeight);
$info['MissingWidth'] = round($k * $ttf->glyphs[0]['w']);
$widths = array_fill(0, 256, $info['MissingWidth']);
foreach ($map as $c => $v) {
if ($v['name'] !== '.notdef') {
if (isset($ttf->chars[$v['uv']])) {
$id = $ttf->chars[$v['uv']];
$w = $ttf->glyphs[$id]['w'];
$widths[$c] = round($k * $w);
} else {
Warning('Character ' . $v['name'] . ' is missing');
}
}
}
$info['Widths'] = $widths;
return $info;
}
function GetInfoFromType1($file, $embed, $map): array
{
// Return information from a Type1 font
if ($embed) {
$f = fopen($file, 'rb');
if (!$f) {
Error('Can\'t open font file');
}
// Read first segment
$a = unpack('Cmarker/Ctype/Vsize', fread($f, 6));
if ($a['marker'] != 128) {
Error('Font file is not a valid binary Type1');
}
$size1 = $a['size'];
$data = fread($f, $size1);
// Read second segment
$a = unpack('Cmarker/Ctype/Vsize', fread($f, 6));
if ($a['marker'] != 128) {
Error('Font file is not a valid binary Type1');
}
$size2 = $a['size'];
$data .= fread($f, $size2);
fclose($f);
$info['Data'] = $data;
$info['Size1'] = $size1;
$info['Size2'] = $size2;
}
$afm = substr($file, 0, -3) . 'afm';
if (!file_exists($afm)) {
Error('AFM font file not found: ' . $afm);
}
$a = file($afm);
if (empty($a)) {
Error('AFM file empty or not readable');
}
foreach ($a as $line) {
$e = explode(' ', rtrim($line));
if (count($e) < 2) {
continue;
}
$entry = $e[0];
if ($entry === 'C') {
$w = $e[4];
$name = $e[7];
$cw[$name] = $w;
} elseif ($entry === 'FontName') {
$info['FontName'] = $e[1];
} elseif ($entry === 'Weight') {
$info['Weight'] = $e[1];
} elseif ($entry === 'ItalicAngle') {
$info['ItalicAngle'] = (int)$e[1];
} elseif ($entry === 'Ascender') {
$info['Ascender'] = (int)$e[1];
} elseif ($entry === 'Descender') {
$info['Descender'] = (int)$e[1];
} elseif ($entry === 'UnderlineThickness') {
$info['UnderlineThickness'] = (int)$e[1];
} elseif ($entry === 'UnderlinePosition') {
$info['UnderlinePosition'] = (int)$e[1];
} elseif ($entry === 'IsFixedPitch') {
$info['IsFixedPitch'] = ($e[1] == 'true');
} elseif ($entry === 'FontBBox') {
$info['FontBBox'] = array((int)$e[1], (int)$e[2], (int)$e[3], (int)$e[4]);
} elseif ($entry === 'CapHeight') {
$info['CapHeight'] = (int)$e[1];
} elseif ($entry === 'StdVW') {
$info['StdVW'] = (int)$e[1];
}
}
if (!isset($info['FontName'])) {
Error('FontName missing in AFM file');
}
if (!isset($info['Ascender'])) {
$info['Ascender'] = $info['FontBBox'][3];
}
if (!isset($info['Descender'])) {
$info['Descender'] = $info['FontBBox'][1];
}
$info['Bold'] = isset($info['Weight']) && preg_match('/bold|black/i', $info['Weight']);
if (isset($cw['.notdef'])) {
$info['MissingWidth'] = $cw['.notdef'];
} else {
$info['MissingWidth'] = 0;
}
$widths = array_fill(0, 256, $info['MissingWidth']);
foreach ($map as $c => $v) {
if ($v['name'] !== '.notdef') {
if (isset($cw[$v['name']])) {
$widths[$c] = $cw[$v['name']];
} else {
Warning('Character ' . $v['name'] . ' is missing');
}
}
}
$info['Widths'] = $widths;
return $info;
}
function MakeFontDescriptor($info): string
{
// Ascent
$fd = "array('Ascent'=>" . $info['Ascender'];
// Descent
$fd .= ",'Descent'=>" . $info['Descender'];
// CapHeight
if (!empty($info['CapHeight'])) {
$fd .= ",'CapHeight'=>" . $info['CapHeight'];
} else {
$fd .= ",'CapHeight'=>" . $info['Ascender'];
}
// Flags
$flags = 0;
if ($info['IsFixedPitch']) {
$flags += 1 << 0;
}
$flags += 1 << 5;
if ($info['ItalicAngle'] !== 0) {
$flags += 1 << 6;
}
$fd .= ",'Flags'=>" . $flags;
// FontBBox
$fbb = $info['FontBBox'];
$fd .= ",'FontBBox'=>'[" . $fbb[0] . ' ' . $fbb[1] . ' ' . $fbb[2] . ' ' . $fbb[3] . "]'";
// ItalicAngle
$fd .= ",'ItalicAngle'=>" . $info['ItalicAngle'];
// StemV
if (isset($info['StdVW'])) {
$stemv = $info['StdVW'];
} elseif ($info['Bold']) {
$stemv = 120;
} else {
$stemv = 70;
}
$fd .= ",'StemV'=>" . $stemv;
// MissingWidth
$fd .= ",'MissingWidth'=>" . $info['MissingWidth'] . ')';
return $fd;
}
function MakeWidthArray($widths): string
{
$s = "array(\n\t";
for ($c = 0; $c <= 255; $c++) {
if (chr($c) === "'") {
$s .= "'\\''";
} elseif (chr($c) === "\\") {
$s .= "'\\\\'";
} elseif ($c >= 32 && $c <= 126) {
$s .= "'" . chr($c) . "'";
} else {
$s .= "chr($c)";
}
$s .= '=>' . $widths[$c];
if ($c < 255) {
$s .= ',';
}
if (($c + 1) % 22 === 0) {
$s .= "\n\t";
}
}
$s .= ')';
return $s;
}
function MakeFontEncoding($map): string
{
// Build differences from reference encoding
$ref = LoadMap('cp1252');
$s = '';
$last = 0;
for ($c = 32; $c <= 255; $c++) {
if ($map[$c]['name'] !== $ref[$c]['name']) {
if ($c !== $last + 1) {
$s .= $c . ' ';
}
$last = $c;
$s .= '/' . $map[$c]['name'] . ' ';
}
}
return rtrim($s);
}
function MakeUnicodeArray($map): string
{
// Build mapping to Unicode values
$ranges = array();
foreach ($map as $c => $v) {
$uv = $v['uv'];
if ($uv !== -1) {
if (isset($range)) {
if ($c === $range[1] + 1 && $uv === $range[3] + 1) {
$range[1]++;
$range[3]++;
} else {
$ranges[] = $range;
$range = array($c, $c, $uv, $uv);
}
} else {
$range = array($c, $c, $uv, $uv);
}
}
}
$ranges[] = $range;
foreach ($ranges as $range) {
if (isset($s)) {
$s .= ',';
} else {
$s = 'array(';
}
$s .= $range[0] . '=>';
$nb = $range[1] - $range[0] + 1;
if ($nb > 1) {
$s .= 'array(' . $range[2] . ',' . $nb . ')';
} else {
$s .= $range[2];
}
}
$s .= ')';
return $s;
}
function SaveToFile($file, $s, $mode): void
{
$f = fopen($file, 'w' . $mode);
if (!$f) {
Error('Can\'t write to file ' . $file);
}
fwrite($f, $s);
fclose($f);
}
function MakeDefinitionFile($file, $type, $enc, $embed, $subset, $map, $info): void
{
$s = "<?php\n";
$s .= '$type = \'' . $type . "';\n";
$s .= '$name = \'' . $info['FontName'] . "';\n";
$s .= '$desc = ' . MakeFontDescriptor($info) . ";\n";
$s .= '$up = ' . $info['UnderlinePosition'] . ";\n";
$s .= '$ut = ' . $info['UnderlineThickness'] . ";\n";
$s .= '$cw = ' . MakeWidthArray($info['Widths']) . ";\n";
$s .= '$enc = \'' . $enc . "';\n";
$diff = MakeFontEncoding($map);
if ($diff) {
$s .= '$diff = \'' . $diff . "';\n";
}
$s .= '$uv = ' . MakeUnicodeArray($map) . ";\n";
if ($embed) {
$s .= '$file = \'' . $info['File'] . "';\n";
if ($type === 'Type1') {
$s .= '$size1 = ' . $info['Size1'] . ";\n";
$s .= '$size2 = ' . $info['Size2'] . ";\n";
} else {
$s .= '$originalsize = ' . $info['OriginalSize'] . ";\n";
if ($subset) {
$s .= "\$subsetted = true;\n";
}
}
}
$s .= "?>\n";
SaveToFile($file, $s, 't');
}
function MakeFont($fontfile, $enc = 'cp1252', $embed = true, $subset = true): void
{
// Generate a font definition file
if (!file_exists($fontfile)) {
Error('Font file not found: ' . $fontfile);
}
$ext = strtolower(substr($fontfile, -3));
if ($ext === 'ttf' || $ext === 'otf') {
$type = 'TrueType';
} elseif ($ext === 'pfb') {
$type = 'Type1';
} else {
Error('Unrecognized font file extension: ' . $ext);
}
$map = LoadMap($enc);
if ($type === 'TrueType') {
$info = GetInfoFromTrueType($fontfile, $embed, $subset, $map);
} else {
$info = GetInfoFromType1($fontfile, $embed, $map);
}
$basename = substr(basename($fontfile), 0, -4);
if ($embed) {
if (function_exists('gzcompress')) {
$file = $basename . '.z';
SaveToFile($file, gzcompress($info['Data']), 'b');
$info['File'] = $file;
Message('Font file compressed: ' . $file);
} else {
$info['File'] = basename($fontfile);
$subset = false;
Notice('Font file could not be compressed (zlib extension not available)');
}
}
MakeDefinitionFile($basename . '.php', $type, $enc, $embed, $subset, $map, $info);
Message('Font definition file generated: ' . $basename . '.php');
}
if (PHP_SAPI === 'cli') {
// Command-line interface
ini_set('log_errors', '0');
if ($argc === 1) {
die("Usage: php makefont.php fontfile [encoding] [embed] [subset]\n");
}
$fontfile = $argv[1];
if ($argc >= 3) {
$enc = $argv[2];
} else {
$enc = 'cp1252';
}
if ($argc >= 4) {
$embed = ($argv[3] == 'true' || $argv[3] == '1');
} else {
$embed = true;
}
if ($argc >= 5) {
$subset = ($argv[4] == 'true' || $argv[4] == '1');
} else {
$subset = true;
}
MakeFont($fontfile, $enc, $embed, $subset);
}
|