|
@ -1,154 +1,8 @@ |
|
|
let util = require('../../../../util'); |
|
|
let util = require('../../../../util'); |
|
|
let ComponentUtil = require('./ComponentUtil').default; |
|
|
let ComponentUtil = require('./ComponentUtil').default; |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Callback to determine text dimensions, using the parent label settings. |
|
|
|
|
|
* @callback MeasureText |
|
|
|
|
|
* @param {text} text |
|
|
|
|
|
* @returns {number} |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
let LabelSplitter = require('./LabelSplitter').default; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Internal helper class used for splitting a label text into lines. |
|
|
|
|
|
* |
|
|
|
|
|
* This has been moved away from the label processing code for better undestanding upon reading. |
|
|
|
|
|
* |
|
|
|
|
|
* @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 (text === undefined || text === "") return; |
|
|
|
|
|
|
|
|
|
|
|
if (this.lines[l] === undefined) { |
|
|
|
|
|
this.lines[l] = { |
|
|
|
|
|
width : 0, |
|
|
|
|
|
height: 0, |
|
|
|
|
|
blocks: [] |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Determine width and get the font properties
|
|
|
|
|
|
let result = this.measureText(text, mod); |
|
|
|
|
|
let block = Object.assign({}, result.values); |
|
|
|
|
|
block.text = text; |
|
|
|
|
|
block.width = result.width; |
|
|
|
|
|
block.mod = mod; |
|
|
|
|
|
|
|
|
|
|
|
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 += result.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++; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 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));
|
|
|
|
|
|
|
|
|
|
|
|
// Determine the heights of the lines
|
|
|
|
|
|
// Note that width has already been set
|
|
|
|
|
|
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; |
|
|
|
|
|
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 label size
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
// Return a simple hash object for further processing.
|
|
|
|
|
|
return { |
|
|
|
|
|
width : this.width, |
|
|
|
|
|
height: this.height, |
|
|
|
|
|
lines : this.lines |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* A Label to be used for Nodes or Edges. |
|
|
* A Label to be used for Nodes or Edges. |
|
|
*/ |
|
|
*/ |
|
@ -160,7 +14,6 @@ class Label { |
|
|
*/ |
|
|
*/ |
|
|
constructor(body, options, edgelabel = false) { |
|
|
constructor(body, options, edgelabel = false) { |
|
|
this.body = body; |
|
|
this.body = body; |
|
|
|
|
|
|
|
|
this.pointToSelf = false; |
|
|
this.pointToSelf = false; |
|
|
this.baseSize = undefined; |
|
|
this.baseSize = undefined; |
|
|
this.fontOptions = {}; |
|
|
this.fontOptions = {}; |
|
@ -169,6 +22,7 @@ class Label { |
|
|
this.isEdgeLabel = edgelabel; |
|
|
this.isEdgeLabel = edgelabel; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* |
|
|
* |
|
|
* @param {Object} options |
|
|
* @param {Object} options |
|
@ -198,6 +52,7 @@ class Label { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* |
|
|
* |
|
|
* @param {Object} parentOptions |
|
|
* @param {Object} parentOptions |
|
@ -486,12 +341,11 @@ class Label { |
|
|
// update the size cache if required
|
|
|
// update the size cache if required
|
|
|
this.calculateLabelSize(ctx, selected, hover, x, y, baseline); |
|
|
this.calculateLabelSize(ctx, selected, hover, x, y, baseline); |
|
|
|
|
|
|
|
|
// create the fontfill background
|
|
|
|
|
|
this._drawBackground(ctx); |
|
|
|
|
|
// draw text
|
|
|
|
|
|
|
|
|
this._drawBackground(ctx); // create the fontfill background
|
|
|
this._drawText(ctx, selected, hover, x, y, baseline); |
|
|
this._drawText(ctx, selected, hover, x, y, baseline); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Draws the label background |
|
|
* Draws the label background |
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
* @param {CanvasRenderingContext2D} ctx |
|
@ -538,7 +392,9 @@ class Label { |
|
|
_drawText(ctx, selected, hover, x, y, baseline = 'middle') { |
|
|
_drawText(ctx, selected, hover, x, y, baseline = 'middle') { |
|
|
let fontSize = this.fontOptions.size; |
|
|
let fontSize = this.fontOptions.size; |
|
|
let viewFontSize = fontSize * this.body.view.scale; |
|
|
let viewFontSize = fontSize * this.body.view.scale; |
|
|
// this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This ensures that there will not be HUGE letters on screen
|
|
|
|
|
|
// by setting an upper limit on the visible text size (regardless of zoomLevel)
|
|
|
if (viewFontSize >= this.elementOptions.scaling.label.maxVisible) { |
|
|
if (viewFontSize >= this.elementOptions.scaling.label.maxVisible) { |
|
|
// TODO: Does this actually do anything?
|
|
|
// TODO: Does this actually do anything?
|
|
|
fontSize = Number(this.elementOptions.scaling.label.maxVisible) / this.body.view.scale; |
|
|
fontSize = Number(this.elementOptions.scaling.label.maxVisible) / this.body.view.scale; |
|
@ -686,281 +542,6 @@ class Label { |
|
|
this.labelDirty = false; |
|
|
this.labelDirty = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* normalize the markup system |
|
|
|
|
|
* |
|
|
|
|
|
* @param {boolean|'md'|'markdown'|'html'} markupSystem |
|
|
|
|
|
* @returns {string} |
|
|
|
|
|
*/ |
|
|
|
|
|
decodeMarkupSystem(markupSystem) { |
|
|
|
|
|
let system = 'none'; |
|
|
|
|
|
if (markupSystem === 'markdown' || markupSystem === 'md') { |
|
|
|
|
|
system = 'markdown'; |
|
|
|
|
|
} else if (markupSystem === true || markupSystem === 'html') { |
|
|
|
|
|
system = 'html' |
|
|
|
|
|
} |
|
|
|
|
|
return system; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Explodes a piece of text into single-font blocks using a given markup |
|
|
|
|
|
* @param {string} text |
|
|
|
|
|
* @param {boolean|'md'|'markdown'|'html'} markupSystem |
|
|
|
|
|
* @returns {Array.<{text: string, mod: string}>} |
|
|
|
|
|
*/ |
|
|
|
|
|
splitBlocks(text, markupSystem) { |
|
|
|
|
|
let system = this.decodeMarkupSystem(markupSystem); |
|
|
|
|
|
if (system === 'none') { |
|
|
|
|
|
return [{ |
|
|
|
|
|
text: text, |
|
|
|
|
|
mod: 'normal' |
|
|
|
|
|
}] |
|
|
|
|
|
} else if (system === 'markdown') { |
|
|
|
|
|
return this.splitMarkdownBlocks(text); |
|
|
|
|
|
} else if (system === 'html') { |
|
|
|
|
|
return this.splitHtmlBlocks(text); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* |
|
|
|
|
|
* @param {string} text |
|
|
|
|
|
* @returns {Array} |
|
|
|
|
|
*/ |
|
|
|
|
|
splitMarkdownBlocks(text) { |
|
|
|
|
|
let blocks = []; |
|
|
|
|
|
let s = { |
|
|
|
|
|
bold: false, |
|
|
|
|
|
ital: false, |
|
|
|
|
|
mono: false, |
|
|
|
|
|
beginable: true, |
|
|
|
|
|
spacing: false, |
|
|
|
|
|
position: 0, |
|
|
|
|
|
buffer: "", |
|
|
|
|
|
modStack: [] |
|
|
|
|
|
}; |
|
|
|
|
|
s.mod = function() { |
|
|
|
|
|
return (this.modStack.length === 0) ? 'normal' : this.modStack[0]; |
|
|
|
|
|
}; |
|
|
|
|
|
s.modName = function() { |
|
|
|
|
|
if (this.modStack.length === 0) |
|
|
|
|
|
return 'normal'; |
|
|
|
|
|
else if (this.modStack[0] === 'mono') |
|
|
|
|
|
return 'mono'; |
|
|
|
|
|
else { |
|
|
|
|
|
if (s.bold && s.ital) { |
|
|
|
|
|
return 'boldital'; |
|
|
|
|
|
} else if (s.bold) { |
|
|
|
|
|
return 'bold'; |
|
|
|
|
|
} else if (s.ital) { |
|
|
|
|
|
return 'ital'; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
|
|
|
|
|
|
if (this.spacing) { |
|
|
|
|
|
this.add(" "); |
|
|
|
|
|
this.spacing = false; |
|
|
|
|
|
} |
|
|
|
|
|
if (this.buffer.length > 0) { |
|
|
|
|
|
blocks.push({ text: this.buffer, mod: this.modName() }); |
|
|
|
|
|
this.buffer = ""; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
s.add = function(text) { |
|
|
|
|
|
if (text === " ") { |
|
|
|
|
|
s.spacing = true; |
|
|
|
|
|
} |
|
|
|
|
|
if (s.spacing) { |
|
|
|
|
|
this.buffer += " "; |
|
|
|
|
|
this.spacing = false; |
|
|
|
|
|
} |
|
|
|
|
|
if (text != " ") { |
|
|
|
|
|
this.buffer += text; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
while (s.position < text.length) { |
|
|
|
|
|
let ch = text.charAt(s.position); |
|
|
|
|
|
if (/[ \t]/.test(ch)) { |
|
|
|
|
|
if (!s.mono) { |
|
|
|
|
|
s.spacing = true; |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
s.beginable = true |
|
|
|
|
|
} else if (/\\/.test(ch)) { |
|
|
|
|
|
if (s.position < text.length+1) { |
|
|
|
|
|
s.position++; |
|
|
|
|
|
ch = text.charAt(s.position); |
|
|
|
|
|
if (/ \t/.test(ch)) { |
|
|
|
|
|
s.spacing = true; |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
s.beginable = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else if (!s.mono && !s.bold && (s.beginable || s.spacing) && /\*/.test(ch)) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.bold = true; |
|
|
|
|
|
s.modStack.unshift("bold"); |
|
|
|
|
|
} else if (!s.mono && !s.ital && (s.beginable || s.spacing) && /\_/.test(ch)) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.ital = true; |
|
|
|
|
|
s.modStack.unshift("ital"); |
|
|
|
|
|
} else if (!s.mono && (s.beginable || s.spacing) && /`/.test(ch)) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.mono = true; |
|
|
|
|
|
s.modStack.unshift("mono"); |
|
|
|
|
|
} else if (!s.mono && (s.mod() === "bold") && /\*/.test(ch)) { |
|
|
|
|
|
if ((s.position === text.length-1) || /[.,_` \t\n]/.test(text.charAt(s.position+1))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.bold = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (!s.mono && (s.mod() === "ital") && /\_/.test(ch)) { |
|
|
|
|
|
if ((s.position === text.length-1) || /[.,*` \t\n]/.test(text.charAt(s.position+1))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.ital = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (s.mono && (s.mod() === "mono") && /`/.test(ch)) { |
|
|
|
|
|
if ((s.position === text.length-1) || (/[.,*_ \t\n]/.test(text.charAt(s.position+1)))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.mono = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
s.beginable = false; |
|
|
|
|
|
} |
|
|
|
|
|
s.position++ |
|
|
|
|
|
} |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
return blocks; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* |
|
|
|
|
|
* @param {string} text |
|
|
|
|
|
* @returns {Array} |
|
|
|
|
|
*/ |
|
|
|
|
|
splitHtmlBlocks(text) { |
|
|
|
|
|
let blocks = []; |
|
|
|
|
|
let s = { |
|
|
|
|
|
bold: false, |
|
|
|
|
|
ital: false, |
|
|
|
|
|
mono: false, |
|
|
|
|
|
spacing: false, |
|
|
|
|
|
position: 0, |
|
|
|
|
|
buffer: "", |
|
|
|
|
|
modStack: [] |
|
|
|
|
|
}; |
|
|
|
|
|
s.mod = function() { |
|
|
|
|
|
return (this.modStack.length === 0) ? 'normal' : this.modStack[0]; |
|
|
|
|
|
}; |
|
|
|
|
|
s.modName = function() { |
|
|
|
|
|
if (this.modStack.length === 0) |
|
|
|
|
|
return 'normal'; |
|
|
|
|
|
else if (this.modStack[0] === 'mono') |
|
|
|
|
|
return 'mono'; |
|
|
|
|
|
else { |
|
|
|
|
|
if (s.bold && s.ital) { |
|
|
|
|
|
return 'boldital'; |
|
|
|
|
|
} else if (s.bold) { |
|
|
|
|
|
return 'bold'; |
|
|
|
|
|
} else if (s.ital) { |
|
|
|
|
|
return 'ital'; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
s.emitBlock = function(override=false) { // eslint-disable-line no-unused-vars
|
|
|
|
|
|
if (this.spacing) { |
|
|
|
|
|
this.add(" "); |
|
|
|
|
|
this.spacing = false; |
|
|
|
|
|
} |
|
|
|
|
|
if (this.buffer.length > 0) { |
|
|
|
|
|
blocks.push({ text: this.buffer, mod: this.modName() }); |
|
|
|
|
|
this.buffer = ""; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
s.add = function(text) { |
|
|
|
|
|
if (text === " ") { |
|
|
|
|
|
s.spacing = true; |
|
|
|
|
|
} |
|
|
|
|
|
if (s.spacing) { |
|
|
|
|
|
this.buffer += " "; |
|
|
|
|
|
this.spacing = false; |
|
|
|
|
|
} |
|
|
|
|
|
if (text != " ") { |
|
|
|
|
|
this.buffer += text; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
while (s.position < text.length) { |
|
|
|
|
|
let ch = text.charAt(s.position); |
|
|
|
|
|
if (/[ \t]/.test(ch)) { |
|
|
|
|
|
if (!s.mono) { |
|
|
|
|
|
s.spacing = true; |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (/</.test(ch)) { |
|
|
|
|
|
if (!s.mono && !s.bold && /<b>/.test(text.substr(s.position,3))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.bold = true; |
|
|
|
|
|
s.modStack.unshift("bold"); |
|
|
|
|
|
s.position += 2; |
|
|
|
|
|
} else if (!s.mono && !s.ital && /<i>/.test(text.substr(s.position,3))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.ital = true; |
|
|
|
|
|
s.modStack.unshift("ital"); |
|
|
|
|
|
s.position += 2; |
|
|
|
|
|
} else if (!s.mono && /<code>/.test(text.substr(s.position,6))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.mono = true; |
|
|
|
|
|
s.modStack.unshift("mono"); |
|
|
|
|
|
s.position += 5; |
|
|
|
|
|
} else if (!s.mono && (s.mod() === 'bold') && /<\/b>/.test(text.substr(s.position,4))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.bold = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
s.position += 3; |
|
|
|
|
|
} else if (!s.mono && (s.mod() === 'ital') && /<\/i>/.test(text.substr(s.position,4))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.ital = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
s.position += 3; |
|
|
|
|
|
} else if ((s.mod() === 'mono') && /<\/code>/.test(text.substr(s.position,7))) { |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
s.mono = false; |
|
|
|
|
|
s.modStack.shift(); |
|
|
|
|
|
s.position += 6; |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (/&/.test(ch)) { |
|
|
|
|
|
if (/</.test(text.substr(s.position,4))) { |
|
|
|
|
|
s.add("<"); |
|
|
|
|
|
s.position += 3; |
|
|
|
|
|
} else if (/&/.test(text.substr(s.position,5))) { |
|
|
|
|
|
s.add("&"); |
|
|
|
|
|
s.position += 4; |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add("&"); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
s.add(ch); |
|
|
|
|
|
} |
|
|
|
|
|
s.position++ |
|
|
|
|
|
} |
|
|
|
|
|
s.emitBlock(); |
|
|
|
|
|
return blocks; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* |
|
|
* |
|
@ -1027,177 +608,13 @@ class Label { |
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
* @param {boolean} selected |
|
|
* @param {boolean} selected |
|
|
* @param {boolean} hover |
|
|
* @param {boolean} hover |
|
|
* @param {string} text the text to explode |
|
|
|
|
|
|
|
|
* @param {string} inText the text to explode |
|
|
* @returns {{width, height, lines}|*} |
|
|
* @returns {{width, height, lines}|*} |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
_processLabelText(ctx, selected, hover, text) { |
|
|
|
|
|
let self = this; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Callback to determine text width; passed to LabelAccumulator instance |
|
|
|
|
|
* |
|
|
|
|
|
* @param {String} text string to determine width of |
|
|
|
|
|
* @param {String} mod font type to use for this text |
|
|
|
|
|
* @return {Object} { width, values} width in pixels and font attributes |
|
|
|
|
|
*/ |
|
|
|
|
|
let textWidth = function(text, mod) { |
|
|
|
|
|
if (text === undefined) return 0; |
|
|
|
|
|
|
|
|
|
|
|
// TODO: This can be done more efficiently with caching
|
|
|
|
|
|
let values = self.getFormattingValues(ctx, selected, hover, mod); |
|
|
|
|
|
|
|
|
|
|
|
let width = 0; |
|
|
|
|
|
if (text !== '') { |
|
|
|
|
|
// NOTE: The following may actually be *incorrect* for the mod fonts!
|
|
|
|
|
|
// This returns the size with a regular font, bold etc. may
|
|
|
|
|
|
// have different sizes.
|
|
|
|
|
|
let measure = ctx.measureText(text); |
|
|
|
|
|
width = measure.width; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return {width, values: values}; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let lines = new LabelAccumulator(textWidth); |
|
|
|
|
|
|
|
|
|
|
|
if (text === undefined || text === "") { |
|
|
|
|
|
return lines.finalize(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let overMaxWidth = function(text) { |
|
|
|
|
|
let width = ctx.measureText(text).width; |
|
|
|
|
|
return (lines.curWidth() + width > self.fontOptions.maxWdt); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Determine the longest part of the sentence which still fits in the |
|
|
|
|
|
* current max width. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Array} words Array of strings signifying a text lines |
|
|
|
|
|
* @return {number} index of first item in string making string go over max |
|
|
|
|
|
*/ |
|
|
|
|
|
let getLongestFit = function(words) { |
|
|
|
|
|
let text = ''; |
|
|
|
|
|
let w = 0; |
|
|
|
|
|
|
|
|
|
|
|
while (w < words.length) { |
|
|
|
|
|
let pre = (text === '') ? '' : ' '; |
|
|
|
|
|
let newText = text + pre + words[w]; |
|
|
|
|
|
|
|
|
|
|
|
if (overMaxWidth(newText)) break; |
|
|
|
|
|
text = newText; |
|
|
|
|
|
w++; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return w; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Determine the longest part of the string which still fits in the |
|
|
|
|
|
* current max width. |
|
|
|
|
|
* |
|
|
|
|
|
* @param {Array} words Array of strings signifying a text lines |
|
|
|
|
|
* @return {number} index of first item in string making string go over max |
|
|
|
|
|
*/ |
|
|
|
|
|
let getLongestFitWord = function(words) { |
|
|
|
|
|
let w = 0; |
|
|
|
|
|
|
|
|
|
|
|
while (w < words.length) { |
|
|
|
|
|
if (overMaxWidth(words.slice(0,w))) break; |
|
|
|
|
|
w++; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return w; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let splitStringIntoLines = function(str, mod = 'normal', appendLast = false) { |
|
|
|
|
|
let words = str.split(" "); |
|
|
|
|
|
|
|
|
|
|
|
while (words.length > 0) { |
|
|
|
|
|
let w = getLongestFit(words); |
|
|
|
|
|
|
|
|
|
|
|
if (w === 0) { |
|
|
|
|
|
// Special case: the first word may already
|
|
|
|
|
|
// be larger than the max width.
|
|
|
|
|
|
let word = words[0]; |
|
|
|
|
|
|
|
|
|
|
|
// Break the word to the largest part that fits the line
|
|
|
|
|
|
let x = getLongestFitWord(word); |
|
|
|
|
|
lines.newLine(word.slice(0, x), mod); |
|
|
|
|
|
|
|
|
|
|
|
// Adjust the word, so that the rest will be done next iteration
|
|
|
|
|
|
words[0] = word.slice(x); |
|
|
|
|
|
} else { |
|
|
|
|
|
let text = words.slice(0, w).join(" "); |
|
|
|
|
|
|
|
|
|
|
|
if (w == words.length && appendLast) { |
|
|
|
|
|
lines.append(text, mod); |
|
|
|
|
|
} else { |
|
|
|
|
|
lines.newLine(text, mod); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
words = words.slice(w); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let nlLines = String(text).split('\n'); |
|
|
|
|
|
let lineCount = nlLines.length; |
|
|
|
|
|
|
|
|
|
|
|
if (this.elementOptions.font.multi) { |
|
|
|
|
|
// Multi-font case: styling tags active
|
|
|
|
|
|
for (let i = 0; i < lineCount; i++) { |
|
|
|
|
|
let blocks = this.splitBlocks(nlLines[i], this.elementOptions.font.multi); |
|
|
|
|
|
if (blocks === undefined) continue; |
|
|
|
|
|
|
|
|
|
|
|
if (blocks.length === 0) { |
|
|
|
|
|
lines.newLine(""); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (this.fontOptions.maxWdt > 0) { |
|
|
|
|
|
// widthConstraint.maximum defined
|
|
|
|
|
|
//console.log('Running widthConstraint multi, max: ' + this.fontOptions.maxWdt);
|
|
|
|
|
|
for (let j = 0; j < blocks.length; j++) { |
|
|
|
|
|
let mod = blocks[j].mod; |
|
|
|
|
|
let text = blocks[j].text; |
|
|
|
|
|
splitStringIntoLines(text, mod, true); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// widthConstraint.maximum NOT defined
|
|
|
|
|
|
for (let j = 0; j < blocks.length; j++) { |
|
|
|
|
|
let mod = blocks[j].mod; |
|
|
|
|
|
let text = blocks[j].text; |
|
|
|
|
|
lines.append(text, mod); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
lines.newLine(); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// Single-font case
|
|
|
|
|
|
if (this.fontOptions.maxWdt > 0) { |
|
|
|
|
|
// widthConstraint.maximum defined
|
|
|
|
|
|
// console.log('Running widthConstraint normal, max: ' + this.fontOptions.maxWdt);
|
|
|
|
|
|
for (let i = 0; i < lineCount; i++) { |
|
|
|
|
|
splitStringIntoLines(nlLines[i]); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// widthConstraint.maximum NOT defined
|
|
|
|
|
|
for (let i = 0; i < lineCount; i++) { |
|
|
|
|
|
lines.newLine(nlLines[i]); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return lines.finalize(); |
|
|
|
|
|
|
|
|
_processLabelText(ctx, selected, hover, inText) { |
|
|
|
|
|
let splitter = new LabelSplitter(ctx, this, selected, hover); |
|
|
|
|
|
return splitter.process(inText); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|