Show More
Commit Description:
force log out when password change
Commit Description:
force log out when password change
References:
File last commit:
Show/Diff file:
Action:
node_modules/fontkit/src/glyph/TTFGlyph.js
| 391 lines
| 11.5 KiB
| application/javascript
| JavascriptLexer
|
r789 | import Glyph from './Glyph'; | |||
import Path from './Path'; | ||||
import BBox from './BBox'; | ||||
import r from 'restructure'; | ||||
// The header for both simple and composite glyphs | ||||
let GlyfHeader = new r.Struct({ | ||||
numberOfContours: r.int16, // if negative, this is a composite glyph | ||||
xMin: r.int16, | ||||
yMin: r.int16, | ||||
xMax: r.int16, | ||||
yMax: r.int16 | ||||
}); | ||||
// Flags for simple glyphs | ||||
const ON_CURVE = 1 << 0; | ||||
const X_SHORT_VECTOR = 1 << 1; | ||||
const Y_SHORT_VECTOR = 1 << 2; | ||||
const REPEAT = 1 << 3; | ||||
const SAME_X = 1 << 4; | ||||
const SAME_Y = 1 << 5; | ||||
// Flags for composite glyphs | ||||
const ARG_1_AND_2_ARE_WORDS = 1 << 0; | ||||
const ARGS_ARE_XY_VALUES = 1 << 1; | ||||
const ROUND_XY_TO_GRID = 1 << 2; | ||||
const WE_HAVE_A_SCALE = 1 << 3; | ||||
const MORE_COMPONENTS = 1 << 5; | ||||
const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; | ||||
const WE_HAVE_A_TWO_BY_TWO = 1 << 7; | ||||
const WE_HAVE_INSTRUCTIONS = 1 << 8; | ||||
const USE_MY_METRICS = 1 << 9; | ||||
const OVERLAP_COMPOUND = 1 << 10; | ||||
const SCALED_COMPONENT_OFFSET = 1 << 11; | ||||
const UNSCALED_COMPONENT_OFFSET = 1 << 12; | ||||
// Represents a point in a simple glyph | ||||
export class Point { | ||||
constructor(onCurve, endContour, x = 0, y = 0) { | ||||
this.onCurve = onCurve; | ||||
this.endContour = endContour; | ||||
this.x = x; | ||||
this.y = y; | ||||
} | ||||
copy() { | ||||
return new Point(this.onCurve, this.endContour, this.x, this.y); | ||||
} | ||||
} | ||||
// Represents a component in a composite glyph | ||||
class Component { | ||||
constructor(glyphID, dx, dy) { | ||||
this.glyphID = glyphID; | ||||
this.dx = dx; | ||||
this.dy = dy; | ||||
this.pos = 0; | ||||
this.scaleX = this.scaleY = 1; | ||||
this.scale01 = this.scale10 = 0; | ||||
} | ||||
} | ||||
/** | ||||
* Represents a TrueType glyph. | ||||
*/ | ||||
export default class TTFGlyph extends Glyph { | ||||
// Parses just the glyph header and returns the bounding box | ||||
_getCBox(internal) { | ||||
// We need to decode the glyph if variation processing is requested, | ||||
// so it's easier just to recompute the path's cbox after decoding. | ||||
if (this._font._variationProcessor && !internal) { | ||||
return this.path.cbox; | ||||
} | ||||
let stream = this._font._getTableStream('glyf'); | ||||
stream.pos += this._font.loca.offsets[this.id]; | ||||
let glyph = GlyfHeader.decode(stream); | ||||
let cbox = new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax); | ||||
return Object.freeze(cbox); | ||||
} | ||||
// Parses a single glyph coordinate | ||||
_parseGlyphCoord(stream, prev, short, same) { | ||||
if (short) { | ||||
var val = stream.readUInt8(); | ||||
if (!same) { | ||||
val = -val; | ||||
} | ||||
val += prev; | ||||
} else { | ||||
if (same) { | ||||
var val = prev; | ||||
} else { | ||||
var val = prev + stream.readInt16BE(); | ||||
} | ||||
} | ||||
return val; | ||||
} | ||||
// Decodes the glyph data into points for simple glyphs, | ||||
// or components for composite glyphs | ||||
_decode() { | ||||
let glyfPos = this._font.loca.offsets[this.id]; | ||||
let nextPos = this._font.loca.offsets[this.id + 1]; | ||||
// Nothing to do if there is no data for this glyph | ||||
if (glyfPos === nextPos) { return null; } | ||||
let stream = this._font._getTableStream('glyf'); | ||||
stream.pos += glyfPos; | ||||
let startPos = stream.pos; | ||||
let glyph = GlyfHeader.decode(stream); | ||||
if (glyph.numberOfContours > 0) { | ||||
this._decodeSimple(glyph, stream); | ||||
} else if (glyph.numberOfContours < 0) { | ||||
this._decodeComposite(glyph, stream, startPos); | ||||
} | ||||
return glyph; | ||||
} | ||||
_decodeSimple(glyph, stream) { | ||||
// this is a simple glyph | ||||
glyph.points = []; | ||||
let endPtsOfContours = new r.Array(r.uint16, glyph.numberOfContours).decode(stream); | ||||
glyph.instructions = new r.Array(r.uint8, r.uint16).decode(stream); | ||||
let flags = []; | ||||
let numCoords = endPtsOfContours[endPtsOfContours.length - 1] + 1; | ||||
while (flags.length < numCoords) { | ||||
var flag = stream.readUInt8(); | ||||
flags.push(flag); | ||||
// check for repeat flag | ||||
if (flag & REPEAT) { | ||||
let count = stream.readUInt8(); | ||||
for (let j = 0; j < count; j++) { | ||||
flags.push(flag); | ||||
} | ||||
} | ||||
} | ||||
for (var i = 0; i < flags.length; i++) { | ||||
var flag = flags[i]; | ||||
let point = new Point(!!(flag & ON_CURVE), endPtsOfContours.indexOf(i) >= 0, 0, 0); | ||||
glyph.points.push(point); | ||||
} | ||||
let px = 0; | ||||
for (var i = 0; i < flags.length; i++) { | ||||
var flag = flags[i]; | ||||
glyph.points[i].x = px = this._parseGlyphCoord(stream, px, flag & X_SHORT_VECTOR, flag & SAME_X); | ||||
} | ||||
let py = 0; | ||||
for (var i = 0; i < flags.length; i++) { | ||||
var flag = flags[i]; | ||||
glyph.points[i].y = py = this._parseGlyphCoord(stream, py, flag & Y_SHORT_VECTOR, flag & SAME_Y); | ||||
} | ||||
if (this._font._variationProcessor) { | ||||
let points = glyph.points.slice(); | ||||
points.push(...this._getPhantomPoints(glyph)); | ||||
this._font._variationProcessor.transformPoints(this.id, points); | ||||
glyph.phantomPoints = points.slice(-4); | ||||
} | ||||
return; | ||||
} | ||||
_decodeComposite(glyph, stream, offset = 0) { | ||||
// this is a composite glyph | ||||
glyph.components = []; | ||||
let haveInstructions = false; | ||||
let flags = MORE_COMPONENTS; | ||||
while (flags & MORE_COMPONENTS) { | ||||
flags = stream.readUInt16BE(); | ||||
let gPos = stream.pos - offset; | ||||
let glyphID = stream.readUInt16BE(); | ||||
if (!haveInstructions) { | ||||
haveInstructions = (flags & WE_HAVE_INSTRUCTIONS) !== 0; | ||||
} | ||||
if (flags & ARG_1_AND_2_ARE_WORDS) { | ||||
var dx = stream.readInt16BE(); | ||||
var dy = stream.readInt16BE(); | ||||
} else { | ||||
var dx = stream.readInt8(); | ||||
var dy = stream.readInt8(); | ||||
} | ||||
var component = new Component(glyphID, dx, dy); | ||||
component.pos = gPos; | ||||
if (flags & WE_HAVE_A_SCALE) { | ||||
// fixed number with 14 bits of fraction | ||||
component.scaleX = | ||||
component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
} else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { | ||||
component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
} else if (flags & WE_HAVE_A_TWO_BY_TWO) { | ||||
component.scaleX = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
component.scale01 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
component.scale10 = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
component.scaleY = ((stream.readUInt8() << 24) | (stream.readUInt8() << 16)) / 1073741824; | ||||
} | ||||
glyph.components.push(component); | ||||
} | ||||
if (this._font._variationProcessor) { | ||||
let points = []; | ||||
for (let j = 0; j < glyph.components.length; j++) { | ||||
var component = glyph.components[j]; | ||||
points.push(new Point(true, true, component.dx, component.dy)); | ||||
} | ||||
points.push(...this._getPhantomPoints(glyph)); | ||||
this._font._variationProcessor.transformPoints(this.id, points); | ||||
glyph.phantomPoints = points.splice(-4, 4); | ||||
for (let i = 0; i < points.length; i++) { | ||||
let point = points[i]; | ||||
glyph.components[i].dx = point.x; | ||||
glyph.components[i].dy = point.y; | ||||
} | ||||
} | ||||
return haveInstructions; | ||||
} | ||||
_getPhantomPoints(glyph) { | ||||
let cbox = this._getCBox(true); | ||||
if (this._metrics == null) { | ||||
this._metrics = Glyph.prototype._getMetrics.call(this, cbox); | ||||
} | ||||
let { advanceWidth, advanceHeight, leftBearing, topBearing } = this._metrics; | ||||
return [ | ||||
new Point(false, true, glyph.xMin - leftBearing, 0), | ||||
new Point(false, true, glyph.xMin - leftBearing + advanceWidth, 0), | ||||
new Point(false, true, 0, glyph.yMax + topBearing), | ||||
new Point(false, true, 0, glyph.yMax + topBearing + advanceHeight) | ||||
]; | ||||
} | ||||
// Decodes font data, resolves composite glyphs, and returns an array of contours | ||||
_getContours() { | ||||
let glyph = this._decode(); | ||||
if (!glyph) { | ||||
return []; | ||||
} | ||||
let points = []; | ||||
if (glyph.numberOfContours < 0) { | ||||
// resolve composite glyphs | ||||
for (let component of glyph.components) { | ||||
let contours = this._font.getGlyph(component.glyphID)._getContours(); | ||||
for (let i = 0; i < contours.length; i++) { | ||||
let contour = contours[i]; | ||||
for (let j = 0; j < contour.length; j++) { | ||||
let point = contour[j]; | ||||
let x = point.x * component.scaleX + point.y * component.scale01 + component.dx; | ||||
let y = point.y * component.scaleY + point.x * component.scale10 + component.dy; | ||||
points.push(new Point(point.onCurve, point.endContour, x, y)); | ||||
} | ||||
} | ||||
} | ||||
} else { | ||||
points = glyph.points || []; | ||||
} | ||||
// Recompute and cache metrics if we performed variation processing, and don't have an HVAR table | ||||
if (glyph.phantomPoints && !this._font.directory.tables.HVAR) { | ||||
this._metrics.advanceWidth = glyph.phantomPoints[1].x - glyph.phantomPoints[0].x; | ||||
this._metrics.advanceHeight = glyph.phantomPoints[3].y - glyph.phantomPoints[2].y; | ||||
this._metrics.leftBearing = glyph.xMin - glyph.phantomPoints[0].x; | ||||
this._metrics.topBearing = glyph.phantomPoints[2].y - glyph.yMax; | ||||
} | ||||
let contours = []; | ||||
let cur = []; | ||||
for (let k = 0; k < points.length; k++) { | ||||
var point = points[k]; | ||||
cur.push(point); | ||||
if (point.endContour) { | ||||
contours.push(cur); | ||||
cur = []; | ||||
} | ||||
} | ||||
return contours; | ||||
} | ||||
_getMetrics() { | ||||
if (this._metrics) { | ||||
return this._metrics; | ||||
} | ||||
let cbox = this._getCBox(true); | ||||
super._getMetrics(cbox); | ||||
if (this._font._variationProcessor && !this._font.HVAR) { | ||||
// No HVAR table, decode the glyph. This triggers recomputation of metrics. | ||||
this.path; | ||||
} | ||||
return this._metrics; | ||||
} | ||||
// Converts contours to a Path object that can be rendered | ||||
_getPath() { | ||||
let contours = this._getContours(); | ||||
let path = new Path; | ||||
for (let i = 0; i < contours.length; i++) { | ||||
let contour = contours[i]; | ||||
let firstPt = contour[0]; | ||||
let lastPt = contour[contour.length - 1]; | ||||
let start = 0; | ||||
if (firstPt.onCurve) { | ||||
// The first point will be consumed by the moveTo command, so skip in the loop | ||||
var curvePt = null; | ||||
start = 1; | ||||
} else { | ||||
if (lastPt.onCurve) { | ||||
// Start at the last point if the first point is off curve and the last point is on curve | ||||
firstPt = lastPt; | ||||
} else { | ||||
// Start at the middle if both the first and last points are off curve | ||||
firstPt = new Point(false, false, (firstPt.x + lastPt.x) / 2, (firstPt.y + lastPt.y) / 2); | ||||
} | ||||
var curvePt = firstPt; | ||||
} | ||||
path.moveTo(firstPt.x, firstPt.y); | ||||
for (let j = start; j < contour.length; j++) { | ||||
let pt = contour[j]; | ||||
let prevPt = j === 0 ? firstPt : contour[j - 1]; | ||||
if (prevPt.onCurve && pt.onCurve) { | ||||
path.lineTo(pt.x, pt.y); | ||||
} else if (prevPt.onCurve && !pt.onCurve) { | ||||
var curvePt = pt; | ||||
} else if (!prevPt.onCurve && !pt.onCurve) { | ||||
let midX = (prevPt.x + pt.x) / 2; | ||||
let midY = (prevPt.y + pt.y) / 2; | ||||
path.quadraticCurveTo(prevPt.x, prevPt.y, midX, midY); | ||||
var curvePt = pt; | ||||
} else if (!prevPt.onCurve && pt.onCurve) { | ||||
path.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y); | ||||
var curvePt = null; | ||||
} else { | ||||
throw new Error("Unknown TTF path state"); | ||||
} | ||||
} | ||||
// Connect the first and last points | ||||
if (curvePt) { | ||||
path.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y); | ||||
} | ||||
path.closePath(); | ||||
} | ||||
return path; | ||||
} | ||||
} | ||||