165 lines
4.7 KiB
JavaScript
165 lines
4.7 KiB
JavaScript
"use strict";
|
|
|
|
const Point = require("../../support/point");
|
|
const { AnyCv } = require("../../support/gr");
|
|
|
|
function autoref(glyphStore) {
|
|
suppressNaN(glyphStore);
|
|
hashContours(glyphStore);
|
|
|
|
const glyphEntryList = getGlyphEntryList(glyphStore);
|
|
linkRefl(glyphEntryList);
|
|
linkComponent(glyphEntryList);
|
|
unlinkHybrid(glyphStore);
|
|
}
|
|
|
|
function suppressNaN(glyphStore) {
|
|
for (const g of glyphStore.glyphs()) {
|
|
if (!g.contours) continue;
|
|
for (let k = 0; k < g.contours.length; k++) {
|
|
let contour = g.contours[k];
|
|
for (let z of contour) {
|
|
if (!isFinite(z.x)) z.x = 0;
|
|
if (!isFinite(z.y)) z.y = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function hashContours(glyphStore) {
|
|
for (const g of glyphStore.glyphs()) {
|
|
if (!g.contours) continue;
|
|
for (let k = 0; k < g.contours.length; k++) {
|
|
const contour = g.contours[k];
|
|
contour.hash = contourHash(contour);
|
|
}
|
|
}
|
|
}
|
|
function contourHash(c) {
|
|
if (!c || c.length < 2) return ".";
|
|
let lx = c[0].x,
|
|
ly = c[0].y;
|
|
let buf = "";
|
|
for (let j = 1; j < c.length; j++) {
|
|
const z = c[j];
|
|
buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`;
|
|
(lx = z.x), (ly = z.y);
|
|
}
|
|
return buf;
|
|
}
|
|
function delta(a, b) {
|
|
return Math.round((a - b) * 32);
|
|
}
|
|
|
|
function getGlyphEntryList(glyphStore) {
|
|
const excludeUnicode = new Set();
|
|
excludeUnicode.add(0x80);
|
|
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
|
|
|
|
for (const [j, gn, g] of glyphStore.indexedNamedEntries()) {
|
|
if (AnyCv.query(g).length) g.autoRefPriority = -1;
|
|
const us = glyphStore.queryUnicodeOf(g);
|
|
if (us) {
|
|
for (const u of us) if (excludeUnicode.has(u)) g.avoidBeingComposite = true;
|
|
}
|
|
}
|
|
|
|
return Array.from(glyphStore.indexedNamedEntries()).sort(byGlyphPriority);
|
|
}
|
|
|
|
function byGlyphPriority([ja, gna, a], [jb, gnb, 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 linkRefl(glyphEntryList) {
|
|
for (let j = 0; j < glyphEntryList.length; j++) {
|
|
const [, gnj, gj] = glyphEntryList[j];
|
|
if (!gj.contours.length || (gj.references && gj.references.length)) continue;
|
|
for (let k = j + 1; k < glyphEntryList.length; k++) {
|
|
const [, gnk, gk] = glyphEntryList[k];
|
|
if (gj.contours.length === gk.contours.length) {
|
|
match(gnj, gj, gnk, gk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function linkComponent(glyphEntryList) {
|
|
for (let j = 0; j < glyphEntryList.length; j++) {
|
|
const [, gnj, gj] = glyphEntryList[j];
|
|
if (gj.autoRefPriority < 0) continue;
|
|
if (!gj.contours.length) continue;
|
|
if (gj.references && gj.references.length) continue;
|
|
for (let k = glyphEntryList.length - 1; k >= 0; k--) {
|
|
const [, gnk, gk] = glyphEntryList[k];
|
|
if (gj.contours.length > gk.contours.length) continue;
|
|
if (
|
|
gj.contours.length === gk.contours.length &&
|
|
!(gk.references && gk.references.length)
|
|
) {
|
|
continue;
|
|
}
|
|
while (match(gnj, gj, gnk, gk)) "pass";
|
|
}
|
|
}
|
|
}
|
|
function match(gn1, g1, gn2, g2) {
|
|
for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) {
|
|
let found = true;
|
|
for (let k = j; k < g2.contours.length && k - j < g1.contours.length; k++) {
|
|
if (
|
|
g1.contours[k - j].hash !== g2.contours[k].hash ||
|
|
!(
|
|
k <= j ||
|
|
(delta(g1.contours[k - j][0].x, g1.contours[k - j - 1][0].x) ===
|
|
delta(g2.contours[k][0].x, g2.contours[k - 1][0].x) &&
|
|
delta(g1.contours[k - j][0].y, g1.contours[k - j - 1][0].y) ===
|
|
delta(g2.contours[k][0].y, g2.contours[k - 1][0].y))
|
|
)
|
|
) {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
const refX = g2.contours[j][0].x - g1.contours[0][0].x || 0;
|
|
const refY = g2.contours[j][0].y - g1.contours[0][0].y || 0;
|
|
if (Math.abs(refY) > 1 && g1.advanceWidth > 1) {
|
|
continue;
|
|
}
|
|
if (!g2.references) g2.references = [];
|
|
g2.references.push({ glyph: gn1, x: refX, y: refY, roundToGrid: false });
|
|
g2.contours.splice(j, g1.contours.length);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function unlinkHybrid(glyphStore) {
|
|
for (const g of glyphStore.glyphs()) {
|
|
if (!g.references || g.references.length === 0) continue;
|
|
if (!g.avoidBeingComposite && g.contours.length === 0) continue;
|
|
g.contours = unlinkRef(g, 0, 0, glyphStore);
|
|
g.references = [];
|
|
}
|
|
}
|
|
|
|
function unlinkRef(g, dx, dy, glyphStore) {
|
|
let contours = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic)));
|
|
if (g.references) {
|
|
for (let r of g.references) {
|
|
contours = contours.concat(
|
|
unlinkRef(glyphStore.queryByName(r.glyph), r.x + dx, r.y + dy, glyphStore)
|
|
);
|
|
}
|
|
}
|
|
return contours;
|
|
}
|
|
|
|
module.exports = autoref;
|