* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $ */ namespace FontLib\Glyph; /** * `glyf` font table. * * @package php-font-lib */ class OutlineSimple extends Outline { const ON_CURVE = 0x01; const X_SHORT_VECTOR = 0x02; const Y_SHORT_VECTOR = 0x04; const REPEAT = 0x08; const THIS_X_IS_SAME = 0x10; const THIS_Y_IS_SAME = 0x20; public $instructions; public $points; function parseData() { parent::parseData(); if (!$this->size) { return; } $font = $this->getFont(); $noc = $this->numberOfContours; if ($noc == 0) { return; } $endPtsOfContours = $font->r(array(self::uint16, $noc)); $instructionLength = $font->readUInt16(); $this->instructions = $font->r(array(self::uint8, $instructionLength)); $count = $endPtsOfContours[$noc - 1] + 1; // Flags $flags = array(); for ($index = 0; $index < $count; $index++) { $flags[$index] = $font->readUInt8(); if ($flags[$index] & self::REPEAT) { $repeats = $font->readUInt8(); for ($i = 1; $i <= $repeats; $i++) { $flags[$index + $i] = $flags[$index]; } $index += $repeats; } } $points = array(); foreach ($flags as $i => $flag) { $points[$i]["onCurve"] = $flag & self::ON_CURVE; $points[$i]["endOfContour"] = in_array($i, $endPtsOfContours); } // X Coords $x = 0; for ($i = 0; $i < $count; $i++) { $flag = $flags[$i]; if ($flag & self::THIS_X_IS_SAME) { if ($flag & self::X_SHORT_VECTOR) { $x += $font->readUInt8(); } } else { if ($flag & self::X_SHORT_VECTOR) { $x -= $font->readUInt8(); } else { $x += $font->readInt16(); } } $points[$i]["x"] = $x; } // Y Coords $y = 0; for ($i = 0; $i < $count; $i++) { $flag = $flags[$i]; if ($flag & self::THIS_Y_IS_SAME) { if ($flag & self::Y_SHORT_VECTOR) { $y += $font->readUInt8(); } } else { if ($flag & self::Y_SHORT_VECTOR) { $y -= $font->readUInt8(); } else { $y += $font->readInt16(); } } $points[$i]["y"] = $y; } $this->points = $points; } public function splitSVGPath($path) { preg_match_all('/([a-z])|(-?\d+(?:\.\d+)?)/i', $path, $matches, PREG_PATTERN_ORDER); return $matches[0]; } public function makePoints($path) { $path = $this->splitSVGPath($path); $l = count($path); $i = 0; $points = array(); while ($i < $l) { switch ($path[$i]) { // moveTo case "M": $points[] = array( "onCurve" => true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => false, ); break; // lineTo case "L": $points[] = array( "onCurve" => true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => false, ); break; // quadraticCurveTo case "Q": $points[] = array( "onCurve" => false, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => false, ); $points[] = array( "onCurve" => true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => false, ); break; // closePath /** @noinspection PhpMissingBreakStatementInspection */ case "z": $points[count($points) - 1]["endOfContour"] = true; default: $i++; break; } } return $points; } function encode() { if (empty($this->points)) { return parent::encode(); } return $this->size = $this->encodePoints($this->points); } public function encodePoints($points) { $endPtsOfContours = array(); $flags = array(); $coords_x = array(); $coords_y = array(); $last_x = 0; $last_y = 0; $xMin = $yMin = 0xFFFF; $xMax = $yMax = -0xFFFF; foreach ($points as $i => $point) { $flag = 0; if ($point["onCurve"]) { $flag |= self::ON_CURVE; } if ($point["endOfContour"]) { $endPtsOfContours[] = $i; } // Simplified, we could do some optimizations if ($point["x"] == $last_x) { $flag |= self::THIS_X_IS_SAME; } else { $x = intval($point["x"]); $xMin = min($x, $xMin); $xMax = max($x, $xMax); $coords_x[] = $x - $last_x; // int16 } // Simplified, we could do some optimizations if ($point["y"] == $last_y) { $flag |= self::THIS_Y_IS_SAME; } else { $y = intval($point["y"]); $yMin = min($y, $yMin); $yMax = max($y, $yMax); $coords_y[] = $y - $last_y; // int16 } $flags[] = $flag; $last_x = $point["x"]; $last_y = $point["y"]; } $font = $this->getFont(); $l = 0; $l += $font->writeInt16(count($endPtsOfContours)); // endPtsOfContours $l += $font->writeFWord(isset($this->xMin) ? $this->xMin : $xMin); // xMin $l += $font->writeFWord(isset($this->yMin) ? $this->yMin : $yMin); // yMin $l += $font->writeFWord(isset($this->xMax) ? $this->xMax : $xMax); // xMax $l += $font->writeFWord(isset($this->yMax) ? $this->yMax : $yMax); // yMax // Simple glyf $l += $font->w(array(self::uint16, count($endPtsOfContours)), $endPtsOfContours); // endPtsOfContours $l += $font->writeUInt16(0); // instructionLength $l += $font->w(array(self::uint8, count($flags)), $flags); // flags $l += $font->w(array(self::int16, count($coords_x)), $coords_x); // xCoordinates $l += $font->w(array(self::int16, count($coords_y)), $coords_y); // yCoordinates return $l; } public function getSVGContours($points = null) { $path = ""; if (!$points) { if (empty($this->points)) { $this->parseData(); } $points = $this->points; } $length = count($points); $firstIndex = 0; $count = 0; for ($i = 0; $i < $length; $i++) { $count++; if ($points[$i]["endOfContour"]) { $path .= $this->getSVGPath($points, $firstIndex, $count); $firstIndex = $i + 1; $count = 0; } } return $path; } protected function getSVGPath($points, $startIndex, $count) { $offset = 0; $path = ""; while ($offset < $count) { $point = $points[$startIndex + $offset % $count]; $point_p1 = $points[$startIndex + ($offset + 1) % $count]; if ($offset == 0) { $path .= "M{$point['x']},{$point['y']} "; } if ($point["onCurve"]) { if ($point_p1["onCurve"]) { $path .= "L{$point_p1['x']},{$point_p1['y']} "; $offset++; } else { $point_p2 = $points[$startIndex + ($offset + 2) % $count]; if ($point_p2["onCurve"]) { $path .= "Q{$point_p1['x']},{$point_p1['y']},{$point_p2['x']},{$point_p2['y']} "; } else { $path .= "Q{$point_p1['x']},{$point_p1['y']}," . $this->midValue($point_p1['x'], $point_p2['x']) . "," . $this->midValue($point_p1['y'], $point_p2['y']) . " "; } $offset += 2; } } else { if ($point_p1["onCurve"]) { $path .= "Q{$point['x']},{$point['y']},{$point_p1['x']},{$point_p1['y']} "; } else { $path .= "Q{$point['x']},{$point['y']}," . $this->midValue($point['x'], $point_p1['x']) . "," . $this->midValue($point['y'], $point_p1['y']) . " "; } $offset++; } } $path .= "z "; return $path; } function midValue($a, $b) { return $a + ($b - $a) / 2; } }