static function read_NUMBER(&$fh)
 {
     $b0 = FileRead::read_BYTE($fh);
     // single byte integers
     if (32 <= $b0 && $b0 <= 246) {
         return $b0 - 139;
     }
     // two byte integers
     $b1 = FileRead::read_BYTE($fh);
     if (247 <= $b0 && $b0 <= 250) {
         return 256 * ($b0 - 247) + $b1 + 108;
     }
     if (251 <= $b0 && $b0 <= 259) {
         return -256 * ($b0 - 251) - ($b1 + 108);
     }
     // three byte integers
     $b2 = FileRead::read_BYTE($fh);
     if ($b0 == 28) {
         $v = $b1 << 8 | $b2;
         if ($v > pow(2, 15)) {
             $v -= pow(2, 16);
         }
         return $v;
     }
     // five byte integers
     $b3 = FileRead::read_BYTE($fh);
     $b4 = FileRead::read_BYTE($fh);
     if ($b0 == 29) {
         return $b1 << 24 | $b2 << 16 | $b3 << 8 | $b4;
     } elseif ($b0 == 30) {
         $stop_nibble = 0xf;
         $n1 = -1;
         $n2 = -1;
         $numstring = '';
         while ($n1 != $stop_nibble && $n2 != $stop_nibble) {
             $nibbles = FileRead::read_BYTE($fh);
             //echo dechex($nibbles) . ": {";
             $n1 = $nibbles >> 4;
             //echo "$n1,";
             $n2 = ($nibbles & 0xf) << 4 >> 4;
             //echo "$n2} ";
             // check both nibbles for what they actually mean
             $nibbles = array($n1, $n2);
             foreach ($nibbles as $nibble) {
                 if (0 <= $nibble && $nibble <= 9) {
                     $numstring .= $nibble;
                 } else {
                     switch ($nibble) {
                         case 0xa:
                             $numstring .= ".";
                             break;
                         case 0xb:
                             $numstring .= "E";
                             break;
                         case 0xc:
                             $numstring .= "E-";
                             break;
                             // case(0xd) : { reserved }
                         // case(0xd) : { reserved }
                         case 0xe:
                             $numstring .= "-";
                             break;
                         case 0xf:
                             break;
                     }
                 }
             }
         }
         $real = floatval($numstring);
         //echo "\n returning number conversion of $numstring ($real)\n";
         return $real;
     }
     // fallback.
     echo "error in read_NUMBER, byte pattern ({$b0}) didn't make sense.\n";
     return 0;
 }
 private static function get_TTF_glyph_for_index($font, $char, $index, $matrix = array(0, 0, 1, 0, 0, 1))
 {
     // echo "glyph index for $char: $index\n";
     // step zero: does this char exist?
     if ($index == GlyphFetcher::$NOTSET) {
         $index = $font->get_index($char);
     }
     if ($index === false) {
         return false;
     }
     // there was no cache yet. Perform the real lookup.
     require_once OTTTFONT::$TTFlocation . "ttfglyphdata.php";
     $font->log("This is a TTF font. consulting 'index to location' table.");
     $fh = $font->open();
     $head =& $font->getOTFTableLoader()->get_head_table($font);
     $indexToLocFormat = $head->indexToLocFormat;
     // tells us whether the 'loca' table uses USHORT or ULONG data fields
     // "Index to Location" table, which will tell us where in the "glyf" table we can find the glyph
     $loca =& $font->tables['loca'];
     // "Glyph Data" table, which should give us the glyph's actual outline data (if there is any)
     $glyf =& $font->tables['glyf'];
     // navigate to position $index
     $glyphpointer = 0;
     $next = 0;
     if ($indexToLocFormat == 0) {
         // USHORT entries = seek based on 2 byte jumps (since a USHORT is 16 bit)
         $offset = $loca->offset + $index * 2;
         rewind($fh);
         fseek($fh, $offset);
         // pointer values are stored as half of what they really are in the short table
         $glyphpointer = 2 * FileRead::read_USHORT($fh);
         $next = 2 * FileRead::read_USHORT($fh);
     } elseif ($indexToLocFormat == 1) {
         // ULONG entries = seek based on 4 byte jumps (since a ULONG is 32 bit)
         $step = $index * 4;
         $offset = $loca->offset + $step;
         rewind($fh);
         fseek($fh, $offset);
         // pointer values are stored normally in the long table
         $glyphpointer = FileRead::read_ULONG($fh);
         $next = FileRead::read_ULONG($fh);
     }
     // if the two pointer values are the same, the glyph has no outline
     // (there are a number of invisible characters, mostly spacers).
     $empty = false;
     if ($glyphpointer == $next) {
         $empty = true;
         $font->log("glyph has no outline data in " . $font->fontfilelocation);
     }
     $data = new TTFGlyphData();
     // if there is no outline data, we need to fill in the zero-valued metrics
     if ($empty) {
         $data->xMin = 0;
         $data->yMin = 0;
         $data->xMax = 0;
         $data->yMax = 0;
         $data->height = 0;
         require_once OTTTFont::$GDlocation . "glyphrules.php";
         $data->glyphdata = new Type2GlyphRules();
     } else {
         rewind($fh);
         $offset = $glyf->offset + $glyphpointer;
         $fs = filesize($font->fontfilelocation);
         if ($offset > $fs) {
             echo "ERROR: tried to move the pointer " . ($offset - $fs) . " bytes beyond the end of file!\n";
             return false;
         }
         fseek($fh, $offset);
         // read glyph data (see http://www.microsoft.com/typography/otspec/glyf.htm)
         $data->unitsPerEm = $head->unitsPerEm;
         $numberOfContours = FileRead::read_SHORT($fh);
         // If the number of contours is greater than zero, this is a single glyph;
         // if negative, this is a composite glyph.
         $xMin = FileRead::read_SHORT($fh);
         // Minimum x for coordinate data.
         $yMin = FileRead::read_SHORT($fh);
         // Minimum y for coordinate data.
         $xMax = FileRead::read_SHORT($fh);
         // Maximum x for coordinate data.
         $yMax = FileRead::read_SHORT($fh);
         // Maximum y for coordinate data.
         $width = $xMax - $xMin;
         $height = $yMax - $yMin;
         $data->numberOfContours = $numberOfContours;
         $data->xMin = $xMin;
         $data->yMin = $yMin;
         $data->xMax = $xMax;
         $data->yMax = $yMax;
         $data->width = $width;
         $data->height = $height;
         $font->log("glyph has {$numberOfContours} contours ({$width}x{$height}), x/y min/max: ({$xMin},{$yMin},{$xMax},{$yMax}) in " . $font->fontfilelocation);
         // simple glyph
         if ($numberOfContours >= 0) {
             // first things first: if there is only one contour point, and it's really small, it's probably not actually a glyph
             if ($numberOfContours == 1 && $width < 120 && $height < 120) {
                 return false;
             }
             // read the glyph data
             $endPtsOfContours = array();
             for ($i = 0; $i < $numberOfContours; $i++) {
                 $endPtsOfContours[] = FileRead::read_USHORT($fh);
             }
             $data->endPtsOfContours = $endPtsOfContours;
             // it's much easier to also have access to contour startpoints, rather than just endpoints
             $startPtsOfContours = array(0);
             for ($i = 0; $i < count($endPtsOfContours) - 1; $i++) {
                 $startPtsOfContours[] = $endPtsOfContours[$i] + 1;
             }
             $data->startPtsOfContours = $startPtsOfContours;
             // get instructions
             $instructionLength = FileRead::read_USHORT($fh);
             $data->instructionLength = $instructionLength;
             $instructions = array();
             for ($i = 0; $i < $instructionLength; $i++) {
                 $instructions[] = FileRead::read_BYTE($fh);
             }
             $data->instructions = $instructions;
             // get the coordinate information
             $count = $endPtsOfContours[$numberOfContours - 1] + 1;
             // get all the coordinate flags (code based on Apache's batik java code)
             $data->flags = array();
             for ($flag = 0; $flag < $count; $flag++) {
                 $data->flags[$flag] = FileRead::read_BYTE($fh);
                 if ($data->flag_repeats($flag)) {
                     $repeats = FileRead::read_BYTE($fh);
                     for ($i = 1; $i <= $repeats; $i++) {
                         $data->flags[$flag + $i] = $data->flags[$flag];
                     }
                     $flag += $repeats;
                 }
             }
             // x-coordinates (relative, code based on Apache's batik java code)
             $xCoordinates = array();
             for ($i = 0; $i < $count; $i++) {
                 $x = 0;
                 $xShort = $data->x_is_byte($i);
                 $xDual = $data->x_dual_set($i);
                 // If x-Short Vector is set, xDual describes the sign of the value, with 1 equalling positive and 0 negative.
                 if ($xShort) {
                     if ($xDual) {
                         $x += FileRead::read_BYTE($fh);
                     } else {
                         $x -= FileRead::read_BYTE($fh);
                     }
                 } elseif (!$xDual) {
                     $x += FileRead::read_SHORT($fh);
                 }
                 // correct for offset
                 $xCoordinates[$i] = $x - $xMin;
             }
             // y-coordinates (relative, code based on Apache's batik java code)
             $yCoordinates = array();
             for ($i = 0; $i < $count; $i++) {
                 $y = 0;
                 $yShort = $data->y_is_byte($i);
                 $yDual = $data->y_dual_set($i);
                 // If y-Short Vector is set, yDual describes the sign of the value, with 1 equalling positive and 0 negative.
                 if ($yShort) {
                     if ($yDual) {
                         $y += FileRead::read_BYTE($fh);
                     } else {
                         $y -= FileRead::read_BYTE($fh);
                     }
                 } elseif (!$yDual) {
                     $y += FileRead::read_SHORT($fh);
                 }
                 // correct for offset and flip y coordinate
                 $yCoordinates[$i] = $y - $yMin;
             }
             // bind data and form a glyphrules object
             $data->xCoordinates = $xCoordinates;
             $data->yCoordinates = $yCoordinates;
             $data->formGlyphRules($matrix);
         } else {
             $ARG_1_AND_2_ARE_WORDS = 1;
             // If this is set, the arguments are words; otherwise, they are bytes.
             $ARGS_ARE_XY_VALUES = 2;
             // If this is set, the arguments are xy values; otherwise, they are points.
             $ROUND_XY_TO_GRID = 4;
             // For the xy values if the preceding is true.
             $WE_HAVE_A_SCALE = 8;
             // This indicates that there is a simple scale for the component. Otherwise, scale = 1.0.
             $MORE_COMPONENTS = 32;
             // Indicates at least one more glyph after this one.
             $WE_HAVE_AN_X_AND_Y_SCALE = 64;
             // The x direction will use a different scale from the y direction.
             $WE_HAVE_A_TWO_BY_TWO = 128;
             // There is a 2 by 2 transformation that will be used to scale the component.
             $WE_HAVE_INSTRUCTIONS = 256;
             // Following the last component are instructions for the composite character.
             $USE_MY_METRICS = 512;
             // If set, this forces the aw and lsb (and rsb) for the composite to be equal to those from this original glyph.
             $old_xoffset = 0;
             $old_yoffset = 0;
             $old_xdiff = 0;
             $old_ydiff = 0;
             $last = false;
             $current = false;
             $flags = "";
             do {
                 $flags = FileRead::read_USHORT($fh);
                 $glyphIndex = FileRead::read_USHORT($fh);
                 $arg1 = "";
                 $arg2 = "";
                 // read in argument 1 and 2
                 if (masks($flags, $ARG_1_AND_2_ARE_WORDS)) {
                     $arg1 = FileRead::read_SHORT($fh);
                     $arg2 = FileRead::read_SHORT($fh);
                 } else {
                     $arg1 = FileRead::read_BYTE($fh);
                     $arg2 = FileRead::read_SBYTE($fh);
                 }
                 $xscale = 1;
                 $scale01 = 0;
                 $scale10 = 0;
                 $yscale = 1;
                 if (masks($flags, $WE_HAVE_A_SCALE)) {
                     $xscale = FileRead::read_F2DOT14($fh);
                     $yscale = $xscale;
                 } elseif (masks($flags, $WE_HAVE_AN_X_AND_Y_SCALE)) {
                     $xscale = FileRead::read_F2DOT14($fh);
                     $yscale = FileRead::read_F2DOT14($fh);
                 } elseif (masks($flags, $WE_HAVE_A_TWO_BY_TWO)) {
                     $xscale = FileRead::read_F2DOT14($fh);
                     $scale01 = FileRead::read_F2DOT14($fh);
                     $scale10 = FileRead::read_F2DOT14($fh);
                     $yscale = FileRead::read_F2DOT14($fh);
                 }
                 $xoffset = $arg1;
                 $yoffset = $arg2;
                 // Merge data: if not masked, the arguments indicate how the glyphs link up:
                 // 	- arg1 is the connecting point in the "current" glyph
                 // 	- arg2 is the connecting point in the "next" glyph
                 // We can use these to derive the x/y offset for the linked glyph
                 if (!masks($flags, $ARGS_ARE_XY_VALUES)) {
                     // TODO: this has not yet been implemented
                     trigger_error("point matching not yet implemented\n");
                 }
                 // push the administrative values through.
                 $last = $current;
                 $matrix = array($xoffset, $yoffset, $xscale, $scale01, $scale10, $yscale);
                 $mark = ftell($fh);
                 $current = GlyphFetcher::get_TTF_glyph_for_index($font, $char, $glyphIndex, $matrix);
                 $old_xoffset = $xoffset;
                 $old_yoffset = $yoffset;
                 rewind($fh);
                 fseek($fh, $mark);
                 $data->merge($current);
             } while (masks($flags, $MORE_COMPONENTS));
             // if there are instructions, we read them in but don't do anything with them,
             // because this parser does not process instructions.
             if (masks($flags, $WE_HAVE_INSTRUCTIONS)) {
                 $numInstr = FileRead::read_USHORT($fh);
                 $bytes = array();
                 for ($n = 0; $n < $numInstr; $n++) {
                     $bytes[] = FileRead::read_BYTE($fh);
                 }
             }
         }
     }
     fclose($fh);
     return $data;
 }
 static function read_UTRIPLET(&$filehandle)
 {
     return 0x10000 * FileRead::read_BYTE($filehandle) + FileRead::read_USHORT($filehandle);
 }
 /**
  * Parse OS/2 and Windows table (OS/2)
  */
 function get_os2_table($font)
 {
     if ($this->os2_table != '') {
         return $this->os2_table;
     }
     $os2 =& $font->tables['OS/2'];
     $fh = $font->open();
     fseek($fh, $os2->offset);
     // see http://www.microsoft.com/typography/otspec/os2.htm
     $os2 = new OTTTFontOS2();
     $os2->version = FileRead::read_USHORT($fh);
     $os2->xAvgCharWidth = FileRead::read_SHORT($fh);
     $os2->usWeightClass = FileRead::read_USHORT($fh);
     $os2->usWidthClass = FileRead::read_USHORT($fh);
     $os2->fsType = FileRead::read_USHORT($fh);
     $os2->ySubscriptXSize = FileRead::read_SHORT($fh);
     $os2->ySubscriptYSize = FileRead::read_SHORT($fh);
     $os2->ySubscriptXOffset = FileRead::read_SHORT($fh);
     $os2->ySubscriptYOffset = FileRead::read_SHORT($fh);
     $os2->ySuperscriptXSize = FileRead::read_SHORT($fh);
     $os2->ySuperscriptYSize = FileRead::read_SHORT($fh);
     $os2->ySuperscriptXOffset = FileRead::read_SHORT($fh);
     $os2->ySuperscriptYOffset = FileRead::read_SHORT($fh);
     $os2->yStrikeoutSize = FileRead::read_SHORT($fh);
     $os2->yStrikeoutPosition = FileRead::read_SHORT($fh);
     $os2->sFamilyClass = FileRead::read_SHORT($fh);
     $os2->panose = array();
     for ($i = 0; $i < 10; $i++) {
         $os2->panose[] = FileRead::read_BYTE($fh);
     }
     $os2->ulUnicodeRange1 = FileRead::read_ULONG($fh);
     $os2->ulUnicodeRange2 = FileRead::read_ULONG($fh);
     $os2->ulUnicodeRange3 = FileRead::read_ULONG($fh);
     $os2->ulUnicodeRange4 = FileRead::read_ULONG($fh);
     $os2->achVendID = array();
     // technically char, but the datatype's the same
     for ($i = 0; $i < 4; $i++) {
         $os2->achVendID[] = FileRead::read_BYTE($fh);
     }
     $os2->fsSelection = FileRead::read_USHORT($fh);
     $os2->usFirstCharIndex = FileRead::read_USHORT($fh);
     $os2->usLastCharIndex = FileRead::read_USHORT($fh);
     $os2->sTypoAscender = FileRead::read_SHORT($fh);
     $os2->sTypoDescender = FileRead::read_SHORT($fh);
     $os2->sTypoLineGap = FileRead::read_SHORT($fh);
     $os2->usWinAscent = FileRead::read_USHORT($fh);
     $os2->usWinDescent = FileRead::read_USHORT($fh);
     $os2->ulCodePageRange1 = FileRead::read_ULONG($fh);
     $os2->ulCodePageRange2 = FileRead::read_ULONG($fh);
     $os2->sxHeight = FileRead::read_SHORT($fh);
     $os2->sCapHeight = FileRead::read_SHORT($fh);
     $os2->usDefaultChar = FileRead::read_USHORT($fh);
     $os2->usBreakChar = FileRead::read_USHORT($fh);
     $os2->usMaxContext = FileRead::read_USHORT($fh);
     $this->os2_table = $os2;
     fclose($fh);
     return $this->get_os2_table($font);
 }
 function set_base_values(&$fh)
 {
     $this->byteposition = ftell($fh);
     //echo "table start = " . $this->byteposition . " (".dechex($this->byteposition).")\n";
     $this->count = FileRead::read_USHORT($fh);
     // are there any elements?
     if ($this->count == 0) {
         $this->nexttable = ftell($fh);
     } else {
         //echo "count is " . $this->count . "\n";
         $offSize = FileRead::read_BYTE($fh);
         //echo "offSize = " . $offSize . "\n";
         $this->offSize = $offSize;
         $this->offsets = array();
         $blocksize = ($this->count + 1) * $offSize;
         $block = fread($fh, $blocksize);
         // you could optimise this by using some nice code with a switch statement and compact source, and it would run slower.
         // normally, this would not be a big problem, but when it's about the speed at which individual letters are plucked from a font,
         // 10ms per letter adds up. Very quickly. "Things not to do" include:
         //
         //   - imploding the unpack instead of grabbing [1]
         //   - using a single for(...) loop with a switch(offsize) for the offset reading
         //   - split datablock to byte array first, using preg_split, and then reading (series of) bytes instead of substrings
         //
         $offsets = array();
         $i = 0;
         if ($offSize == 1) {
             for ($i = 0; $i < $blocksize; $i++) {
                 $up = unpack('C', substr($block, $i, 1));
                 $offsets[] = $up[1];
             }
         } elseif ($offSize == 2) {
             for ($i = 0; $i < $blocksize; $i = $i + 2) {
                 $up = unpack('n', substr($block, $i, 2));
                 $offsets[] = $up[1];
             }
         } elseif ($offSize == 3) {
             for ($i = 0; $i < $blocksize; $i = $i + 3) {
                 $byte = unpack('C', substr($block, $i, 1));
                 $short = unpack('n', substr($block, $i + 1, 2));
                 $offsets[] = 0x10000 * $byte[1] + $short[1];
             }
         } elseif ($offSize == 4) {
             for ($i = 0; $i < $blocksize; $i = $i + 4) {
                 $up = unpack('N', substr($block, $i, 4));
                 $offsets[] = $up[1];
             }
         } else {
             echo "ERROR: offSize is unknown size: {$offSize}\n";
             exit(-1);
         }
         $this->offsets = $offsets;
         // the filepointer for the data start is set at 1 byte before the actual data
         //  (see 16 March 2000 version of Adobe CFF spec, pp10, right after Table 7)
         $this->datastart = ftell($fh) - 1;
         // the next table can be found with a calculation mostly derived from the example on page 50 of the CFF specification
         $offsetcount = count($this->offsets);
         $offset_and_data = $this->offSize * $offsetcount + $this->offsets[$offsetcount - 1];
         // = last entry in the offsets array			(after, fp at start of next table)
         $this->nexttable = $this->byteposition + 2 + $offset_and_data;
         //echo "next table starts at " . $this->nexttable . " (".dechex($this->nexttable).")\n";
     }
 }