Show More
Commit Description:
prevent multiple place login using uuid cookie
Commit Description:
prevent multiple place login using uuid cookie
References:
File last commit:
Show/Diff file:
Action:
node_modules/fontkit/src/glyph/GlyphVariationProcessor.js
| 486 lines
| 14.4 KiB
| application/javascript
| JavascriptLexer
|
r789 | const TUPLES_SHARE_POINT_NUMBERS = 0x8000; | |||
const TUPLE_COUNT_MASK = 0x0fff; | ||||
const EMBEDDED_TUPLE_COORD = 0x8000; | ||||
const INTERMEDIATE_TUPLE = 0x4000; | ||||
const PRIVATE_POINT_NUMBERS = 0x2000; | ||||
const TUPLE_INDEX_MASK = 0x0fff; | ||||
const POINTS_ARE_WORDS = 0x80; | ||||
const POINT_RUN_COUNT_MASK = 0x7f; | ||||
const DELTAS_ARE_ZERO = 0x80; | ||||
const DELTAS_ARE_WORDS = 0x40; | ||||
const DELTA_RUN_COUNT_MASK = 0x3f; | ||||
/** | ||||
* This class is transforms TrueType glyphs according to the data from | ||||
* the Apple Advanced Typography variation tables (fvar, gvar, and avar). | ||||
* These tables allow infinite adjustments to glyph weight, width, slant, | ||||
* and optical size without the designer needing to specify every exact style. | ||||
* | ||||
* Apple's documentation for these tables is not great, so thanks to the | ||||
* Freetype project for figuring much of this out. | ||||
* | ||||
* @private | ||||
*/ | ||||
export default class GlyphVariationProcessor { | ||||
constructor(font, coords) { | ||||
this.font = font; | ||||
this.normalizedCoords = this.normalizeCoords(coords); | ||||
this.blendVectors = new Map; | ||||
} | ||||
normalizeCoords(coords) { | ||||
// the default mapping is linear along each axis, in two segments: | ||||
// from the minValue to defaultValue, and from defaultValue to maxValue. | ||||
let normalized = []; | ||||
for (var i = 0; i < this.font.fvar.axis.length; i++) { | ||||
let axis = this.font.fvar.axis[i]; | ||||
if (coords[i] < axis.defaultValue) { | ||||
normalized.push((coords[i] - axis.defaultValue + Number.EPSILON) / (axis.defaultValue - axis.minValue + Number.EPSILON)); | ||||
} else { | ||||
normalized.push((coords[i] - axis.defaultValue + Number.EPSILON) / (axis.maxValue - axis.defaultValue + Number.EPSILON)); | ||||
} | ||||
} | ||||
// if there is an avar table, the normalized value is calculated | ||||
// by interpolating between the two nearest mapped values. | ||||
if (this.font.avar) { | ||||
for (var i = 0; i < this.font.avar.segment.length; i++) { | ||||
let segment = this.font.avar.segment[i]; | ||||
for (let j = 0; j < segment.correspondence.length; j++) { | ||||
let pair = segment.correspondence[j]; | ||||
if (j >= 1 && normalized[i] < pair.fromCoord) { | ||||
let prev = segment.correspondence[j - 1]; | ||||
normalized[i] = ((normalized[i] - prev.fromCoord) * (pair.toCoord - prev.toCoord) + Number.EPSILON) / | ||||
(pair.fromCoord - prev.fromCoord + Number.EPSILON) + | ||||
prev.toCoord; | ||||
break; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
return normalized; | ||||
} | ||||
transformPoints(gid, glyphPoints) { | ||||
if (!this.font.fvar || !this.font.gvar) { return; } | ||||
let { gvar } = this.font; | ||||
if (gid >= gvar.glyphCount) { return; } | ||||
let offset = gvar.offsets[gid]; | ||||
if (offset === gvar.offsets[gid + 1]) { return; } | ||||
// Read the gvar data for this glyph | ||||
let { stream } = this.font; | ||||
stream.pos = offset; | ||||
if (stream.pos >= stream.length) { | ||||
return; | ||||
} | ||||
let tupleCount = stream.readUInt16BE(); | ||||
let offsetToData = offset + stream.readUInt16BE(); | ||||
if (tupleCount & TUPLES_SHARE_POINT_NUMBERS) { | ||||
var here = stream.pos; | ||||
stream.pos = offsetToData; | ||||
var sharedPoints = this.decodePoints(); | ||||
offsetToData = stream.pos; | ||||
stream.pos = here; | ||||
} | ||||
let origPoints = glyphPoints.map(pt => pt.copy()); | ||||
tupleCount &= TUPLE_COUNT_MASK; | ||||
for (let i = 0; i < tupleCount; i++) { | ||||
let tupleDataSize = stream.readUInt16BE(); | ||||
let tupleIndex = stream.readUInt16BE(); | ||||
if (tupleIndex & EMBEDDED_TUPLE_COORD) { | ||||
var tupleCoords = []; | ||||
for (let a = 0; a < gvar.axisCount; a++) { | ||||
tupleCoords.push(stream.readInt16BE() / 16384); | ||||
} | ||||
} else { | ||||
if ((tupleIndex & TUPLE_INDEX_MASK) >= gvar.globalCoordCount) { | ||||
throw new Error('Invalid gvar table'); | ||||
} | ||||
var tupleCoords = gvar.globalCoords[tupleIndex & TUPLE_INDEX_MASK]; | ||||
} | ||||
if (tupleIndex & INTERMEDIATE_TUPLE) { | ||||
var startCoords = []; | ||||
for (let a = 0; a < gvar.axisCount; a++) { | ||||
startCoords.push(stream.readInt16BE() / 16384); | ||||
} | ||||
var endCoords = []; | ||||
for (let a = 0; a < gvar.axisCount; a++) { | ||||
endCoords.push(stream.readInt16BE() / 16384); | ||||
} | ||||
} | ||||
// Get the factor at which to apply this tuple | ||||
let factor = this.tupleFactor(tupleIndex, tupleCoords, startCoords, endCoords); | ||||
if (factor === 0) { | ||||
offsetToData += tupleDataSize; | ||||
continue; | ||||
} | ||||
var here = stream.pos; | ||||
stream.pos = offsetToData; | ||||
if (tupleIndex & PRIVATE_POINT_NUMBERS) { | ||||
var points = this.decodePoints(); | ||||
} else { | ||||
var points = sharedPoints; | ||||
} | ||||
// points.length = 0 means there are deltas for all points | ||||
let nPoints = points.length === 0 ? glyphPoints.length : points.length; | ||||
let xDeltas = this.decodeDeltas(nPoints); | ||||
let yDeltas = this.decodeDeltas(nPoints); | ||||
if (points.length === 0) { // all points | ||||
for (let i = 0; i < glyphPoints.length; i++) { | ||||
var point = glyphPoints[i]; | ||||
point.x += Math.round(xDeltas[i] * factor); | ||||
point.y += Math.round(yDeltas[i] * factor); | ||||
} | ||||
} else { | ||||
let outPoints = origPoints.map(pt => pt.copy()); | ||||
let hasDelta = glyphPoints.map(() => false); | ||||
for (let i = 0; i < points.length; i++) { | ||||
let idx = points[i]; | ||||
if (idx < glyphPoints.length) { | ||||
let point = outPoints[idx]; | ||||
hasDelta[idx] = true; | ||||
point.x += Math.round(xDeltas[i] * factor); | ||||
point.y += Math.round(yDeltas[i] * factor); | ||||
} | ||||
} | ||||
this.interpolateMissingDeltas(outPoints, origPoints, hasDelta); | ||||
for (let i = 0; i < glyphPoints.length; i++) { | ||||
let deltaX = outPoints[i].x - origPoints[i].x; | ||||
let deltaY = outPoints[i].y - origPoints[i].y; | ||||
glyphPoints[i].x += deltaX; | ||||
glyphPoints[i].y += deltaY; | ||||
} | ||||
} | ||||
offsetToData += tupleDataSize; | ||||
stream.pos = here; | ||||
} | ||||
} | ||||
decodePoints() { | ||||
let stream = this.font.stream; | ||||
let count = stream.readUInt8(); | ||||
if (count & POINTS_ARE_WORDS) { | ||||
count = (count & POINT_RUN_COUNT_MASK) << 8 | stream.readUInt8(); | ||||
} | ||||
let points = new Uint16Array(count); | ||||
let i = 0; | ||||
let point = 0; | ||||
while (i < count) { | ||||
let run = stream.readUInt8(); | ||||
let runCount = (run & POINT_RUN_COUNT_MASK) + 1; | ||||
let fn = run & POINTS_ARE_WORDS ? stream.readUInt16 : stream.readUInt8; | ||||
for (let j = 0; j < runCount && i < count; j++) { | ||||
point += fn.call(stream); | ||||
points[i++] = point; | ||||
} | ||||
} | ||||
return points; | ||||
} | ||||
decodeDeltas(count) { | ||||
let stream = this.font.stream; | ||||
let i = 0; | ||||
let deltas = new Int16Array(count); | ||||
while (i < count) { | ||||
let run = stream.readUInt8(); | ||||
let runCount = (run & DELTA_RUN_COUNT_MASK) + 1; | ||||
if (run & DELTAS_ARE_ZERO) { | ||||
i += runCount; | ||||
} else { | ||||
let fn = run & DELTAS_ARE_WORDS ? stream.readInt16BE : stream.readInt8; | ||||
for (let j = 0; j < runCount && i < count; j++) { | ||||
deltas[i++] = fn.call(stream); | ||||
} | ||||
} | ||||
} | ||||
return deltas; | ||||
} | ||||
tupleFactor(tupleIndex, tupleCoords, startCoords, endCoords) { | ||||
let normalized = this.normalizedCoords; | ||||
let { gvar } = this.font; | ||||
let factor = 1; | ||||
for (let i = 0; i < gvar.axisCount; i++) { | ||||
if (tupleCoords[i] === 0) { | ||||
continue; | ||||
} | ||||
if (normalized[i] === 0) { | ||||
return 0; | ||||
} | ||||
if ((tupleIndex & INTERMEDIATE_TUPLE) === 0) { | ||||
if ((normalized[i] < Math.min(0, tupleCoords[i])) || | ||||
(normalized[i] > Math.max(0, tupleCoords[i]))) { | ||||
return 0; | ||||
} | ||||
factor = (factor * normalized[i] + Number.EPSILON) / (tupleCoords[i] + Number.EPSILON); | ||||
} else { | ||||
if ((normalized[i] < startCoords[i]) || | ||||
(normalized[i] > endCoords[i])) { | ||||
return 0; | ||||
} else if (normalized[i] < tupleCoords[i]) { | ||||
factor = factor * (normalized[i] - startCoords[i] + Number.EPSILON) / (tupleCoords[i] - startCoords[i] + Number.EPSILON); | ||||
} else { | ||||
factor = factor * (endCoords[i] - normalized[i] + Number.EPSILON) / (endCoords[i] - tupleCoords[i] + Number.EPSILON); | ||||
} | ||||
} | ||||
} | ||||
return factor; | ||||
} | ||||
// Interpolates points without delta values. | ||||
// Needed for the Ø and Q glyphs in Skia. | ||||
// Algorithm from Freetype. | ||||
interpolateMissingDeltas(points, inPoints, hasDelta) { | ||||
if (points.length === 0) { | ||||
return; | ||||
} | ||||
let point = 0; | ||||
while (point < points.length) { | ||||
let firstPoint = point; | ||||
// find the end point of the contour | ||||
let endPoint = point; | ||||
let pt = points[endPoint]; | ||||
while (!pt.endContour) { | ||||
pt = points[++endPoint]; | ||||
} | ||||
// find the first point that has a delta | ||||
while (point <= endPoint && !hasDelta[point]) { | ||||
point++; | ||||
} | ||||
if (point > endPoint) { | ||||
continue; | ||||
} | ||||
let firstDelta = point; | ||||
let curDelta = point; | ||||
point++; | ||||
while (point <= endPoint) { | ||||
// find the next point with a delta, and interpolate intermediate points | ||||
if (hasDelta[point]) { | ||||
this.deltaInterpolate(curDelta + 1, point - 1, curDelta, point, inPoints, points); | ||||
curDelta = point; | ||||
} | ||||
point++; | ||||
} | ||||
// shift contour if we only have a single delta | ||||
if (curDelta === firstDelta) { | ||||
this.deltaShift(firstPoint, endPoint, curDelta, inPoints, points); | ||||
} else { | ||||
// otherwise, handle the remaining points at the end and beginning of the contour | ||||
this.deltaInterpolate(curDelta + 1, endPoint, curDelta, firstDelta, inPoints, points); | ||||
if (firstDelta > 0) { | ||||
this.deltaInterpolate(firstPoint, firstDelta - 1, curDelta, firstDelta, inPoints, points); | ||||
} | ||||
} | ||||
point = endPoint + 1; | ||||
} | ||||
} | ||||
deltaInterpolate(p1, p2, ref1, ref2, inPoints, outPoints) { | ||||
if (p1 > p2) { | ||||
return; | ||||
} | ||||
let iterable = ['x', 'y']; | ||||
for (let i = 0; i < iterable.length; i++) { | ||||
let k = iterable[i]; | ||||
if (inPoints[ref1][k] > inPoints[ref2][k]) { | ||||
var p = ref1; | ||||
ref1 = ref2; | ||||
ref2 = p; | ||||
} | ||||
let in1 = inPoints[ref1][k]; | ||||
let in2 = inPoints[ref2][k]; | ||||
let out1 = outPoints[ref1][k]; | ||||
let out2 = outPoints[ref2][k]; | ||||
// If the reference points have the same coordinate but different | ||||
// delta, inferred delta is zero. Otherwise interpolate. | ||||
if (in1 !== in2 || out1 === out2) { | ||||
let scale = in1 === in2 ? 0 : (out2 - out1) / (in2 - in1); | ||||
for (let p = p1; p <= p2; p++) { | ||||
let out = inPoints[p][k]; | ||||
if (out <= in1) { | ||||
out += out1 - in1; | ||||
} else if (out >= in2) { | ||||
out += out2 - in2; | ||||
} else { | ||||
out = out1 + (out - in1) * scale; | ||||
} | ||||
outPoints[p][k] = out; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
deltaShift(p1, p2, ref, inPoints, outPoints) { | ||||
let deltaX = outPoints[ref].x - inPoints[ref].x; | ||||
let deltaY = outPoints[ref].y - inPoints[ref].y; | ||||
if (deltaX === 0 && deltaY === 0) { | ||||
return; | ||||
} | ||||
for (let p = p1; p <= p2; p++) { | ||||
if (p !== ref) { | ||||
outPoints[p].x += deltaX; | ||||
outPoints[p].y += deltaY; | ||||
} | ||||
} | ||||
} | ||||
getAdvanceAdjustment(gid, table) { | ||||
let outerIndex, innerIndex; | ||||
if (table.advanceWidthMapping) { | ||||
let idx = gid; | ||||
if (idx >= table.advanceWidthMapping.mapCount) { | ||||
idx = table.advanceWidthMapping.mapCount - 1; | ||||
} | ||||
let entryFormat = table.advanceWidthMapping.entryFormat; | ||||
({outerIndex, innerIndex} = table.advanceWidthMapping.mapData[idx]); | ||||
} else { | ||||
outerIndex = 0; | ||||
innerIndex = gid; | ||||
} | ||||
return this.getDelta(table.itemVariationStore, outerIndex, innerIndex); | ||||
} | ||||
// See pseudo code from `Font Variations Overview' | ||||
// in the OpenType specification. | ||||
getDelta(itemStore, outerIndex, innerIndex) { | ||||
if (outerIndex >= itemStore.itemVariationData.length) { | ||||
return 0; | ||||
} | ||||
let varData = itemStore.itemVariationData[outerIndex]; | ||||
if (innerIndex >= varData.deltaSets.length) { | ||||
return 0; | ||||
} | ||||
let deltaSet = varData.deltaSets[innerIndex]; | ||||
let blendVector = this.getBlendVector(itemStore, outerIndex); | ||||
let netAdjustment = 0; | ||||
for (let master = 0; master < varData.regionIndexCount; master++) { | ||||
netAdjustment += deltaSet.deltas[master] * blendVector[master]; | ||||
} | ||||
return netAdjustment; | ||||
} | ||||
getBlendVector(itemStore, outerIndex) { | ||||
let varData = itemStore.itemVariationData[outerIndex]; | ||||
if (this.blendVectors.has(varData)) { | ||||
return this.blendVectors.get(varData); | ||||
} | ||||
let normalizedCoords = this.normalizedCoords; | ||||
let blendVector = []; | ||||
// outer loop steps through master designs to be blended | ||||
for (let master = 0; master < varData.regionIndexCount; master++) { | ||||
let scalar = 1; | ||||
let regionIndex = varData.regionIndexes[master]; | ||||
let axes = itemStore.variationRegionList.variationRegions[regionIndex]; | ||||
// inner loop steps through axes in this region | ||||
for (let j = 0; j < axes.length; j++) { | ||||
let axis = axes[j]; | ||||
let axisScalar; | ||||
// compute the scalar contribution of this axis | ||||
// ignore invalid ranges | ||||
if (axis.startCoord > axis.peakCoord || axis.peakCoord > axis.endCoord) { | ||||
axisScalar = 1; | ||||
} else if (axis.startCoord < 0 && axis.endCoord > 0 && axis.peakCoord !== 0) { | ||||
axisScalar = 1; | ||||
// peak of 0 means ignore this axis | ||||
} else if (axis.peakCoord === 0) { | ||||
axisScalar = 1; | ||||
// ignore this region if coords are out of range | ||||
} else if (normalizedCoords[j] < axis.startCoord || normalizedCoords[j] > axis.endCoord) { | ||||
axisScalar = 0; | ||||
// calculate a proportional factor | ||||
} else { | ||||
if (normalizedCoords[j] === axis.peakCoord) { | ||||
axisScalar = 1; | ||||
} else if (normalizedCoords[j] < axis.peakCoord) { | ||||
axisScalar = (normalizedCoords[j] - axis.startCoord + Number.EPSILON) / | ||||
(axis.peakCoord - axis.startCoord + Number.EPSILON); | ||||
} else { | ||||
axisScalar = (axis.endCoord - normalizedCoords[j] + Number.EPSILON) / | ||||
(axis.endCoord - axis.peakCoord + Number.EPSILON); | ||||
} | ||||
} | ||||
// take product of all the axis scalars | ||||
scalar *= axisScalar; | ||||
} | ||||
blendVector[master] = scalar; | ||||
} | ||||
this.blendVectors.set(varData, blendVector); | ||||
return blendVector; | ||||
} | ||||
} | ||||