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"; } }