176 lines
3.5 KiB
JavaScript
176 lines
3.5 KiB
JavaScript
define(['./SVGUtilities'], (svg) => {
|
|
'use strict';
|
|
|
|
function fontDetails(attrs) {
|
|
const size = Number(attrs['font-size']);
|
|
const lineHeight = size * (Number(attrs['line-height']) || 1);
|
|
return {
|
|
size,
|
|
lineHeight,
|
|
};
|
|
}
|
|
|
|
class SVGTextBlock {
|
|
constructor(
|
|
container,
|
|
attrs,
|
|
{text = '', x = 0, y = 0} = {}
|
|
) {
|
|
this.container = container;
|
|
this.attrs = attrs;
|
|
this.text = '';
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = 0;
|
|
this.height = 0;
|
|
this.nodes = [];
|
|
this.setText(text);
|
|
}
|
|
|
|
_updateY() {
|
|
const {size, lineHeight} = fontDetails(this.attrs);
|
|
this.nodes.forEach(({element}, i) => {
|
|
element.setAttribute('y', this.y + i * lineHeight + size);
|
|
});
|
|
this.height = lineHeight * this.nodes.length;
|
|
}
|
|
|
|
_rebuildNodes(count) {
|
|
if(count === this.nodes.length) {
|
|
return;
|
|
}
|
|
if(count > this.nodes.length) {
|
|
const attrs = Object.assign({'x': this.x}, this.attrs);
|
|
while(this.nodes.length < count) {
|
|
const element = svg.make('text', attrs);
|
|
const text = svg.makeText();
|
|
element.appendChild(text);
|
|
this.container.appendChild(element);
|
|
this.nodes.push({element, text});
|
|
}
|
|
} else {
|
|
while(this.nodes.length > count) {
|
|
const {element} = this.nodes.pop();
|
|
this.container.removeChild(element);
|
|
}
|
|
}
|
|
this._updateY();
|
|
}
|
|
|
|
firstLine() {
|
|
if(this.nodes.length > 0) {
|
|
return this.nodes[0].element;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
setText(newText) {
|
|
if(newText === this.text) {
|
|
return;
|
|
}
|
|
if(!newText) {
|
|
this.clear();
|
|
return;
|
|
}
|
|
this.text = newText;
|
|
const lines = this.text.split('\n');
|
|
|
|
this._rebuildNodes(lines.length);
|
|
let maxWidth = 0;
|
|
this.nodes.forEach(({text, element}, i) => {
|
|
if(text.nodeValue !== lines[i]) {
|
|
text.nodeValue = lines[i];
|
|
}
|
|
maxWidth = Math.max(maxWidth, element.getComputedTextLength());
|
|
});
|
|
this.width = maxWidth;
|
|
}
|
|
|
|
reanchor(newX, newY) {
|
|
if(newX !== this.x) {
|
|
this.x = newX;
|
|
this.nodes.forEach(({element}) => {
|
|
element.setAttribute('x', this.x);
|
|
});
|
|
}
|
|
|
|
if(newY !== this.y) {
|
|
this.y = newY;
|
|
this._updateY();
|
|
}
|
|
}
|
|
|
|
clear() {
|
|
this._rebuildNodes(0);
|
|
this.text = '';
|
|
this.width = 0;
|
|
this.height = 0;
|
|
}
|
|
}
|
|
|
|
class SizeTester {
|
|
constructor(container) {
|
|
this.testers = svg.make('g', {'display': 'none'});
|
|
this.container = container;
|
|
this.cache = new Map();
|
|
}
|
|
|
|
measure(attrs, content) {
|
|
if(!content) {
|
|
return {width: 0, height: 0};
|
|
}
|
|
|
|
let tester = this.cache.get(attrs);
|
|
if(!tester) {
|
|
const text = svg.makeText();
|
|
const node = svg.make('text', attrs);
|
|
node.appendChild(text);
|
|
this.testers.appendChild(node);
|
|
tester = {text, node};
|
|
this.cache.set(attrs, tester);
|
|
}
|
|
|
|
if(!this.testers.parentNode) {
|
|
this.container.appendChild(this.testers);
|
|
}
|
|
|
|
const lines = content.split('\n');
|
|
let width = 0;
|
|
lines.forEach((line) => {
|
|
tester.text.nodeValue = line;
|
|
width = Math.max(width, tester.node.getComputedTextLength());
|
|
});
|
|
|
|
return {
|
|
width,
|
|
height: lines.length * fontDetails(attrs).lineHeight,
|
|
};
|
|
}
|
|
|
|
measureHeight(attrs, content) {
|
|
if(!content) {
|
|
return 0;
|
|
}
|
|
|
|
const lines = content.split('\n');
|
|
return lines.length * fontDetails(attrs).lineHeight;
|
|
}
|
|
|
|
resetCache() {
|
|
svg.empty(this.testers);
|
|
this.cache.clear();
|
|
}
|
|
|
|
detach() {
|
|
if(this.testers.parentNode) {
|
|
this.container.removeChild(this.testers);
|
|
}
|
|
}
|
|
}
|
|
|
|
SVGTextBlock.SizeTester = SizeTester;
|
|
|
|
return SVGTextBlock;
|
|
});
|