SequenceDiagram/scripts/svg/VirtualTextSizer.mjs

130 lines
3.1 KiB
JavaScript

const fs = require('fs');
const opentype = require('opentype.js');
const path = require('path');
const FONTDIR = process.env.FONTDIR || './fonts/';
const FONTS = new Map();
function loadFont(relativePath) {
// Must be synchronous so that measurements are ready once startup completes
/* eslint-disable no-sync */
const data = fs.readFileSync(path.join(FONTDIR, relativePath));
/* eslint-enable no-sync */
return opentype.parse(data.buffer);
}
function addFont(name, variants) {
const types = new Map();
for(const v in variants) {
if(Object.prototype.hasOwnProperty.call(variants, v)) {
const font = loadFont(variants[v]);
font.id = name + '-' + v;
types.set(v, font);
}
}
FONTS.set(name.toLowerCase(), types);
}
addFont('sans-serif', {
'': 'liberation-fonts/LiberationSans-Regular.ttf',
'bold': 'liberation-fonts/LiberationSans-Bold.ttf',
'bold-italic': 'liberation-fonts/LiberationSans-BoldItalic.ttf',
'italic': 'liberation-fonts/LiberationSans-Italic.ttf',
});
addFont('monospace', {
'': 'liberation-fonts/LiberationMono-Regular.ttf',
'bold': 'liberation-fonts/LiberationMono-Bold.ttf',
'bold-italic': 'liberation-fonts/LiberationMono-BoldItalic.ttf',
'italic': 'liberation-fonts/LiberationMono-Italic.ttf',
});
addFont('handlee', {
'': 'handlee/Handlee.ttf',
});
const DEFAULT_FONT = 'sans-serif';
const OPENTYPE_OPTIONS = {hinting: true};
function getFont(attrs) {
const family = (attrs['font-family'] || DEFAULT_FONT).split(',');
for(const nm of family) {
const name = nm.trim().replace(/['"]/g, '').toLowerCase();
const font = FONTS.get(name);
if(font) {
return font;
}
}
return FONTS.get(DEFAULT_FONT);
}
function tryVariant(font, condition, name) {
if(!condition) {
return null;
}
return font.get(name) || null;
}
function getVariantRaw(font, {bold, italic}) {
return (
tryVariant(font, bold && italic, 'bold-italic') ||
tryVariant(font, bold, 'bold') ||
tryVariant(font, italic, 'italic') ||
font.get('')
);
}
function getVariant(font, attrs) {
const weight = attrs['font-weight'] || '';
const style = attrs['font-style'] || '';
const bold = (weight.includes('bold') || Number(weight) > 400);
const italic = style.includes('italic');
return getVariantRaw(font, {bold, italic});
}
function getFontSize(attrs) {
return Number(attrs['font-size']);
}
function measure(attrs, text) {
const font = getFont(attrs);
const variant = getVariant(font, attrs);
const size = getFontSize(attrs);
return variant.getAdvanceWidth(text, size, OPENTYPE_OPTIONS);
}
export default class VirtualTextSizer {
baseline({attrs}) {
return getFontSize(attrs);
}
measureHeight({attrs, formatted}) {
const size = this.baseline({attrs, formatted});
const lineHeight = size * (Number(attrs['line-height']) || 1);
return formatted.length * lineHeight;
}
prepMeasurement(attrs, formatted) {
return {attrs, formatted};
}
prepComplete() {
// No-op
}
performMeasurement({attrs, formatted}) {
let len = 0;
for(const part of formatted) {
if(!part.text) {
continue;
}
len += measure(Object.assign({}, attrs, part.attrs), part.text);
}
return len;
}
teardown() {
// No-op
}
}