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; }
/** * Get the glyph data for a particular character */ function get_glyph($char) { if (isset($this->glyphcache[$char])) { return $this->glyphcache[$char]; } require_once OTTTFont::$GDlocation . "glyphfetcher.php"; // get index. This will also implicitly cache the glyph data, // so subsequent get_glyph calls will retrieved the cached // data rather than searching the font again. $index = $this->get_index($char); if ($index === false) { return false; } $data = GlyphFetcher::get_glyph($this, $char, $index); $this->cache_glyph($char, $data); return $data; }