UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

220 lines (219 loc) 7.57 kB
import { Font, FontStyle, FontWeight } from './font.js'; import { log } from './util.js'; function L(...args) { if (TextFormatter.DEBUG) log('Vex.Flow.TextFormatter', args); } const textWidthCache = {}; const textHeightCache = {}; const registry = {}; class TextFormatter { static getFontFamilies() { const registeredFonts = []; for (const fontFamily in registry) { const formatterInfo = registry[fontFamily]; registeredFonts.push(Object.assign({}, formatterInfo)); } return registeredFonts; } static create(requestedFont = {}) { L('create: ', requestedFont); if (!requestedFont.family) { requestedFont.family = Font.SANS_SERIF; } const candidates = []; const requestedFamilies = requestedFont.family.split(/\s*,\s*/); for (const requestedFamily of requestedFamilies) { for (const fontFamily in registry) { if (fontFamily.startsWith(requestedFamily)) { candidates.push(registry[fontFamily]); } } if (candidates.length > 0) { break; } } let formatter; if (candidates.length === 0) { formatter = new TextFormatter(Object.values(registry)[0]); } else if (candidates.length === 1) { formatter = new TextFormatter(candidates[0]); } else { const bold = Font.isBold(requestedFont.weight); const italic = Font.isItalic(requestedFont.style); const perfectMatch = candidates.find((f) => f.bold === bold && f.italic === italic); if (perfectMatch) { formatter = new TextFormatter(perfectMatch); } else { const partialMatch = candidates.find((f) => f.italic === italic || f.bold === bold); if (partialMatch) { formatter = new TextFormatter(partialMatch); } else { formatter = new TextFormatter(candidates[0]); } } } const fontSize = requestedFont.size; if (typeof fontSize !== 'undefined') { const fontSizeInPt = Font.convertSizeToPointValue(fontSize); formatter.setFontSize(fontSizeInPt); } return formatter; } static getInfo(fontFamily) { return registry[fontFamily]; } static registerInfo(info, overwrite = false) { L('registerInfo: ', info, overwrite); const fontFamily = info.family; const currFontInfo = registry[fontFamily]; if (currFontInfo === undefined || overwrite) { registry[fontFamily] = info; } } constructor(formatterInfo) { this.family = ''; this.size = 14; this.resolution = 1000; this.glyphs = {}; this.serifs = false; this.monospaced = false; this.italic = false; this.bold = false; this.superscriptOffset = 0; this.subscriptOffset = 0; this.maxSizeGlyph = '@'; this.cacheKey = ''; this.updateParams(formatterInfo); } get localHeightCache() { var _a; if (textHeightCache[this.cacheKey] === undefined) { textHeightCache[this.cacheKey] = {}; } return (_a = textHeightCache[this.cacheKey]) !== null && _a !== void 0 ? _a : {}; } updateParams(params) { if (params.family) this.family = params.family; if (params.resolution) this.resolution = params.resolution; if (params.glyphs) this.glyphs = params.glyphs; if (params.serifs) this.serifs = params.serifs; if (params.monospaced) this.monospaced = params.monospaced; if (params.italic) this.italic = params.italic; if (params.bold) this.bold = params.bold; if (params.maxSizeGlyph) this.maxSizeGlyph = params.maxSizeGlyph; if (params.superscriptOffset) this.superscriptOffset = params.superscriptOffset; if (params.subscriptOffset) this.subscriptOffset = params.subscriptOffset; this.updateCacheKey(); } updateCacheKey() { const family = this.family.replace(/\s+/g, '_'); const size = this.size; const weight = this.bold ? FontWeight.BOLD : FontWeight.NORMAL; const style = this.italic ? FontStyle.ITALIC : FontStyle.NORMAL; this.cacheKey = `${family}%${size}%${weight}%${style}`; } getGlyphMetrics(character) { if (this.glyphs[character]) { return this.glyphs[character]; } else { return this.glyphs[this.maxSizeGlyph]; } } get maxHeight() { const metrics = this.getGlyphMetrics(this.maxSizeGlyph); return (metrics.ha / this.resolution) * this.fontSizeInPixels; } getWidthForCharacterInEm(c) { var _a; const metrics = this.getGlyphMetrics(c); if (!metrics) { return 0.65; } else { const advanceWidth = (_a = metrics.advanceWidth) !== null && _a !== void 0 ? _a : 0; return advanceWidth / this.resolution; } } getYForCharacterInPx(c) { const metrics = this.getGlyphMetrics(c); const rv = { yMin: 0, yMax: this.maxHeight, height: this.maxHeight }; if (!metrics) { return rv; } else { if (typeof metrics.y_min === 'number') { rv.yMin = (metrics.y_min / this.resolution) * this.fontSizeInPixels; } if (typeof metrics.y_max === 'number') { rv.yMax = (metrics.y_max / this.resolution) * this.fontSizeInPixels; } rv.height = rv.yMax - rv.yMin; return rv; } } getYForStringInPx(str) { const entry = this.localHeightCache; const extent = { yMin: 0, yMax: this.maxHeight, height: this.maxHeight }; const cache = entry[str]; if (cache !== undefined) { return cache; } for (let i = 0; i < str.length; ++i) { const curY = this.getYForCharacterInPx(str[i]); extent.yMin = Math.min(extent.yMin, curY.yMin); extent.yMax = Math.max(extent.yMax, curY.yMax); extent.height = extent.yMax - extent.yMin; } entry[str] = extent; return extent; } getWidthForTextInEm(text) { const key = this.cacheKey; let cachedWidths = textWidthCache[key]; if (cachedWidths === undefined) { cachedWidths = {}; textWidthCache[key] = cachedWidths; } let width = cachedWidths[text]; if (width === undefined) { width = 0; for (let i = 0; i < text.length; ++i) { width += this.getWidthForCharacterInEm(text[i]); } cachedWidths[text] = width; } return width; } getWidthForTextInPx(text) { return this.getWidthForTextInEm(text) * this.fontSizeInPixels; } setFontSize(size) { this.size = size; this.updateCacheKey(); return this; } get fontSizeInPixels() { return this.size * Font.scaleToPxFrom.pt; } getResolution() { return this.resolution; } } TextFormatter.DEBUG = false; export { TextFormatter };