import * as util from '../../../util'; import * as math from '../../../math'; let CRp = {}; CRp.eleTextBiggerThanMin = function( ele, scale ){ if( !scale ){ let zoom = ele.cy().zoom(); let pxRatio = this.getPixelRatio(); let lvl = Math.ceil( math.log2( zoom * pxRatio ) ); // the effective texture level scale = Math.pow( 2, lvl ); } let computedSize = ele.pstyle( 'font-size' ).pfValue * scale; let minSize = ele.pstyle( 'min-zoomed-font-size' ).pfValue; if( computedSize < minSize ){ return false; } return true; }; CRp.drawElementText = function( context, ele, shiftToOriginWithBb, force, prefix, useEleOpacity = true ){ let r = this; if( force == null ){ if( useEleOpacity && !r.eleTextBiggerThanMin( ele ) ){ return; } } else if( force === false ){ return; } if( ele.isNode() ){ let label = ele.pstyle( 'label' ); if( !label || !label.value ){ return; } let justification = r.getLabelJustification(ele); context.textAlign = justification; context.textBaseline = 'bottom'; } else { let badLine = ele.element()._private.rscratch.badLine; let label = ele.pstyle( 'label' ); let srcLabel = ele.pstyle( 'source-label' ); let tgtLabel = ele.pstyle( 'target-label' ); if( badLine || ( ( !label || !label.value ) && ( !srcLabel || !srcLabel.value ) && ( !tgtLabel || !tgtLabel.value ) ) ){ return; } context.textAlign = 'center'; context.textBaseline = 'bottom'; } let applyRotation = !shiftToOriginWithBb; let bb; if( shiftToOriginWithBb ){ bb = shiftToOriginWithBb; context.translate( -bb.x1, -bb.y1 ); } if( prefix == null ){ r.drawText( context, ele, null, applyRotation, useEleOpacity ); if( ele.isEdge() ){ r.drawText( context, ele, 'source', applyRotation, useEleOpacity ); r.drawText( context, ele, 'target', applyRotation, useEleOpacity ); } } else { r.drawText( context, ele, prefix, applyRotation, useEleOpacity ); } if( shiftToOriginWithBb ){ context.translate( bb.x1, bb.y1 ); } }; CRp.getFontCache = function( context ){ let cache; this.fontCaches = this.fontCaches || []; for( let i = 0; i < this.fontCaches.length; i++ ){ cache = this.fontCaches[ i ]; if( cache.context === context ){ return cache; } } cache = { context: context }; this.fontCaches.push( cache ); return cache; }; // set up canvas context with font // returns transformed text string CRp.setupTextStyle = function( context, ele, useEleOpacity = true ){ // Font style let labelStyle = ele.pstyle( 'font-style' ).strValue; let labelSize = ele.pstyle( 'font-size' ).pfValue + 'px'; let labelFamily = ele.pstyle( 'font-family' ).strValue; let labelWeight = ele.pstyle( 'font-weight' ).strValue; let opacity = (useEleOpacity ? ele.effectiveOpacity() * ele.pstyle('text-opacity').value : 1); let outlineOpacity = ele.pstyle( 'text-outline-opacity' ).value * opacity; let color = ele.pstyle( 'color' ).value; let outlineColor = ele.pstyle( 'text-outline-color' ).value; context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; context.lineJoin = 'round'; // so text outlines aren't jagged this.colorFillStyle( context, color[ 0 ], color[ 1 ], color[ 2 ], opacity ); this.colorStrokeStyle( context, outlineColor[ 0 ], outlineColor[ 1 ], outlineColor[ 2 ], outlineOpacity ); }; // TODO ensure re-used function roundRect( ctx, x, y, width, height, radius = 5, stroke){ ctx.beginPath(); ctx.moveTo( x + radius, y ); ctx.lineTo( x + width - radius, y ); ctx.quadraticCurveTo( x + width, y, x + width, y + radius ); ctx.lineTo( x + width, y + height - radius ); ctx.quadraticCurveTo( x + width, y + height, x + width - radius, y + height ); ctx.lineTo( x + radius, y + height ); ctx.quadraticCurveTo( x, y + height, x, y + height - radius ); ctx.lineTo( x, y + radius ); ctx.quadraticCurveTo( x, y, x + radius, y ); ctx.closePath(); if(stroke) ctx.stroke(); else ctx.fill(); } CRp.getTextAngle = function( ele, prefix ){ let theta; let _p = ele._private; let rscratch = _p.rscratch; let pdash = prefix ? prefix + '-' : ''; let rotation = ele.pstyle( pdash + 'text-rotation' ); let textAngle = util.getPrefixedProperty( rscratch, 'labelAngle', prefix ); if( rotation.strValue === 'autorotate' ){ theta = ele.isEdge() ? textAngle : 0; } else if( rotation.strValue === 'none' ){ theta = 0; } else { theta = rotation.pfValue; } return theta; }; CRp.drawText = function( context, ele, prefix, applyRotation = true, useEleOpacity = true ){ let _p = ele._private; let rscratch = _p.rscratch; let parentOpacity = useEleOpacity ? ele.effectiveOpacity() : 1; if( useEleOpacity && (parentOpacity === 0 || ele.pstyle( 'text-opacity' ).value === 0) ){ return; } // use 'main' as an alias for the main label (i.e. null prefix) if( prefix === 'main' ){ prefix = null; } let textX = util.getPrefixedProperty( rscratch, 'labelX', prefix ); let textY = util.getPrefixedProperty( rscratch, 'labelY', prefix ); let orgTextX, orgTextY; // used for rotation let text = this.getLabelText( ele, prefix ); if( text != null && text !== '' && !isNaN( textX ) && !isNaN( textY ) ){ this.setupTextStyle( context, ele, useEleOpacity ); let pdash = prefix ? prefix + '-' : ''; let textW = util.getPrefixedProperty( rscratch, 'labelWidth', prefix ); let textH = util.getPrefixedProperty( rscratch, 'labelHeight', prefix ); let marginX = ele.pstyle( pdash + 'text-margin-x' ).pfValue; let marginY = ele.pstyle( pdash + 'text-margin-y' ).pfValue; let isEdge = ele.isEdge(); let halign = ele.pstyle( 'text-halign' ).value; let valign = ele.pstyle( 'text-valign' ).value; if( isEdge ){ halign = 'center'; valign = 'center'; } textX += marginX; textY += marginY; let theta; if( !applyRotation ){ theta = 0; } else { theta = this.getTextAngle(ele, prefix); } if( theta !== 0 ){ orgTextX = textX; orgTextY = textY; context.translate( orgTextX, orgTextY ); context.rotate( theta ); textX = 0; textY = 0; } switch( valign ){ case 'top': break; case 'center': textY += textH / 2; break; case 'bottom': textY += textH; break; } let backgroundOpacity = ele.pstyle( 'text-background-opacity' ).value; let borderOpacity = ele.pstyle( 'text-border-opacity' ).value; let textBorderWidth = ele.pstyle( 'text-border-width' ).pfValue; let backgroundPadding = ele.pstyle( 'text-background-padding' ).pfValue; let styleShape = ele.pstyle( 'text-background-shape' ).strValue; let rounded = styleShape.indexOf('round') === 0; let roundRadius = 2; if( backgroundOpacity > 0 || ( textBorderWidth > 0 && borderOpacity > 0 ) ){ let bgX = textX - backgroundPadding; switch( halign ){ case 'left': bgX -= textW; break; case 'center': bgX -= textW / 2; break; case 'right': break; } let bgY = textY - textH - backgroundPadding; let bgW = textW + 2*backgroundPadding; let bgH = textH + 2*backgroundPadding; if( backgroundOpacity > 0 ){ let textFill = context.fillStyle; let textBackgroundColor = ele.pstyle( 'text-background-color' ).value; context.fillStyle = 'rgba(' + textBackgroundColor[ 0 ] + ',' + textBackgroundColor[ 1 ] + ',' + textBackgroundColor[ 2 ] + ',' + backgroundOpacity * parentOpacity + ')'; if( rounded ){ roundRect( context, bgX, bgY, bgW, bgH, roundRadius ); } else { context.fillRect( bgX, bgY, bgW, bgH ); } context.fillStyle = textFill; } if( textBorderWidth > 0 && borderOpacity > 0 ){ let textStroke = context.strokeStyle; let textLineWidth = context.lineWidth; let textBorderColor = ele.pstyle( 'text-border-color' ).value; let textBorderStyle = ele.pstyle( 'text-border-style' ).value; context.strokeStyle = 'rgba(' + textBorderColor[ 0 ] + ',' + textBorderColor[ 1 ] + ',' + textBorderColor[ 2 ] + ',' + borderOpacity * parentOpacity + ')'; context.lineWidth = textBorderWidth; if( context.setLineDash ){ // for very outofdate browsers switch( textBorderStyle ){ case 'dotted': context.setLineDash( [ 1, 1 ] ); break; case 'dashed': context.setLineDash( [ 4, 2 ] ); break; case 'double': context.lineWidth = textBorderWidth / 4; // 50% reserved for white between the two borders context.setLineDash( [] ); break; case 'solid': context.setLineDash( [] ); break; } } if( rounded ){ roundRect( context, bgX, bgY, bgW, bgH, roundRadius, 'stroke' ); } else { context.strokeRect( bgX, bgY, bgW, bgH ); } if( textBorderStyle === 'double' ){ let whiteWidth = textBorderWidth / 2; if( rounded ){ roundRect( context, bgX + whiteWidth, bgY + whiteWidth, bgW - whiteWidth * 2, bgH - whiteWidth * 2, roundRadius, 'stroke' ); } else { context.strokeRect( bgX + whiteWidth, bgY + whiteWidth, bgW - whiteWidth * 2, bgH - whiteWidth * 2 ); } } if( context.setLineDash ){ // for very outofdate browsers context.setLineDash( [] ); } context.lineWidth = textLineWidth; context.strokeStyle = textStroke; } } let lineWidth = 2 * ele.pstyle( 'text-outline-width' ).pfValue; // *2 b/c the stroke is drawn centred on the middle if( lineWidth > 0 ){ context.lineWidth = lineWidth; } if( ele.pstyle( 'text-wrap' ).value === 'wrap' ){ let lines = util.getPrefixedProperty( rscratch, 'labelWrapCachedLines', prefix ); let lineHeight = util.getPrefixedProperty( rscratch, 'labelLineHeight', prefix ); let halfTextW = textW/2; let justification = this.getLabelJustification(ele); if( justification === 'auto' ){ // then it's already ok, so skip all the other ifs } else if( halign === 'left' ){ // auto justification : right if( justification === 'left' ){ textX += -textW; } else if( justification === 'center' ){ textX += -halfTextW; } // else same as auto } else if( halign === 'center' ){ // auto justfication : center if( justification === 'left' ){ textX += -halfTextW; } else if( justification === 'right' ){ textX += halfTextW; } // else same as auto } else if( halign === 'right' ){ // auto justification : left if( justification === 'center' ){ textX += halfTextW; } else if( justification === 'right' ){ textX += textW; } // else same as auto } switch( valign ){ case 'top': textY -= ( lines.length - 1 ) * lineHeight; break; case 'center': case 'bottom': textY -= ( lines.length - 1 ) * lineHeight; break; } for( let l = 0; l < lines.length; l++ ){ if( lineWidth > 0 ){ context.strokeText( lines[ l ], textX, textY ); } context.fillText( lines[ l ], textX, textY ); textY += lineHeight; } } else { if( lineWidth > 0 ){ context.strokeText( text, textX, textY ); } context.fillText( text, textX, textY ); } if( theta !== 0 ){ context.rotate( -theta ); context.translate( -orgTextX, -orgTextY ); } } }; export default CRp;