UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

117 lines (116 loc) 4.53 kB
import * as util from '../util'; import { Rect } from '../spatial'; import { Pen } from './pen'; import { TextMeasurer } from './textmeasurer'; export class Label { config; log; lines; font; rect; ctx = null; constructor(config, log, lines, font, rect) { this.config = config; this.log = log; this.lines = lines; this.font = font; this.rect = rect; } static singleLine(config, log, textContent, position, padding, font) { const fragments = Label.split(textContent, font, Infinity); const pen = new Pen(position); const lines = new Array(); for (const fragment of fragments) { const paddingLeft = padding.left ?? 0; const paddingRight = padding.right ?? 0; const paddingTop = padding.top ?? 0; const paddingBottom = padding.bottom ?? 0; const x = pen.x - paddingLeft; const y = pen.y - paddingTop - fragment.height; const w = fragment.width + paddingLeft + paddingRight; const h = fragment.height + paddingTop + paddingBottom; const rect = new Rect(x, y, w, h); lines.push({ text: fragment.text, rect }); } const rect = Rect.merge(lines.map((line) => line.rect)); return new Label(config, log, lines, font, rect); } static centerAligned(config, log, maxWidth, textContent, position, padding, font) { const fragments = Label.split(textContent, font, maxWidth); const pen = new Pen(position); const lines = new Array(); for (const fragment of fragments) { const paddingLeft = padding.left ?? 0; const paddingRight = padding.right ?? 0; const paddingTop = padding.top ?? 0; const paddingBottom = padding.bottom ?? 0; const x = position.x + (maxWidth - fragment.width) / 2 - paddingLeft; const y = pen.y - paddingTop - fragment.height; const w = fragment.width + paddingLeft + paddingRight; const h = fragment.height + paddingTop + paddingBottom; const rect = new Rect(x, y, w, h); lines.push({ text: fragment.text, rect }); const lineHeight = font.lineHeight ?? fragment.height; pen.moveTo({ x: position.x, y: pen.y + lineHeight }); } const rect = Rect.merge(lines.map((line) => line.rect)); return new Label(config, log, lines, font, rect); } static split(textContent, font, maxWidth) { const words = textContent.split(/\s+/); const fragments = new Array(); const textMeasurer = new TextMeasurer(font); const spaceWidth = textMeasurer.measure(' ').width; let texts = new Array(); let width = 0; let height = 0; // Break down the words into fragments that fit within the maxWidth. for (let index = 0; index < words.length; index++) { const word = words[index]; const wordMetrics = textMeasurer.measure(word); const wordWidth = wordMetrics.width; const wordHeight = wordMetrics.approximateHeight; const remainingWidth = maxWidth - width; if (remainingWidth < wordWidth && texts.length > 0) { fragments.push({ text: texts.join(' '), width, height }); texts = []; width = 0; height = 0; } texts.push(word); width += wordWidth + spaceWidth; height = Math.max(height, wordHeight); if (index === words.length - 1 && texts.length > 0) { fragments.push({ text: texts.join(' '), width, height }); } } return fragments; } setContext(ctx) { this.ctx = ctx; return this; } draw() { const ctx = this.ctx; util.assertNotNull(ctx); ctx.save(); if (this.font.color) { ctx.setFillStyle(this.font.color); } const fontInfo = {}; if (this.font.family) { fontInfo.family = this.font.family; } if (this.font.size) { fontInfo.size = this.font.size; } if (Object.keys(fontInfo).length > 0) { ctx.setFont(fontInfo); } for (const line of this.lines) { ctx.fillText(line.text, line.rect.x, line.rect.y + line.rect.h); } ctx.restore(); return this; } }