@ -1,5 +1,130 @@
let util = require ( '../../../../util' ) ;
/ * *
* 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 {
constructor ( measureText ) {
this . measureText = measureText ; // callback to determine text dimensions, using the parent label settings.
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 { boolean } mod id of multi - font to use , default '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 .
* /
curWidth ( ) {
let line = this . lines [ this . current ] ;
if ( line === undefined ) return 0 ;
return line . width ;
}
/ * *
* Add text in block to current line
* /
append ( text , mod = 'normal' ) {
this . _add ( this . current , text , mod ) ;
}
/ * *
* Add text in block to current line and start a new line
* /
newLine ( text , mod = 'normal' ) {
this . _add ( this . current , text , mod ) ;
this . current ++ ;
}
/ * *
* Set the sizes for all lines and the whole thing .
* /
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 ] ;
let height = 0 ;
for ( let l = 0 ; l < line . blocks . length ; l ++ ) {
let block = line . blocks [ l ] ;
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
}
}
}
class Label {
constructor ( body , options , edgelabel = false ) {
this . body = body ;
@ -15,7 +140,7 @@ class Label {
setOptions ( options , allowDeletion = false ) {
this . elementOptions = options ;
// We want to keep the font options sepe rated from the node options.
// We want to keep the font options sepa rated from the node options.
// The node options have to mirror the globals when they are not overruled.
this . fontOptions = util . deepExtend ( { } , options . font , true ) ;
@ -796,155 +921,210 @@ class Label {
differentState ( selected , hover ) {
return ( ( selected !== this . fontOptions . selectedState ) && ( hover !== this . fontOptions . hoverState ) ) ;
}
/ * *
* This explodes the label string into lines and sets the width , height and number of lines .
* This explodes the passed text into lines and determines the width , height and number of lines .
*
* @ param ctx
* @ param selected
* @ param hover
* @ param { String } text the text to explode
* @ private
* /
_processLabel ( ctx , selected , hover ) {
let width = 0 ;
let height = 0 ;
let nlLines = [ ] ;
let lines = [ ] ;
let k = 0 ;
lines . add = function ( l , text , font , color , width , height , vadjust , mod , strokeWidth , strokeColor ) {
if ( this . length == l ) {
this [ l ] = { width : 0 , height : 0 , blocks : [ ] } ;
}
this [ l ] . blocks . push ( { text , font , color , width , height , vadjust , mod , strokeWidth , strokeColor } ) ;
}
lines . accumulate = function ( l , width , height ) {
this [ l ] . width += width ;
this [ l ] . height = height > this [ l ] . height ? height : this [ l ] . height ;
}
lines . addAndAccumulate = function ( l , text , font , color , width , height , vadjust , mod , strokeWidth , strokeColor ) {
this . add ( l , text , font , color , width , height , vadjust , mod , strokeWidth , strokeColor ) ;
this . accumulate ( l , width , height ) ;
}
if ( this . elementOptions . label !== undefined ) {
let nlLines = String ( this . elementOptions . label ) . split ( '\n' ) ;
let lineCount = nlLines . length ;
if ( this . elementOptions . font . multi ) {
for ( let i = 0 ; i < lineCount ; i ++ ) {
let blocks = this . splitBlocks ( nlLines [ i ] , this . elementOptions . font . multi ) ;
let lineWidth = 0 ;
let lineHeight = 0 ;
if ( blocks ) {
if ( blocks . length == 0 ) {
let values = this . getFormattingValues ( ctx , selected , hover , "normal" ) ;
lines . addAndAccumulate ( k , "" , values . font , values . color , 0 , values . size , values . vadjust , "normal" , values . strokeWidth , values . strokeColor ) ;
height += lines [ k ] . height ;
k ++ ;
continue ;
}
for ( let j = 0 ; j < blocks . length ; j ++ ) {
if ( this . fontOptions . maxWdt > 0 ) {
let values = this . getFormattingValues ( ctx , selected , hover , blocks [ j ] . mod ) ;
let words = blocks [ j ] . text . split ( " " ) ;
let atStart = true
let text = "" ;
let measure = { width : 0 } ;
let lastMeasure ;
let w = 0 ;
while ( w < words . length ) {
let pre = atStart ? "" : " " ;
lastMeasure = measure ;
measure = ctx . measureText ( text + pre + words [ w ] ) ;
if ( ( lineWidth + measure . width > this . fontOptions . maxWdt ) &&
( lastMeasure . width != 0 ) ) {
lineHeight = ( values . height > lineHeight ) ? values . height : lineHeight ;
lines . add ( k , text , values . font , values . color , lastMeasure . width , values . height , values . vadjust , blocks [ j ] . mod , values . strokeWidth , values . strokeColor ) ;
lines . accumulate ( k , lastMeasure . width , lineHeight ) ;
text = "" ;
atStart = true ;
lineWidth = 0 ;
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
height += lines [ k ] . height ;
k ++ ;
} else {
text = text + pre + words [ w ] ;
if ( w === words . length - 1 ) {
lineHeight = ( values . height > lineHeight ) ? values . height : lineHeight ;
lineWidth += measure . width ;
lines . add ( k , text , values . font , values . color , measure . width , values . height , values . vadjust , blocks [ j ] . mod , values . strokeWidth , values . strokeColor ) ;
lines . accumulate ( k , measure . width , lineHeight ) ;
if ( j === blocks . length - 1 ) {
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
height += lines [ k ] . height ;
k ++ ;
}
}
w ++ ;
atStart = false ;
}
}
} else {
let values = this . getFormattingValues ( ctx , selected , hover , blocks [ j ] . mod ) ;
let measure = ctx . measureText ( blocks [ j ] . text ) ;
lines . addAndAccumulate ( k , blocks [ j ] . text , values . font , values . color , measure . width , values . height , values . vadjust , blocks [ j ] . mod , values . strokeWidth , values . strokeColor ) ;
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
if ( blocks . length - 1 === j ) {
height += lines [ k ] . height ;
k ++ ;
}
}
}
_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 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 th string which still fits in the
* current max width .
*
* @ param { Array } words Array of strings signifying a text lines
* @ return index of first item in string making string go over max
* /
let getLongestFitWord = function ( word ) {
let w = 0 ;
while ( w < word . length ) {
if ( overMaxWidth ( word . 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 ++ ) {
let values = this . getFormattingValues ( ctx , selected , hover , "normal" ) ;
if ( this . fontOptions . maxWdt > 0 ) {
let words = nlLines [ i ] . split ( " " ) ;
let text = "" ;
let measure = { width : 0 } ;
let lastMeasure ;
let w = 0 ;
while ( w < words . length ) {
let pre = ( text === "" ) ? "" : " " ;
lastMeasure = measure ;
measure = ctx . measureText ( text + pre + words [ w ] ) ;
if ( ( measure . width > this . fontOptions . maxWdt ) && ( lastMeasure . width != 0 ) ) {
lines . addAndAccumulate ( k , text , values . font , values . color , lastMeasure . width , values . size , values . vadjust , "normal" , values . strokeWidth , values . strokeColor )
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
height += lines [ k ] . height ;
text = "" ;
k ++ ;
} else {
text = text + pre + words [ w ] ;
if ( w === words . length - 1 ) {
lines . addAndAccumulate ( k , text , values . font , values . color , measure . width , values . size , values . vadjust , "normal" , values . strokeWidth , values . strokeColor )
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
height += lines [ k ] . height ;
k ++ ;
}
w ++ ;
}
}
} else {
let text = nlLines [ i ] ;
let measure = ctx . measureText ( text ) ;
lines . addAndAccumulate ( k , text , values . font , values . color , measure . width , values . size , values . vadjust , "normal" , values . strokeWidth , values . strokeColor ) ;
width = lines [ k ] . width > width ? lines [ k ] . width : width ;
height += lines [ k ] . height ;
k ++ ;
}
lines . newLine ( nlLines [ i ] ) ;
}
}
}
if ( ( this . fontOptions . minWdt > 0 ) && ( width < this . fontOptions . minWdt ) ) {
width = this . fontOptions . minWdt ;
return lines . finalize ( ) ;
}
/ * *
* This explodes the label string into lines and sets the width , height and number of lines .
* @ param ctx
* @ param selected
* @ param hover
* @ private
* /
_processLabel ( ctx , selected , hover ) {
let state = this . _processLabelText ( ctx , selected , hover , this . elementOptions . label ) ;
if ( ( this . fontOptions . minWdt > 0 ) && ( state . width < this . fontOptions . minWdt ) ) {
state . width = this . fontOptions . minWdt ;
}
this . size . labelHeight = height ;
if ( ( this . fontOptions . minHgt > 0 ) && ( height < this . fontOptions . minHgt ) ) {
height = this . fontOptions . minHgt ;
this . size . labelHeight = state . height ;
if ( ( this . fontOptions . minHgt > 0 ) && ( state . height < this . fontOptions . minHgt ) ) {
state . height = this . fontOptions . minHgt ;
}
this . lines = lines ;
this . lineCount = lines . length ;
this . size . width = width ;
this . size . height = height ;
this . lines = state . lines ;
this . lineCount = state . lines . length ;
this . size . width = state . width ;
this . size . height = state . height ;
this . selectedState = selected ;
this . hoverState = hover ;
}