SequenceDiagram/scripts/stubs/SVGTextBlock.js

192 lines
3.8 KiB
JavaScript

define(['svg/SVGUtilities'], (svg) => {
'use strict';
// Simplified text block renderer, which assumes all characters render as
// 1x1 px squares for repeatable renders in all browsers
function merge(state, newState) {
for(const k in state) {
if(state.hasOwnProperty(k)) {
if(newState[k] !== null && newState[k] !== undefined) {
state[k] = newState[k];
}
}
}
}
const EMPTY = [];
class SVGTextBlock {
constructor(container, initialState = {}) {
this.container = container;
this.state = {
attrs: {},
formatted: EMPTY,
x: 0,
y: 0,
};
this.nodes = [];
this.set(initialState);
}
_rebuildNodes(count) {
if(count > this.nodes.length) {
const attrs = Object.assign({
'x': this.state.x,
}, this.state.attrs);
while(this.nodes.length < count) {
const text = svg.makeText();
const element = svg.make('text', attrs, [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);
}
}
}
_reset() {
this._rebuildNodes(0);
}
_renderText() {
if(!this.state.formatted) {
this._reset();
return;
}
const formatted = this.state.formatted;
this._rebuildNodes(formatted.length);
this.nodes.forEach(({text, element}, i) => {
const ln = formatted[i].reduce((v, pt) => v + pt.text, '');
text.nodeValue = ln;
});
}
_updateX() {
this.nodes.forEach(({element}) => {
element.setAttribute('x', this.state.x);
});
}
_updateY() {
this.nodes.forEach(({element}, i) => {
element.setAttribute('y', this.state.y + i);
});
}
firstLine() {
if(this.nodes.length > 0) {
return this.nodes[0].element;
} else {
return null;
}
}
set(newState) {
const oldState = Object.assign({}, this.state);
merge(this.state, newState);
if(this.state.attrs !== oldState.attrs) {
this._reset();
oldState.formatted = EMPTY;
}
const oldNodes = this.nodes.length;
if(this.state.formatted !== oldState.formatted) {
this._renderText();
}
if(this.state.x !== oldState.x) {
this._updateX();
}
if(this.state.y !== oldState.y || this.nodes.length !== oldNodes) {
this._updateY();
}
}
}
class SizeTester {
constructor() {
this.expected = new Set();
this.measured = new Set();
}
_getMeasurementOpts(attrs, formatted) {
if(!formatted) {
if(typeof attrs === 'object' && attrs.state) {
formatted = attrs.state.formatted || [];
attrs = attrs.state.attrs;
} else {
formatted = [];
}
} else if(!Array.isArray(formatted)) {
throw new Error('Invalid formatted text: ' + formatted);
}
return {attrs, formatted};
}
expectMeasure(attrs, formatted) {
const opts = this._getMeasurementOpts(attrs, formatted);
this.expected.add(JSON.stringify(opts));
}
performMeasurementsPre() {
}
performMeasurementsAct() {
this.measured = new Set(this.expected);
}
performMeasurementsPost() {
}
measure(attrs, formatted) {
const opts = this._getMeasurementOpts(attrs, formatted);
if(!this.measured.has(JSON.stringify(opts))) {
throw new Error('Unexpected measurement', opts);
}
if(!opts.formatted || !opts.formatted.length) {
return {width: 0, height: 0};
}
let width = 0;
opts.formatted.forEach((line) => {
const length = line.reduce((v, pt) => v + pt.text.length, 0);
width = Math.max(width, length);
});
return {
width,
height: opts.formatted.length,
};
}
measureHeight(attrs, formatted) {
if(!formatted) {
return 0;
}
return formatted.length;
}
resetCache() {
this.expected.clear();
this.measured.clear();
}
}
SVGTextBlock.SizeTester = SizeTester;
return SVGTextBlock;
});