|
|
- /**
- * 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;
|