Outline.php
3 years ago
OutlineComponent.php
3 years ago
OutlineComposite.php
3 years ago
OutlineSimple.php
3 years ago
OutlineSimple.php
268 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * @package php-font-lib |
| 5 | * @link https://github.com/PhenX/php-font-lib |
| 6 | * @author Fabien Ménager <fabien.menager@gmail.com> |
| 7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License |
| 8 | * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $ |
| 9 | */ |
| 10 | namespace IAWP\FontLib\Glyph; |
| 11 | |
| 12 | /** |
| 13 | * `glyf` font table. |
| 14 | * |
| 15 | * @package php-font-lib |
| 16 | */ |
| 17 | class OutlineSimple extends Outline |
| 18 | { |
| 19 | const ON_CURVE = 0x1; |
| 20 | const X_SHORT_VECTOR = 0x2; |
| 21 | const Y_SHORT_VECTOR = 0x4; |
| 22 | const REPEAT = 0x8; |
| 23 | const THIS_X_IS_SAME = 0x10; |
| 24 | const THIS_Y_IS_SAME = 0x20; |
| 25 | public $instructions; |
| 26 | public $points; |
| 27 | function parseData() |
| 28 | { |
| 29 | parent::parseData(); |
| 30 | if (!$this->size) { |
| 31 | return; |
| 32 | } |
| 33 | $font = $this->getFont(); |
| 34 | $noc = $this->numberOfContours; |
| 35 | if ($noc == 0) { |
| 36 | return; |
| 37 | } |
| 38 | $endPtsOfContours = $font->r(array(self::uint16, $noc)); |
| 39 | $instructionLength = $font->readUInt16(); |
| 40 | $this->instructions = $font->r(array(self::uint8, $instructionLength)); |
| 41 | $count = $endPtsOfContours[$noc - 1] + 1; |
| 42 | // Flags |
| 43 | $flags = array(); |
| 44 | for ($index = 0; $index < $count; $index++) { |
| 45 | $flags[$index] = $font->readUInt8(); |
| 46 | if ($flags[$index] & self::REPEAT) { |
| 47 | $repeats = $font->readUInt8(); |
| 48 | for ($i = 1; $i <= $repeats; $i++) { |
| 49 | $flags[$index + $i] = $flags[$index]; |
| 50 | } |
| 51 | $index += $repeats; |
| 52 | } |
| 53 | } |
| 54 | $points = array(); |
| 55 | foreach ($flags as $i => $flag) { |
| 56 | $points[$i]["onCurve"] = $flag & self::ON_CURVE; |
| 57 | $points[$i]["endOfContour"] = \in_array($i, $endPtsOfContours); |
| 58 | } |
| 59 | // X Coords |
| 60 | $x = 0; |
| 61 | for ($i = 0; $i < $count; $i++) { |
| 62 | $flag = $flags[$i]; |
| 63 | if ($flag & self::THIS_X_IS_SAME) { |
| 64 | if ($flag & self::X_SHORT_VECTOR) { |
| 65 | $x += $font->readUInt8(); |
| 66 | } |
| 67 | } else { |
| 68 | if ($flag & self::X_SHORT_VECTOR) { |
| 69 | $x -= $font->readUInt8(); |
| 70 | } else { |
| 71 | $x += $font->readInt16(); |
| 72 | } |
| 73 | } |
| 74 | $points[$i]["x"] = $x; |
| 75 | } |
| 76 | // Y Coords |
| 77 | $y = 0; |
| 78 | for ($i = 0; $i < $count; $i++) { |
| 79 | $flag = $flags[$i]; |
| 80 | if ($flag & self::THIS_Y_IS_SAME) { |
| 81 | if ($flag & self::Y_SHORT_VECTOR) { |
| 82 | $y += $font->readUInt8(); |
| 83 | } |
| 84 | } else { |
| 85 | if ($flag & self::Y_SHORT_VECTOR) { |
| 86 | $y -= $font->readUInt8(); |
| 87 | } else { |
| 88 | $y += $font->readInt16(); |
| 89 | } |
| 90 | } |
| 91 | $points[$i]["y"] = $y; |
| 92 | } |
| 93 | $this->points = $points; |
| 94 | } |
| 95 | public function splitSVGPath($path) |
| 96 | { |
| 97 | \preg_match_all('/([a-z])|(-?\\d+(?:\\.\\d+)?)/i', $path, $matches, \PREG_PATTERN_ORDER); |
| 98 | return $matches[0]; |
| 99 | } |
| 100 | public function makePoints($path) |
| 101 | { |
| 102 | $path = $this->splitSVGPath($path); |
| 103 | $l = \count($path); |
| 104 | $i = 0; |
| 105 | $points = array(); |
| 106 | while ($i < $l) { |
| 107 | switch ($path[$i]) { |
| 108 | // moveTo |
| 109 | case "M": |
| 110 | $points[] = array("onCurve" => \true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => \false); |
| 111 | break; |
| 112 | // lineTo |
| 113 | case "L": |
| 114 | $points[] = array("onCurve" => \true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => \false); |
| 115 | break; |
| 116 | // quadraticCurveTo |
| 117 | case "Q": |
| 118 | $points[] = array("onCurve" => \false, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => \false); |
| 119 | $points[] = array("onCurve" => \true, "x" => $path[++$i], "y" => $path[++$i], "endOfContour" => \false); |
| 120 | break; |
| 121 | // closePath |
| 122 | /** @noinspection PhpMissingBreakStatementInspection */ |
| 123 | case "z": |
| 124 | $points[\count($points) - 1]["endOfContour"] = \true; |
| 125 | default: |
| 126 | $i++; |
| 127 | break; |
| 128 | } |
| 129 | } |
| 130 | return $points; |
| 131 | } |
| 132 | function encode() |
| 133 | { |
| 134 | if (empty($this->points)) { |
| 135 | return parent::encode(); |
| 136 | } |
| 137 | return $this->size = $this->encodePoints($this->points); |
| 138 | } |
| 139 | public function encodePoints($points) |
| 140 | { |
| 141 | $endPtsOfContours = array(); |
| 142 | $flags = array(); |
| 143 | $coords_x = array(); |
| 144 | $coords_y = array(); |
| 145 | $last_x = 0; |
| 146 | $last_y = 0; |
| 147 | $xMin = $yMin = 0xffff; |
| 148 | $xMax = $yMax = -0xffff; |
| 149 | foreach ($points as $i => $point) { |
| 150 | $flag = 0; |
| 151 | if ($point["onCurve"]) { |
| 152 | $flag |= self::ON_CURVE; |
| 153 | } |
| 154 | if ($point["endOfContour"]) { |
| 155 | $endPtsOfContours[] = $i; |
| 156 | } |
| 157 | // Simplified, we could do some optimizations |
| 158 | if ($point["x"] == $last_x) { |
| 159 | $flag |= self::THIS_X_IS_SAME; |
| 160 | } else { |
| 161 | $x = \intval($point["x"]); |
| 162 | $xMin = \min($x, $xMin); |
| 163 | $xMax = \max($x, $xMax); |
| 164 | $coords_x[] = $x - $last_x; |
| 165 | // int16 |
| 166 | } |
| 167 | // Simplified, we could do some optimizations |
| 168 | if ($point["y"] == $last_y) { |
| 169 | $flag |= self::THIS_Y_IS_SAME; |
| 170 | } else { |
| 171 | $y = \intval($point["y"]); |
| 172 | $yMin = \min($y, $yMin); |
| 173 | $yMax = \max($y, $yMax); |
| 174 | $coords_y[] = $y - $last_y; |
| 175 | // int16 |
| 176 | } |
| 177 | $flags[] = $flag; |
| 178 | $last_x = $point["x"]; |
| 179 | $last_y = $point["y"]; |
| 180 | } |
| 181 | $font = $this->getFont(); |
| 182 | $l = 0; |
| 183 | $l += $font->writeInt16(\count($endPtsOfContours)); |
| 184 | // endPtsOfContours |
| 185 | $l += $font->writeFWord(isset($this->xMin) ? $this->xMin : $xMin); |
| 186 | // xMin |
| 187 | $l += $font->writeFWord(isset($this->yMin) ? $this->yMin : $yMin); |
| 188 | // yMin |
| 189 | $l += $font->writeFWord(isset($this->xMax) ? $this->xMax : $xMax); |
| 190 | // xMax |
| 191 | $l += $font->writeFWord(isset($this->yMax) ? $this->yMax : $yMax); |
| 192 | // yMax |
| 193 | // Simple glyf |
| 194 | $l += $font->w(array(self::uint16, \count($endPtsOfContours)), $endPtsOfContours); |
| 195 | // endPtsOfContours |
| 196 | $l += $font->writeUInt16(0); |
| 197 | // instructionLength |
| 198 | $l += $font->w(array(self::uint8, \count($flags)), $flags); |
| 199 | // flags |
| 200 | $l += $font->w(array(self::int16, \count($coords_x)), $coords_x); |
| 201 | // xCoordinates |
| 202 | $l += $font->w(array(self::int16, \count($coords_y)), $coords_y); |
| 203 | // yCoordinates |
| 204 | return $l; |
| 205 | } |
| 206 | public function getSVGContours($points = null) |
| 207 | { |
| 208 | $path = ""; |
| 209 | if (!$points) { |
| 210 | if (empty($this->points)) { |
| 211 | $this->parseData(); |
| 212 | } |
| 213 | $points = $this->points; |
| 214 | } |
| 215 | $length = empty($points) ? 0 : \count($points); |
| 216 | $firstIndex = 0; |
| 217 | $count = 0; |
| 218 | for ($i = 0; $i < $length; $i++) { |
| 219 | $count++; |
| 220 | if ($points[$i]["endOfContour"]) { |
| 221 | $path .= $this->getSVGPath($points, $firstIndex, $count); |
| 222 | $firstIndex = $i + 1; |
| 223 | $count = 0; |
| 224 | } |
| 225 | } |
| 226 | return $path; |
| 227 | } |
| 228 | protected function getSVGPath($points, $startIndex, $count) |
| 229 | { |
| 230 | $offset = 0; |
| 231 | $path = ""; |
| 232 | while ($offset < $count) { |
| 233 | $point = $points[$startIndex + $offset % $count]; |
| 234 | $point_p1 = $points[$startIndex + ($offset + 1) % $count]; |
| 235 | if ($offset == 0) { |
| 236 | $path .= "M{$point['x']},{$point['y']} "; |
| 237 | } |
| 238 | if ($point["onCurve"]) { |
| 239 | if ($point_p1["onCurve"]) { |
| 240 | $path .= "L{$point_p1['x']},{$point_p1['y']} "; |
| 241 | $offset++; |
| 242 | } else { |
| 243 | $point_p2 = $points[$startIndex + ($offset + 2) % $count]; |
| 244 | if ($point_p2["onCurve"]) { |
| 245 | $path .= "Q{$point_p1['x']},{$point_p1['y']},{$point_p2['x']},{$point_p2['y']} "; |
| 246 | } else { |
| 247 | $path .= "Q{$point_p1['x']},{$point_p1['y']}," . $this->midValue($point_p1['x'], $point_p2['x']) . "," . $this->midValue($point_p1['y'], $point_p2['y']) . " "; |
| 248 | } |
| 249 | $offset += 2; |
| 250 | } |
| 251 | } else { |
| 252 | if ($point_p1["onCurve"]) { |
| 253 | $path .= "Q{$point['x']},{$point['y']},{$point_p1['x']},{$point_p1['y']} "; |
| 254 | } else { |
| 255 | $path .= "Q{$point['x']},{$point['y']}," . $this->midValue($point['x'], $point_p1['x']) . "," . $this->midValue($point['y'], $point_p1['y']) . " "; |
| 256 | } |
| 257 | $offset++; |
| 258 | } |
| 259 | } |
| 260 | $path .= "z "; |
| 261 | return $path; |
| 262 | } |
| 263 | function midValue($a, $b) |
| 264 | { |
| 265 | return $a + ($b - $a) / 2; |
| 266 | } |
| 267 | } |
| 268 |