173 lines
4.5 KiB
JavaScript
173 lines
4.5 KiB
JavaScript
"use strict";
|
|
|
|
const autoRef = require("./autoref");
|
|
const TypoGeom = require("typo-geom");
|
|
const Point = require("../../support/point");
|
|
|
|
const CurveUtil = require("../../support/curve-util");
|
|
const { AnyCv } = require("../../support/gr");
|
|
const gcFont = require("./gc");
|
|
|
|
module.exports = function finalizeFont(para, glyphList, excludedCodePoints, font) {
|
|
forceMonospaceIfNeeded(para, glyphList);
|
|
gcFont(glyphList, excludedCodePoints, font, {});
|
|
extractGlyfCmap(regulateGlyphList(para, glyphList), font);
|
|
};
|
|
|
|
function forceMonospaceIfNeeded(para, glyphList) {
|
|
if (!para.forceMonospace || para.spacing > 0) return;
|
|
const unitWidth = Math.round(para.width);
|
|
let i = 0,
|
|
j = 0;
|
|
for (; i < glyphList.length; i++) {
|
|
const g = glyphList[i];
|
|
g.advanceWidth = Math.round(g.advanceWidth || 0);
|
|
if (g.advanceWidth === 0 || g.advanceWidth === unitWidth) glyphList[j++] = g;
|
|
}
|
|
glyphList.length = j;
|
|
}
|
|
|
|
function extractGlyfCmap(glyphList, font) {
|
|
const glyf = {};
|
|
const cmap = {};
|
|
for (let g of glyphList) {
|
|
glyf[g.name] = g;
|
|
if (!g.unicode) continue;
|
|
|
|
for (let u of g.unicode) {
|
|
if (isFinite(u - 0)) cmap[u] = g.name;
|
|
}
|
|
}
|
|
font.glyf = glyf;
|
|
font.cmap = cmap;
|
|
}
|
|
|
|
function regulateGlyphList(para, gs) {
|
|
const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI);
|
|
|
|
const excludeUnicode = new Set();
|
|
excludeUnicode.add(0x80);
|
|
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
|
|
|
|
// autoref
|
|
for (let j = 0; j < gs.length; j++) {
|
|
gs[j].glyphOrder = j;
|
|
if (AnyCv.query(gs[j]).length) gs[j].autoRefPriority = -1;
|
|
if (gs[j].unicode) {
|
|
for (const u of gs[j].unicode) {
|
|
if (excludeUnicode.has(u)) gs[j].avoidBeingComposite = true;
|
|
}
|
|
}
|
|
}
|
|
gs.sort(byGlyphPriority);
|
|
autoRef(gs, excludeUnicode);
|
|
|
|
// regulate
|
|
for (let g of gs) regulateGlyph(g, skew);
|
|
|
|
// reorder
|
|
return gs.sort(byRank);
|
|
}
|
|
|
|
function byGlyphPriority(a, b) {
|
|
const pri1 = a.autoRefPriority || 0;
|
|
const pri2 = b.autoRefPriority || 0;
|
|
if (pri1 > pri2) return -1;
|
|
if (pri1 < pri2) return 1;
|
|
if (a.contours && b.contours && a.contours.length < b.contours.length) return 1;
|
|
if (a.contours && b.contours && a.contours.length > b.contours.length) return -1;
|
|
return 0;
|
|
}
|
|
|
|
function byRank(a, b) {
|
|
return (b.glyphRank || 0) - (a.glyphRank || 0) || (a.glyphOrder || 0) - (b.glyphOrder || 0);
|
|
}
|
|
|
|
function regulateGlyph(g, skew) {
|
|
if (!g.contours || !g.contours.length) return;
|
|
for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew;
|
|
g.contours = simplifyContours(g.contours);
|
|
for (const contour of g.contours) for (const z of contour) z.x += z.y * skew;
|
|
}
|
|
|
|
function simplifyContours(source) {
|
|
const sink = new FairizedShapeSink();
|
|
|
|
TypoGeom.transferGenericShape(
|
|
TypoGeom.Fairize.fairizeBezierShape(
|
|
TypoGeom.Boolean.removeOverlap(
|
|
CurveUtil.convertShapeToArcs(source),
|
|
TypoGeom.Boolean.PolyFillType.pftNonZero,
|
|
CurveUtil.BOOLE_RESOLUTION
|
|
)
|
|
),
|
|
sink,
|
|
CurveUtil.GEOMETRY_PRECISION
|
|
);
|
|
|
|
return sink.contours;
|
|
}
|
|
|
|
class FairizedShapeSink {
|
|
constructor() {
|
|
this.contours = [];
|
|
this.lastContour = [];
|
|
}
|
|
beginShape() {}
|
|
endShape() {
|
|
if (this.lastContour.length > 2) {
|
|
const zFirst = this.lastContour[0],
|
|
zLast = this.lastContour[this.lastContour.length - 1];
|
|
if (zFirst.on && zLast.on && zFirst.x === zLast.x && zFirst.y === zLast.y) {
|
|
this.lastContour.pop();
|
|
}
|
|
this.contours.push(this.lastContour);
|
|
}
|
|
this.lastContour = [];
|
|
}
|
|
moveTo(x, y) {
|
|
this.endShape();
|
|
this.lineTo(x, y);
|
|
}
|
|
lineTo(x, y) {
|
|
const z = Point.cornerFromXY(x, y).round(CurveUtil.RECIP_GEOMETRY_PRECISION);
|
|
while (this.lastContour.length >= 2) {
|
|
const a = this.lastContour[this.lastContour.length - 2],
|
|
b = this.lastContour[this.lastContour.length - 1];
|
|
if (isLineExtend(a, b, z)) {
|
|
this.lastContour.pop();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
this.lastContour.push(z);
|
|
}
|
|
arcTo(arc, x, y) {
|
|
const offPoints = TypoGeom.Quadify.auto(arc, 1, 16);
|
|
if (offPoints) {
|
|
for (const z of offPoints)
|
|
this.lastContour.push(Point.offFrom(z).round(CurveUtil.RECIP_GEOMETRY_PRECISION));
|
|
}
|
|
this.lineTo(x, y);
|
|
}
|
|
}
|
|
function isLineExtend(a, b, c) {
|
|
return (
|
|
a.on &&
|
|
c.on &&
|
|
((aligned(a.x, b.x, c.x) && between(a.y, b.y, c.y)) ||
|
|
(aligned(a.y, b.y, c.y) && between(a.x, b.x, c.x)))
|
|
);
|
|
}
|
|
function geometryPrecisionEqual(a, b) {
|
|
return (
|
|
Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) ===
|
|
Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION)
|
|
);
|
|
}
|
|
function aligned(a, b, c) {
|
|
return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c);
|
|
}
|
|
function between(a, b, c) {
|
|
return (a <= b && b <= c) || (a >= b && b >= c);
|
|
}
|