astro-ghostcms/.pnpm-store/v3/files/af/92e7d2dbded50d4cbb3fde0e9de...

350 lines
12 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
// The `glyf` table describes the glyphs in TrueType outline format.
// http://www.microsoft.com/typography/otspec/glyf.htm
import check from '../check';
import glyphset from '../glyphset';
import parse from '../parse';
import Path from '../path';
// Parse the coordinate data for a glyph.
function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) {
let v;
if ((flag & shortVectorBitMask) > 0) {
// The coordinate is 1 byte long.
v = p.parseByte();
// The `same` bit is re-used for short values to signify the sign of the value.
if ((flag & sameBitMask) === 0) {
v = -v;
}
v = previousValue + v;
} else {
// The coordinate is 2 bytes long.
// If the `same` bit is set, the coordinate is the same as the previous coordinate.
if ((flag & sameBitMask) > 0) {
v = previousValue;
} else {
// Parse the coordinate as a signed 16-bit delta value.
v = previousValue + p.parseShort();
}
}
return v;
}
// Parse a TrueType glyph.
function parseGlyph(glyph, data, start) {
const p = new parse.Parser(data, start);
glyph.numberOfContours = p.parseShort();
glyph._xMin = p.parseShort();
glyph._yMin = p.parseShort();
glyph._xMax = p.parseShort();
glyph._yMax = p.parseShort();
let flags;
let flag;
if (glyph.numberOfContours > 0) {
// This glyph is not a composite.
const endPointIndices = glyph.endPointIndices = [];
for (let i = 0; i < glyph.numberOfContours; i += 1) {
endPointIndices.push(p.parseUShort());
}
glyph.instructionLength = p.parseUShort();
glyph.instructions = [];
for (let i = 0; i < glyph.instructionLength; i += 1) {
glyph.instructions.push(p.parseByte());
}
const numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1;
flags = [];
for (let i = 0; i < numberOfCoordinates; i += 1) {
flag = p.parseByte();
flags.push(flag);
// If bit 3 is set, we repeat this flag n times, where n is the next byte.
if ((flag & 8) > 0) {
const repeatCount = p.parseByte();
for (let j = 0; j < repeatCount; j += 1) {
flags.push(flag);
i += 1;
}
}
}
check.argument(flags.length === numberOfCoordinates, 'Bad flags.');
if (endPointIndices.length > 0) {
const points = [];
let point;
// X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0.
if (numberOfCoordinates > 0) {
for (let i = 0; i < numberOfCoordinates; i += 1) {
flag = flags[i];
point = {};
point.onCurve = !!(flag & 1);
point.lastPointOfContour = endPointIndices.indexOf(i) >= 0;
points.push(point);
}
let px = 0;
for (let i = 0; i < numberOfCoordinates; i += 1) {
flag = flags[i];
point = points[i];
point.x = parseGlyphCoordinate(p, flag, px, 2, 16);
px = point.x;
}
let py = 0;
for (let i = 0; i < numberOfCoordinates; i += 1) {
flag = flags[i];
point = points[i];
point.y = parseGlyphCoordinate(p, flag, py, 4, 32);
py = point.y;
}
}
glyph.points = points;
} else {
glyph.points = [];
}
} else if (glyph.numberOfContours === 0) {
glyph.points = [];
} else {
glyph.isComposite = true;
glyph.points = [];
glyph.components = [];
let moreComponents = true;
while (moreComponents) {
flags = p.parseUShort();
const component = {
glyphIndex: p.parseUShort(),
xScale: 1,
scale01: 0,
scale10: 0,
yScale: 1,
dx: 0,
dy: 0
};
if ((flags & 1) > 0) {
// The arguments are words
if ((flags & 2) > 0) {
// values are offset
component.dx = p.parseShort();
component.dy = p.parseShort();
} else {
// values are matched points
component.matchedPoints = [p.parseUShort(), p.parseUShort()];
}
} else {
// The arguments are bytes
if ((flags & 2) > 0) {
// values are offset
component.dx = p.parseChar();
component.dy = p.parseChar();
} else {
// values are matched points
component.matchedPoints = [p.parseByte(), p.parseByte()];
}
}
if ((flags & 8) > 0) {
// We have a scale
component.xScale = component.yScale = p.parseF2Dot14();
} else if ((flags & 64) > 0) {
// We have an X / Y scale
component.xScale = p.parseF2Dot14();
component.yScale = p.parseF2Dot14();
} else if ((flags & 128) > 0) {
// We have a 2x2 transformation
component.xScale = p.parseF2Dot14();
component.scale01 = p.parseF2Dot14();
component.scale10 = p.parseF2Dot14();
component.yScale = p.parseF2Dot14();
}
glyph.components.push(component);
moreComponents = !!(flags & 32);
}
if (flags & 0x100) {
// We have instructions
glyph.instructionLength = p.parseUShort();
glyph.instructions = [];
for (let i = 0; i < glyph.instructionLength; i += 1) {
glyph.instructions.push(p.parseByte());
}
}
}
}
// Transform an array of points and return a new array.
function transformPoints(points, transform) {
const newPoints = [];
for (let i = 0; i < points.length; i += 1) {
const pt = points[i];
const newPt = {
x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx,
y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy,
onCurve: pt.onCurve,
lastPointOfContour: pt.lastPointOfContour
};
newPoints.push(newPt);
}
return newPoints;
}
function getContours(points) {
const contours = [];
let currentContour = [];
for (let i = 0; i < points.length; i += 1) {
const pt = points[i];
currentContour.push(pt);
if (pt.lastPointOfContour) {
contours.push(currentContour);
currentContour = [];
}
}
check.argument(currentContour.length === 0, 'There are still points left in the current contour.');
return contours;
}
// Convert the TrueType glyph outline to a Path.
function getPath(points) {
const p = new Path();
if (!points) {
return p;
}
const contours = getContours(points);
for (let contourIndex = 0; contourIndex < contours.length; ++contourIndex) {
const contour = contours[contourIndex];
let prev = null;
let curr = contour[contour.length - 1];
let next = contour[0];
if (curr.onCurve) {
p.moveTo(curr.x, curr.y);
} else {
if (next.onCurve) {
p.moveTo(next.x, next.y);
} else {
// If both first and last points are off-curve, start at their middle.
const start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5};
p.moveTo(start.x, start.y);
}
}
for (let i = 0; i < contour.length; ++i) {
prev = curr;
curr = next;
next = contour[(i + 1) % contour.length];
if (curr.onCurve) {
// This is a straight line.
p.lineTo(curr.x, curr.y);
} else {
let prev2 = prev;
let next2 = next;
if (!prev.onCurve) {
prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 };
}
if (!next.onCurve) {
next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 };
}
p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y);
}
}
p.closePath();
}
return p;
}
function buildPath(glyphs, glyph) {
if (glyph.isComposite) {
for (let j = 0; j < glyph.components.length; j += 1) {
const component = glyph.components[j];
const componentGlyph = glyphs.get(component.glyphIndex);
// Force the ttfGlyphLoader to parse the glyph.
componentGlyph.getPath();
if (componentGlyph.points) {
let transformedPoints;
if (component.matchedPoints === undefined) {
// component positioned by offset
transformedPoints = transformPoints(componentGlyph.points, component);
} else {
// component positioned by matched points
if ((component.matchedPoints[0] > glyph.points.length - 1) ||
(component.matchedPoints[1] > componentGlyph.points.length - 1)) {
throw Error('Matched points out of range in ' + glyph.name);
}
const firstPt = glyph.points[component.matchedPoints[0]];
let secondPt = componentGlyph.points[component.matchedPoints[1]];
const transform = {
xScale: component.xScale, scale01: component.scale01,
scale10: component.scale10, yScale: component.yScale,
dx: 0, dy: 0
};
secondPt = transformPoints([secondPt], transform)[0];
transform.dx = firstPt.x - secondPt.x;
transform.dy = firstPt.y - secondPt.y;
transformedPoints = transformPoints(componentGlyph.points, transform);
}
glyph.points = glyph.points.concat(transformedPoints);
}
}
}
return getPath(glyph.points);
}
function parseGlyfTableAll(data, start, loca, font) {
const glyphs = new glyphset.GlyphSet(font);
// The last element of the loca table is invalid.
for (let i = 0; i < loca.length - 1; i += 1) {
const offset = loca[i];
const nextOffset = loca[i + 1];
if (offset !== nextOffset) {
glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
} else {
glyphs.push(i, glyphset.glyphLoader(font, i));
}
}
return glyphs;
}
function parseGlyfTableOnLowMemory(data, start, loca, font) {
const glyphs = new glyphset.GlyphSet(font);
font._push = function(i) {
const offset = loca[i];
const nextOffset = loca[i + 1];
if (offset !== nextOffset) {
glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath));
} else {
glyphs.push(i, glyphset.glyphLoader(font, i));
}
};
return glyphs;
}
// Parse all the glyphs according to the offsets from the `loca` table.
function parseGlyfTable(data, start, loca, font, opt) {
if (opt.lowMemory)
return parseGlyfTableOnLowMemory(data, start, loca, font);
else
return parseGlyfTableAll(data, start, loca, font);
}
export default { getPath, parse: parseGlyfTable};