/**
|
|
* Callback to determine text dimensions, using the parent label settings.
|
|
* @callback MeasureText
|
|
* @param {text} text
|
|
* @param {text} mod
|
|
* @return {Object} { width, values} width in pixels and font attributes
|
|
*/
|
|
|
|
|
|
/**
|
|
* Helper class for Label which collects results of splitting labels into lines and blocks.
|
|
*
|
|
* @private
|
|
*/
|
|
class LabelAccumulator {
|
|
|
|
/**
|
|
* @param {MeasureText} measureText
|
|
*/
|
|
constructor(measureText) {
|
|
this.measureText = measureText;
|
|
this.current = 0;
|
|
this.width = 0;
|
|
this.height = 0;
|
|
this.lines = [];
|
|
}
|
|
|
|
|
|
/**
|
|
* Append given text to the given line.
|
|
*
|
|
* @param {number} l index of line to add to
|
|
* @param {string} text string to append to line
|
|
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
|
* @private
|
|
*/
|
|
_add(l, text, mod = 'normal') {
|
|
|
|
if (this.lines[l] === undefined) {
|
|
this.lines[l] = {
|
|
width : 0,
|
|
height: 0,
|
|
blocks: []
|
|
};
|
|
}
|
|
|
|
// We still need to set a block for undefined and empty texts, hence return at this point
|
|
// This is necessary because we don't know at this point if we're at the
|
|
// start of an empty line or not.
|
|
// To compensate, empty blocks are removed in `finalize()`.
|
|
//
|
|
// Empty strings should still have a height
|
|
let tmpText = text;
|
|
if (text === undefined || text === "") tmpText = " ";
|
|
|
|
// Determine width and get the font properties
|
|
let result = this.measureText(tmpText, mod);
|
|
let block = Object.assign({}, result.values);
|
|
block.text = text;
|
|
block.width = result.width;
|
|
block.mod = mod;
|
|
|
|
if (text === undefined || text === "") {
|
|
block.width = 0;
|
|
}
|
|
|
|
this.lines[l].blocks.push(block);
|
|
|
|
// Update the line width. We need this for determining if a string goes over max width
|
|
this.lines[l].width += block.width;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the width in pixels of the current line.
|
|
*
|
|
* @returns {number}
|
|
*/
|
|
curWidth() {
|
|
let line = this.lines[this.current];
|
|
if (line === undefined) return 0;
|
|
|
|
return line.width;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add text in block to current line
|
|
*
|
|
* @param {string} text
|
|
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
|
*/
|
|
append(text, mod = 'normal') {
|
|
this._add(this.current, text, mod);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add text in block to current line and start a new line
|
|
*
|
|
* @param {string} text
|
|
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
|
|
*/
|
|
newLine(text, mod = 'normal') {
|
|
this._add(this.current, text, mod);
|
|
this.current++;
|
|
}
|
|
|
|
|
|
/**
|
|
* Determine and set the heights of all the lines currently contained in this instance
|
|
*
|
|
* Note that width has already been set.
|
|
*
|
|
* @private
|
|
*/
|
|
determineLineHeights() {
|
|
for (let k = 0; k < this.lines.length; k++) {
|
|
let line = this.lines[k];
|
|
|
|
// Looking for max height of blocks in line
|
|
let height = 0;
|
|
|
|
if (line.blocks !== undefined) { // Can happen if text contains e.g. '\n '
|
|
for (let l = 0; l < line.blocks.length; l++) {
|
|
let block = line.blocks[l];
|
|
|
|
if (height < block.height) {
|
|
height = block.height;
|
|
}
|
|
}
|
|
}
|
|
|
|
line.height = height;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Determine the full size of the label text, as determined by current lines and blocks
|
|
*
|
|
* @private
|
|
*/
|
|
determineLabelSize() {
|
|
let width = 0;
|
|
let height = 0;
|
|
for (let k = 0; k < this.lines.length; k++) {
|
|
let line = this.lines[k];
|
|
|
|
if (line.width > width) {
|
|
width = line.width;
|
|
}
|
|
height += line.height;
|
|
}
|
|
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove all empty blocks and empty lines we don't need
|
|
*
|
|
* This must be done after the width/height determination,
|
|
* so that these are set properly for processing here.
|
|
*
|
|
* @returns {Array<Line>} Lines with empty blocks (and some empty lines) removed
|
|
* @private
|
|
*/
|
|
removeEmptyBlocks() {
|
|
let tmpLines = [];
|
|
for (let k = 0; k < this.lines.length; k++) {
|
|
let line = this.lines[k];
|
|
|
|
// Note: an empty line in between text has width zero but is still relevant to layout.
|
|
// So we can't use width for testing empty line here
|
|
if (line.blocks.length === 0) continue;
|
|
|
|
// Discard final empty line always
|
|
if(k === this.lines.length - 1) {
|
|
if (line.width === 0) continue;
|
|
}
|
|
|
|
let tmpLine = {};
|
|
Object.assign(tmpLine, line);
|
|
tmpLine.blocks = [];
|
|
|
|
let firstEmptyBlock;
|
|
let tmpBlocks = []
|
|
for (let l = 0; l < line.blocks.length; l++) {
|
|
let block = line.blocks[l];
|
|
if (block.width !== 0) {
|
|
tmpBlocks.push(block);
|
|
} else {
|
|
if (firstEmptyBlock === undefined) {
|
|
firstEmptyBlock = block;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that there is *some* text present
|
|
if (tmpBlocks.length === 0 && firstEmptyBlock !== undefined) {
|
|
tmpBlocks.push(firstEmptyBlock);
|
|
}
|
|
|
|
tmpLine.blocks = tmpBlocks;
|
|
|
|
tmpLines.push(tmpLine);
|
|
}
|
|
|
|
return tmpLines;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the sizes for all lines and the whole thing.
|
|
*
|
|
* @returns {{width: (number|*), height: (number|*), lines: Array}}
|
|
*/
|
|
finalize() {
|
|
//console.log(JSON.stringify(this.lines, null, 2));
|
|
|
|
this.determineLineHeights();
|
|
this.determineLabelSize();
|
|
let tmpLines = this.removeEmptyBlocks();
|
|
|
|
|
|
// Return a simple hash object for further processing.
|
|
return {
|
|
width : this.width,
|
|
height: this.height,
|
|
lines : tmpLines
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export default LabelAccumulator;
|