144 lines
3.4 KiB
JavaScript
144 lines
3.4 KiB
JavaScript
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 */
|
|
return opentype.loadSync(path.join(FONTDIR, relativePath));
|
|
/* eslint-enable no-sync */
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
function combineAttrs(base, next) {
|
|
if(!next) {
|
|
return base;
|
|
}
|
|
const result = Object.assign({}, base, next);
|
|
const nextSize = next['font-size'];
|
|
if(typeof nextSize === 'string' && nextSize.endsWith('em')) {
|
|
const ratio = Number.parseFloat(
|
|
nextSize.substr(0, nextSize.length - 2)
|
|
);
|
|
result['font-size'] = base['font-size'] * ratio;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
}
|
|
const combinedAttrs = combineAttrs(attrs, part.attrs);
|
|
len += measure(combinedAttrs, part.text);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
teardown() {
|
|
// No-op
|
|
}
|
|
}
|