Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/src/lib/fpdf/makefont/ttfparser.php b/src/lib/fpdf/makefont/ttfparser.php
new file mode 100644
index 0000000..b5acf29
--- /dev/null
+++ b/src/lib/fpdf/makefont/ttfparser.php
@@ -0,0 +1,714 @@
+<?php

+/*******************************************************************************

+* Class to parse and subset TrueType fonts                                     *

+*                                                                              *

+* Version: 1.11                                                                *

+* Date:    2021-04-18                                                          *

+* Author:  Olivier PLATHEY                                                     *

+*******************************************************************************/

+

+class TTFParser

+{

+	protected $f;

+	protected $tables;

+	protected $numberOfHMetrics;

+	protected $numGlyphs;

+	protected $glyphNames;

+	protected $indexToLocFormat;

+	protected $subsettedChars;

+	protected $subsettedGlyphs;

+	public $chars;

+	public $glyphs;

+	public $unitsPerEm;

+	public $xMin, $yMin, $xMax, $yMax;

+	public $postScriptName;

+	public $embeddable;

+	public $bold;

+	public $typoAscender;

+	public $typoDescender;

+	public $capHeight;

+	public $italicAngle;

+	public $underlinePosition;

+	public $underlineThickness;

+	public $isFixedPitch;

+

+	function __construct($file)

+	{

+		$this->f = fopen($file, 'rb');

+		if(!$this->f)

+			$this->Error('Can\'t open file: '.$file);

+	}

+

+	function __destruct()

+	{

+		if(is_resource($this->f))

+			fclose($this->f);

+	}

+

+	function Parse()

+	{

+		$this->ParseOffsetTable();

+		$this->ParseHead();

+		$this->ParseHhea();

+		$this->ParseMaxp();

+		$this->ParseHmtx();

+		$this->ParseLoca();

+		$this->ParseGlyf();

+		$this->ParseCmap();

+		$this->ParseName();

+		$this->ParseOS2();

+		$this->ParsePost();

+	}

+

+	function ParseOffsetTable()

+	{

+		$version = $this->Read(4);

+		if($version=='OTTO')

+			$this->Error('OpenType fonts based on PostScript outlines are not supported');

+		if($version!="\x00\x01\x00\x00")

+			$this->Error('Unrecognized file format');

+		$numTables = $this->ReadUShort();

+		$this->Skip(3*2); // searchRange, entrySelector, rangeShift

+		$this->tables = array();

+		for($i=0;$i<$numTables;$i++)

+		{

+			$tag = $this->Read(4);

+			$checkSum = $this->Read(4);

+			$offset = $this->ReadULong();

+			$length = $this->ReadULong();

+			$this->tables[$tag] = array('offset'=>$offset, 'length'=>$length, 'checkSum'=>$checkSum);

+		}

+	}	

+

+	function ParseHead()

+	{

+		$this->Seek('head');

+		$this->Skip(3*4); // version, fontRevision, checkSumAdjustment

+		$magicNumber = $this->ReadULong();

+		if($magicNumber!=0x5F0F3CF5)

+			$this->Error('Incorrect magic number');

+		$this->Skip(2); // flags

+		$this->unitsPerEm = $this->ReadUShort();

+		$this->Skip(2*8); // created, modified

+		$this->xMin = $this->ReadShort();

+		$this->yMin = $this->ReadShort();

+		$this->xMax = $this->ReadShort();

+		$this->yMax = $this->ReadShort();

+		$this->Skip(3*2); // macStyle, lowestRecPPEM, fontDirectionHint

+		$this->indexToLocFormat = $this->ReadShort();

+	}

+

+	function ParseHhea()

+	{

+		$this->Seek('hhea');

+		$this->Skip(4+15*2);

+		$this->numberOfHMetrics = $this->ReadUShort();

+	}

+

+	function ParseMaxp()

+	{

+		$this->Seek('maxp');

+		$this->Skip(4);

+		$this->numGlyphs = $this->ReadUShort();

+	}

+

+	function ParseHmtx()

+	{

+		$this->Seek('hmtx');

+		$this->glyphs = array();

+		for($i=0;$i<$this->numberOfHMetrics;$i++)

+		{

+			$advanceWidth = $this->ReadUShort();

+			$lsb = $this->ReadShort();

+			$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);

+		}

+		for($i=$this->numberOfHMetrics;$i<$this->numGlyphs;$i++)

+		{

+			$lsb = $this->ReadShort();

+			$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);

+		}

+	}

+

+	function ParseLoca()

+	{

+		$this->Seek('loca');

+		$offsets = array();

+		if($this->indexToLocFormat==0)

+		{

+			// Short format

+			for($i=0;$i<=$this->numGlyphs;$i++)

+				$offsets[] = 2*$this->ReadUShort();

+		}

+		else

+		{

+			// Long format

+			for($i=0;$i<=$this->numGlyphs;$i++)

+				$offsets[] = $this->ReadULong();

+		}

+		for($i=0;$i<$this->numGlyphs;$i++)

+		{

+			$this->glyphs[$i]['offset'] = $offsets[$i];

+			$this->glyphs[$i]['length'] = $offsets[$i+1] - $offsets[$i];

+		}

+	}

+

+	function ParseGlyf()

+	{

+		$tableOffset = $this->tables['glyf']['offset'];

+		foreach($this->glyphs as &$glyph)

+		{

+			if($glyph['length']>0)

+			{

+				fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);

+				if($this->ReadShort()<0)

+				{

+					// Composite glyph

+					$this->Skip(4*2); // xMin, yMin, xMax, yMax

+					$offset = 5*2;

+					$a = array();

+					do

+					{

+						$flags = $this->ReadUShort();

+						$index = $this->ReadUShort();

+						$a[$offset+2] = $index;

+						if($flags & 1) // ARG_1_AND_2_ARE_WORDS

+							$skip = 2*2;

+						else

+							$skip = 2;

+						if($flags & 8) // WE_HAVE_A_SCALE

+							$skip += 2;

+						elseif($flags & 64) // WE_HAVE_AN_X_AND_Y_SCALE

+							$skip += 2*2;

+						elseif($flags & 128) // WE_HAVE_A_TWO_BY_TWO

+							$skip += 4*2;

+						$this->Skip($skip);

+						$offset += 2*2 + $skip;

+					}

+					while($flags & 32); // MORE_COMPONENTS

+					$glyph['components'] = $a;

+				}

+			}

+		}

+	}

+

+	function ParseCmap()

+	{

+		$this->Seek('cmap');

+		$this->Skip(2); // version

+		$numTables = $this->ReadUShort();

+		$offset31 = 0;

+		for($i=0;$i<$numTables;$i++)

+		{

+			$platformID = $this->ReadUShort();

+			$encodingID = $this->ReadUShort();

+			$offset = $this->ReadULong();

+			if($platformID==3 && $encodingID==1)

+				$offset31 = $offset;

+		}

+		if($offset31==0)

+			$this->Error('No Unicode encoding found');

+

+		$startCount = array();

+		$endCount = array();

+		$idDelta = array();

+		$idRangeOffset = array();

+		$this->chars = array();

+		fseek($this->f, $this->tables['cmap']['offset']+$offset31, SEEK_SET);

+		$format = $this->ReadUShort();

+		if($format!=4)

+			$this->Error('Unexpected subtable format: '.$format);

+		$this->Skip(2*2); // length, language

+		$segCount = $this->ReadUShort()/2;

+		$this->Skip(3*2); // searchRange, entrySelector, rangeShift

+		for($i=0;$i<$segCount;$i++)

+			$endCount[$i] = $this->ReadUShort();

+		$this->Skip(2); // reservedPad

+		for($i=0;$i<$segCount;$i++)

+			$startCount[$i] = $this->ReadUShort();

+		for($i=0;$i<$segCount;$i++)

+			$idDelta[$i] = $this->ReadShort();

+		$offset = ftell($this->f);

+		for($i=0;$i<$segCount;$i++)

+			$idRangeOffset[$i] = $this->ReadUShort();

+

+		for($i=0;$i<$segCount;$i++)

+		{

+			$c1 = $startCount[$i];

+			$c2 = $endCount[$i];

+			$d = $idDelta[$i];

+			$ro = $idRangeOffset[$i];

+			if($ro>0)

+				fseek($this->f, $offset+2*$i+$ro, SEEK_SET);

+			for($c=$c1;$c<=$c2;$c++)

+			{

+				if($c==0xFFFF)

+					break;

+				if($ro>0)

+				{

+					$gid = $this->ReadUShort();

+					if($gid>0)

+						$gid += $d;

+				}

+				else

+					$gid = $c+$d;

+				if($gid>=65536)

+					$gid -= 65536;

+				if($gid>0)

+					$this->chars[$c] = $gid;

+			}

+		}

+	}

+

+	function ParseName()

+	{

+		$this->Seek('name');

+		$tableOffset = $this->tables['name']['offset'];

+		$this->postScriptName = '';

+		$this->Skip(2); // format

+		$count = $this->ReadUShort();

+		$stringOffset = $this->ReadUShort();

+		for($i=0;$i<$count;$i++)

+		{

+			$this->Skip(3*2); // platformID, encodingID, languageID

+			$nameID = $this->ReadUShort();

+			$length = $this->ReadUShort();

+			$offset = $this->ReadUShort();

+			if($nameID==6)

+			{

+				// PostScript name

+				fseek($this->f, $tableOffset+$stringOffset+$offset, SEEK_SET);

+				$s = $this->Read($length);

+				$s = str_replace(chr(0), '', $s);

+				$s = preg_replace('|[ \[\](){}<>/%]|', '', $s);

+				$this->postScriptName = $s;

+				break;

+			}

+		}

+		if($this->postScriptName=='')

+			$this->Error('PostScript name not found');

+	}

+

+	function ParseOS2()

+	{

+		$this->Seek('OS/2');

+		$version = $this->ReadUShort();

+		$this->Skip(3*2); // xAvgCharWidth, usWeightClass, usWidthClass

+		$fsType = $this->ReadUShort();

+		$this->embeddable = ($fsType!=2) && ($fsType & 0x200)==0;

+		$this->Skip(11*2+10+4*4+4);

+		$fsSelection = $this->ReadUShort();

+		$this->bold = ($fsSelection & 32)!=0;

+		$this->Skip(2*2); // usFirstCharIndex, usLastCharIndex

+		$this->typoAscender = $this->ReadShort();

+		$this->typoDescender = $this->ReadShort();

+		if($version>=2)

+		{

+			$this->Skip(3*2+2*4+2);

+			$this->capHeight = $this->ReadShort();

+		}

+		else

+			$this->capHeight = 0;

+	}

+

+	function ParsePost()

+	{

+		$this->Seek('post');

+		$version = $this->ReadULong();

+		$this->italicAngle = $this->ReadShort();

+		$this->Skip(2); // Skip decimal part

+		$this->underlinePosition = $this->ReadShort();

+		$this->underlineThickness = $this->ReadShort();

+		$this->isFixedPitch = ($this->ReadULong()!=0);

+		if($version==0x20000)

+		{

+			// Extract glyph names

+			$this->Skip(4*4); // min/max usage

+			$this->Skip(2); // numberOfGlyphs

+			$glyphNameIndex = array();

+			$names = array();

+			$numNames = 0;

+			for($i=0;$i<$this->numGlyphs;$i++)

+			{

+				$index = $this->ReadUShort();

+				$glyphNameIndex[] = $index;

+				if($index>=258 && $index-257>$numNames)

+					$numNames = $index-257;

+			}

+			for($i=0;$i<$numNames;$i++)

+			{

+				$len = ord($this->Read(1));

+				$names[] = $this->Read($len);

+			}

+			foreach($glyphNameIndex as $i=>$index)

+			{

+				if($index>=258)

+					$this->glyphs[$i]['name'] = $names[$index-258];

+				else

+					$this->glyphs[$i]['name'] = $index;

+			}

+			$this->glyphNames = true;

+		}

+		else

+			$this->glyphNames = false;

+	}

+

+	function Subset($chars)

+	{

+		$this->subsettedGlyphs = array();

+		$this->AddGlyph(0);

+		$this->subsettedChars = array();

+		foreach($chars as $char)

+		{

+			if(isset($this->chars[$char]))

+			{

+				$this->subsettedChars[] = $char;

+				$this->AddGlyph($this->chars[$char]);

+			}

+		}

+	}

+

+	function AddGlyph($id)

+	{

+		if(!isset($this->glyphs[$id]['ssid']))

+		{

+			$this->glyphs[$id]['ssid'] = count($this->subsettedGlyphs);

+			$this->subsettedGlyphs[] = $id;

+			if(isset($this->glyphs[$id]['components']))

+			{

+				foreach($this->glyphs[$id]['components'] as $cid)

+					$this->AddGlyph($cid);

+			}

+		}

+	}

+

+	function Build()

+	{

+		$this->BuildCmap();

+		$this->BuildHhea();

+		$this->BuildHmtx();

+		$this->BuildLoca();

+		$this->BuildGlyf();

+		$this->BuildMaxp();

+		$this->BuildPost();

+		return $this->BuildFont();

+	}

+

+	function BuildCmap()

+	{

+		if(!isset($this->subsettedChars))

+			return;

+

+		// Divide charset in contiguous segments

+		$chars = $this->subsettedChars;

+		sort($chars);

+		$segments = array();

+		$segment = array($chars[0], $chars[0]);

+		for($i=1;$i<count($chars);$i++)

+		{

+			if($chars[$i]>$segment[1]+1)

+			{

+				$segments[] = $segment;

+				$segment = array($chars[$i], $chars[$i]);

+			}

+			else

+				$segment[1]++;

+		}

+		$segments[] = $segment;

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

+		$segCount = count($segments);

+

+		// Build a Format 4 subtable

+		$startCount = array();

+		$endCount = array();

+		$idDelta = array();

+		$idRangeOffset = array();

+		$glyphIdArray = '';

+		for($i=0;$i<$segCount;$i++)

+		{

+			list($start, $end) = $segments[$i];

+			$startCount[] = $start;

+			$endCount[] = $end;

+			if($start!=$end)

+			{

+				// Segment with multiple chars

+				$idDelta[] = 0;

+				$idRangeOffset[] = strlen($glyphIdArray) + ($segCount-$i)*2;

+				for($c=$start;$c<=$end;$c++)

+				{

+					$ssid = $this->glyphs[$this->chars[$c]]['ssid'];

+					$glyphIdArray .= pack('n', $ssid);

+				}

+			}

+			else

+			{

+				// Segment with a single char

+				if($start<0xFFFF)

+					$ssid = $this->glyphs[$this->chars[$start]]['ssid'];

+				else

+					$ssid = 0;

+				$idDelta[] = $ssid - $start;

+				$idRangeOffset[] = 0;

+			}

+		}

+		$entrySelector = 0;

+		$n = $segCount;

+		while($n!=1)

+		{

+			$n = $n>>1;

+			$entrySelector++;

+		}

+		$searchRange = (1<<$entrySelector)*2;

+		$rangeShift = 2*$segCount - $searchRange;

+		$cmap = pack('nnnn', 2*$segCount, $searchRange, $entrySelector, $rangeShift);

+		foreach($endCount as $val)

+			$cmap .= pack('n', $val);

+		$cmap .= pack('n', 0); // reservedPad

+		foreach($startCount as $val)

+			$cmap .= pack('n', $val);

+		foreach($idDelta as $val)

+			$cmap .= pack('n', $val);

+		foreach($idRangeOffset as $val)

+			$cmap .= pack('n', $val);

+		$cmap .= $glyphIdArray;

+

+		$data = pack('nn', 0, 1); // version, numTables

+		$data .= pack('nnN', 3, 1, 12); // platformID, encodingID, offset

+		$data .= pack('nnn', 4, 6+strlen($cmap), 0); // format, length, language

+		$data .= $cmap;

+		$this->SetTable('cmap', $data);

+	}

+

+	function BuildHhea()

+	{

+		$this->LoadTable('hhea');

+		$numberOfHMetrics = count($this->subsettedGlyphs);

+		$data = substr_replace($this->tables['hhea']['data'], pack('n',$numberOfHMetrics), 4+15*2, 2);

+		$this->SetTable('hhea', $data);

+	}

+

+	function BuildHmtx()

+	{

+		$data = '';

+		foreach($this->subsettedGlyphs as $id)

+		{

+			$glyph = $this->glyphs[$id];

+			$data .= pack('nn', $glyph['w'], $glyph['lsb']);

+		}

+		$this->SetTable('hmtx', $data);

+	}

+

+	function BuildLoca()

+	{

+		$data = '';

+		$offset = 0;

+		foreach($this->subsettedGlyphs as $id)

+		{

+			if($this->indexToLocFormat==0)

+				$data .= pack('n', $offset/2);

+			else

+				$data .= pack('N', $offset);

+			$offset += $this->glyphs[$id]['length'];

+		}

+		if($this->indexToLocFormat==0)

+			$data .= pack('n', $offset/2);

+		else

+			$data .= pack('N', $offset);

+		$this->SetTable('loca', $data);

+	}

+

+	function BuildGlyf()

+	{

+		$tableOffset = $this->tables['glyf']['offset'];

+		$data = '';

+		foreach($this->subsettedGlyphs as $id)

+		{

+			$glyph = $this->glyphs[$id];

+			fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);

+			$glyph_data = $this->Read($glyph['length']);

+			if(isset($glyph['components']))

+			{

+				// Composite glyph

+				foreach($glyph['components'] as $offset=>$cid)

+				{

+					$ssid = $this->glyphs[$cid]['ssid'];

+					$glyph_data = substr_replace($glyph_data, pack('n',$ssid), $offset, 2);

+				}

+			}

+			$data .= $glyph_data;

+		}

+		$this->SetTable('glyf', $data);

+	}

+

+	function BuildMaxp()

+	{

+		$this->LoadTable('maxp');

+		$numGlyphs = count($this->subsettedGlyphs);

+		$data = substr_replace($this->tables['maxp']['data'], pack('n',$numGlyphs), 4, 2);

+		$this->SetTable('maxp', $data);

+	}

+

+	function BuildPost()

+	{

+		$this->Seek('post');

+		if($this->glyphNames)

+		{

+			// Version 2.0

+			$numberOfGlyphs = count($this->subsettedGlyphs);

+			$numNames = 0;

+			$names = '';

+			$data = $this->Read(2*4+2*2+5*4);

+			$data .= pack('n', $numberOfGlyphs);

+			foreach($this->subsettedGlyphs as $id)

+			{

+				$name = $this->glyphs[$id]['name'];

+				if(is_string($name))

+				{

+					$data .= pack('n', 258+$numNames);

+					$names .= chr(strlen($name)).$name;

+					$numNames++;

+				}

+				else

+					$data .= pack('n', $name);

+			}

+			$data .= $names;

+		}

+		else

+		{

+			// Version 3.0

+			$this->Skip(4);

+			$data = "\x00\x03\x00\x00";

+			$data .= $this->Read(4+2*2+5*4);

+		}

+		$this->SetTable('post', $data);

+	}

+

+	function BuildFont()

+	{

+		$tags = array();

+		foreach(array('cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'prep') as $tag)

+		{

+			if(isset($this->tables[$tag]))

+				$tags[] = $tag;

+		}

+		$numTables = count($tags);

+		$offset = 12 + 16*$numTables;

+		foreach($tags as $tag)

+		{

+			if(!isset($this->tables[$tag]['data']))

+				$this->LoadTable($tag);

+			$this->tables[$tag]['offset'] = $offset;

+			$offset += strlen($this->tables[$tag]['data']);

+		}

+

+		// Build offset table

+		$entrySelector = 0;

+		$n = $numTables;

+		while($n!=1)

+		{

+			$n = $n>>1;

+			$entrySelector++;

+		}

+		$searchRange = 16*(1<<$entrySelector);

+		$rangeShift = 16*$numTables - $searchRange;

+		$offsetTable = pack('nnnnnn', 1, 0, $numTables, $searchRange, $entrySelector, $rangeShift);

+		foreach($tags as $tag)

+		{

+			$table = $this->tables[$tag];

+			$offsetTable .= $tag.$table['checkSum'].pack('NN', $table['offset'], $table['length']);

+		}

+

+		// Compute checkSumAdjustment (0xB1B0AFBA - font checkSum)

+		$s = $this->CheckSum($offsetTable);

+		foreach($tags as $tag)

+			$s .= $this->tables[$tag]['checkSum'];

+		$a = unpack('n2', $this->CheckSum($s));

+		$high = 0xB1B0 + ($a[1]^0xFFFF);

+		$low = 0xAFBA + ($a[2]^0xFFFF) + 1;

+		$checkSumAdjustment = pack('nn', $high+($low>>16), $low);

+		$this->tables['head']['data'] = substr_replace($this->tables['head']['data'], $checkSumAdjustment, 8, 4);

+

+		$font = $offsetTable;

+		foreach($tags as $tag)

+			$font .= $this->tables[$tag]['data'];

+

+		return $font;

+	}

+

+	function LoadTable($tag)

+	{

+		$this->Seek($tag);

+		$length = $this->tables[$tag]['length'];

+		$n = $length % 4;

+		if($n>0)

+			$length += 4 - $n;

+		$this->tables[$tag]['data'] = $this->Read($length);

+	}

+

+	function SetTable($tag, $data)

+	{

+		$length = strlen($data);

+		$n = $length % 4;

+		if($n>0)

+			$data = str_pad($data, $length+4-$n, "\x00");

+		$this->tables[$tag]['data'] = $data;

+		$this->tables[$tag]['length'] = $length;

+		$this->tables[$tag]['checkSum'] = $this->CheckSum($data);

+	}

+

+	function Seek($tag)

+	{

+		if(!isset($this->tables[$tag]))

+			$this->Error('Table not found: '.$tag);

+		fseek($this->f, $this->tables[$tag]['offset'], SEEK_SET);

+	}

+

+	function Skip($n)

+	{

+		fseek($this->f, $n, SEEK_CUR);

+	}

+

+	function Read($n)

+	{

+		return $n>0 ? fread($this->f, $n) : '';

+	}

+

+	function ReadUShort()

+	{

+		$a = unpack('nn', fread($this->f,2));

+		return $a['n'];

+	}

+

+	function ReadShort()

+	{

+		$a = unpack('nn', fread($this->f,2));

+		$v = $a['n'];

+		if($v>=0x8000)

+			$v -= 65536;

+		return $v;

+	}

+

+	function ReadULong()

+	{

+		$a = unpack('NN', fread($this->f,4));

+		return $a['N'];

+	}

+

+	function CheckSum($s)

+	{

+		$n = strlen($s);

+		$high = 0;

+		$low = 0;

+		for($i=0;$i<$n;$i+=4)

+		{

+			$high += (ord($s[$i])<<8) + ord($s[$i+1]);

+			$low += (ord($s[$i+2])<<8) + ord($s[$i+3]);

+		}

+		return pack('nn', $high+($low>>16), $low);

+	}

+

+	function Error($msg)

+	{

+		throw new Exception($msg);

+	}

+}

+?>