vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
402 lines (401 loc) • 12.4 kB
JavaScript
import { BoundingBox } from './boundingbox.js';
import { Font } from './font.js';
import { Metrics } from './metrics.js';
import { Registry } from './registry.js';
import { defined, prefix, RuntimeError } from './util.js';
export class Element {
static get CATEGORY() {
return "Element";
}
static newID() {
return `auto${Element.ID++}`;
}
static setTextMeasurementCanvas(canvas) {
Element.txtCanvas = canvas;
}
static getTextMeasurementCanvas() {
let txtCanvas = Element.txtCanvas;
if (!txtCanvas) {
if (typeof document !== 'undefined') {
txtCanvas = document.createElement('canvas');
}
else if (typeof OffscreenCanvas !== 'undefined') {
txtCanvas = new OffscreenCanvas(300, 150);
}
Element.txtCanvas = txtCanvas;
}
return txtCanvas;
}
constructor(category) {
var _a;
this.children = [];
this.style = {};
this._text = '';
this.metricsValid = false;
this._textMetrics = {
fontBoundingBoxAscent: 0,
fontBoundingBoxDescent: 0,
actualBoundingBoxAscent: 0,
actualBoundingBoxDescent: 0,
actualBoundingBoxLeft: 0,
actualBoundingBoxRight: 0,
width: 0,
alphabeticBaseline: 0,
emHeightAscent: 0,
emHeightDescent: 0,
hangingBaseline: 0,
ideographicBaseline: 0,
};
this._height = 0;
this._width = 0;
this.xShift = 0;
this.yShift = 0;
this.x = 0;
this.y = 0;
this.attrs = {
id: Element.newID(),
type: category !== null && category !== void 0 ? category : this.constructor.CATEGORY,
class: '',
};
this.rendered = false;
this._fontInfo = Metrics.getFontInfo(this.attrs.type);
this.style = Metrics.getStyle(this.attrs.type);
this.fontScale = Metrics.get(`${this.attrs.type}.fontScale`);
(_a = Registry.getDefaultRegistry()) === null || _a === void 0 ? void 0 : _a.register(this);
}
addChild(child) {
if (child.parent)
throw new RuntimeError('Element', 'Parent already defined');
child.parent = this;
this.children.push(child);
return this;
}
getCategory() {
return this.attrs.type;
}
setStyle(style) {
this.style = style;
return this;
}
setGroupStyle(style) {
this.style = style;
this.children.forEach((child) => child.setGroupStyle(style));
return this;
}
getStyle() {
return this.style;
}
applyStyle(context = this.context, style = this.getStyle()) {
if (!context)
return this;
if (style.shadowColor)
context.setShadowColor(style.shadowColor);
if (style.shadowBlur)
context.setShadowBlur(style.shadowBlur);
if (style.fillStyle)
context.setFillStyle(style.fillStyle);
if (style.strokeStyle)
context.setStrokeStyle(style.strokeStyle);
if (style.lineWidth)
context.setLineWidth(style.lineWidth);
if (style.lineDash)
context.setLineDash(style.lineDash.split(' ').map(Number));
return this;
}
drawWithStyle() {
const ctx = this.checkContext();
ctx.save();
this.applyStyle(ctx);
this.draw();
ctx.restore();
return this;
}
draw() {
throw new RuntimeError('Element', 'Draw not defined');
}
hasClass(className) {
var _a;
if (!this.attrs.class)
return false;
return ((_a = this.attrs.class) === null || _a === void 0 ? void 0 : _a.split(' ').indexOf(className)) !== -1;
}
addClass(className) {
var _a;
if (this.hasClass(className))
return this;
if (!this.attrs.class)
this.attrs.class = `${className}`;
else
this.attrs.class = `${this.attrs.class} ${className}`;
(_a = this.registry) === null || _a === void 0 ? void 0 : _a.onUpdate({
id: this.attrs.id,
name: 'class',
value: className,
oldValue: undefined,
});
return this;
}
removeClass(className) {
var _a, _b;
if (!this.hasClass(className))
return this;
const arr = (_a = this.attrs.class) === null || _a === void 0 ? void 0 : _a.split(' ');
if (arr) {
arr.splice(arr.indexOf(className));
this.attrs.class = arr.join(' ');
}
(_b = this.registry) === null || _b === void 0 ? void 0 : _b.onUpdate({
id: this.attrs.id,
name: 'class',
value: undefined,
oldValue: className,
});
return this;
}
onRegister(registry) {
this.registry = registry;
return this;
}
isRendered() {
return this.rendered;
}
setRendered(rendered = true) {
this.rendered = rendered;
return this;
}
getAttributes() {
return this.attrs;
}
getAttribute(name) {
return this.attrs[name];
}
getSVGElement(suffix = '') {
const id = prefix(this.attrs.id + suffix);
const element = document.getElementById(id);
if (element)
return element;
}
setAttribute(name, value) {
var _a;
const oldID = this.attrs.id;
const oldValue = this.attrs[name];
this.attrs[name] = value;
(_a = this.registry) === null || _a === void 0 ? void 0 : _a.onUpdate({ id: oldID, name, value, oldValue });
return this;
}
getBoundingBox() {
return new BoundingBox(this.x + this.xShift, this.y + this.yShift - this.textMetrics.actualBoundingBoxAscent, this.width, this.height);
}
getContext() {
return this.context;
}
setContext(context) {
this.context = context;
return this;
}
checkContext() {
return defined(this.context, 'NoContext', 'No rendering context attached to instance.');
}
set font(f) {
this.setFont(f);
}
get font() {
return Font.toCSSString(this._fontInfo);
}
setFont(font, size, weight, style) {
const defaultTextFont = Metrics.getFontInfo(this.attrs.type);
const fontIsObject = typeof font === 'object';
const fontIsString = typeof font === 'string';
const sizeWeightStyleAreUndefined = size === undefined && weight === undefined && style === undefined;
this.metricsValid = false;
if (fontIsObject) {
this._fontInfo = Object.assign(Object.assign({}, defaultTextFont), font);
}
else if (fontIsString && sizeWeightStyleAreUndefined) {
this._fontInfo = Font.fromCSSString(font);
}
else {
this._fontInfo = Font.validate(font !== null && font !== void 0 ? font : defaultTextFont.family, size !== null && size !== void 0 ? size : defaultTextFont.size, weight !== null && weight !== void 0 ? weight : defaultTextFont.weight, style !== null && style !== void 0 ? style : defaultTextFont.style);
}
return this;
}
getFont() {
return Font.toCSSString(this._fontInfo);
}
get fontInfo() {
return this._fontInfo;
}
set fontInfo(fontInfo) {
this.setFont(fontInfo);
}
setFontSize(size) {
const fontInfo = this.fontInfo;
this.setFont(fontInfo.family, size, fontInfo.weight, fontInfo.style);
return this;
}
getFontSize() {
return this.fontSize;
}
getFontScale() {
return this.fontScale;
}
set fontSize(size) {
this.setFontSize(size);
}
get fontSize() {
let size = this.fontInfo.size;
if (typeof size === 'number') {
size = `${size}pt`;
}
return size;
}
get fontSizeInPoints() {
return Font.convertSizeToPointValue(this.fontSize);
}
get fontSizeInPixels() {
return Font.convertSizeToPixelValue(this.fontSize);
}
get fontStyle() {
return this.fontInfo.style;
}
set fontStyle(style) {
const fontInfo = this.fontInfo;
this.setFont(fontInfo.family, fontInfo.size, fontInfo.weight, style);
}
get fontWeight() {
return this.fontInfo.weight + '';
}
set fontWeight(weight) {
const fontInfo = this.fontInfo;
this.setFont(fontInfo.family, fontInfo.size, weight, fontInfo.style);
}
getWidth() {
return this.width;
}
get width() {
if (!this.metricsValid)
this.measureText();
return this._width;
}
setWidth(width) {
this.width = width;
return this;
}
set width(width) {
if (!this.metricsValid)
this.measureText();
this._width = width;
}
setX(x) {
this.x = x;
return this;
}
getX() {
return this.x;
}
getY() {
return this.y;
}
setY(y) {
this.y = y;
return this;
}
setYShift(yShift) {
this.yShift = yShift;
return this;
}
getYShift() {
return this.yShift;
}
setXShift(xShift) {
this.xShift = xShift;
return this;
}
getXShift() {
return this.xShift;
}
setText(text) {
this.text = text;
return this;
}
set text(text) {
this.metricsValid = false;
this._text = text;
}
getText() {
return this._text;
}
get text() {
return this._text;
}
renderText(ctx, xPos, yPos) {
ctx.setFont(this._fontInfo);
ctx.fillText(this._text, xPos + this.x + this.xShift, yPos + this.y + this.yShift);
this.children.forEach((child) => {
ctx.setFont(child.fontInfo);
ctx.fillText(child.text, xPos + child.x + child.xShift, yPos + child.y + child.yShift);
});
}
measureText() {
var _a;
const context = (_a = Element.getTextMeasurementCanvas()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
if (!context) {
console.warn('Element: No context for txtCanvas. Returning empty text metrics.');
return this._textMetrics;
}
context.font = Font.toCSSString(Font.validate(this.fontInfo));
this._textMetrics = context.measureText(this.text);
this._height = this._textMetrics.actualBoundingBoxAscent + this._textMetrics.actualBoundingBoxDescent;
this._width = this._textMetrics.width;
this.metricsValid = true;
return this._textMetrics;
}
static measureWidth(text, key = '') {
var _a;
const context = (_a = Element.getTextMeasurementCanvas()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
if (!context) {
console.warn('Element: No context for txtCanvas. Returning empty text metrics.');
return 0;
}
context.font = Font.toCSSString(Metrics.getFontInfo(key));
return context.measureText(text).width;
}
getTextMetrics() {
return this.textMetrics;
}
get textMetrics() {
if (!this.metricsValid)
this.measureText();
return this._textMetrics;
}
getHeight() {
return this.height;
}
get height() {
if (!this.metricsValid)
this.measureText();
return this._height;
}
set height(height) {
if (!this.metricsValid)
this.measureText();
this._height = height;
}
setOriginX(x) {
const bbox = this.getBoundingBox();
const originX = Math.abs((bbox.getX() - this.xShift) / bbox.getW());
const xShift = (x - originX) * bbox.getW();
this.xShift = -xShift;
}
setOriginY(y) {
const bbox = this.getBoundingBox();
const originY = Math.abs((bbox.getY() - this.yShift) / bbox.getH());
const yShift = (y - originY) * bbox.getH();
this.yShift = -yShift;
}
setOrigin(x, y) {
this.setOriginX(x);
this.setOriginY(y);
}
}
Element.ID = 1000;