Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5590,6 +5590,20 @@ declare namespace cytoscape {
* height of a line of text.
*/
"line-height": PropertyValue<SingularType, number>;
/**
* Horizontal and vertical margin of error applied to a node's bounding box.
* These margins help compensate for layout inaccuracies or rendering quirks.
* Values are in pixels.
*/
"bbox-margin-error-x": PropertyValue<SingularType, number>;
"bbox-margin-error-y": PropertyValue<SingularType, number>;
/**
* The vertical alignment of text relative to its baseline. This affects how text
* dimensions are calculated and displayed.
* - `default`: Calculate using font size.
* - `actual`: Calculate using actual label text metrics (for ascending/descending character size).
*/
"text-metrics": PropertyValue<SingularType, "default" | "actual">;

/**
* Node label alignment:
Expand Down
11 changes: 6 additions & 5 deletions src/collection/dimensions/bounds.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ let updateBoundsFromLabel = function( bounds, ele, prefix ){
let borderWidth = ele.pstyle( 'text-border-width' ).pfValue;
let halfBorderWidth = borderWidth / 2;
let padding = ele.pstyle( 'text-background-padding' ).pfValue;
let marginOfError = 2; // expand to work around browser dimension inaccuracies
let marginOfErrorX = ele.pstyle( 'bbox-margin-error-x' ).pfValue; // expand to work around browser dimension inaccuracies
let marginOfErrorY = ele.pstyle( 'bbox-margin-error-y' ).pfValue; // expand to work around browser dimension inaccuracies

let lh = labelHeight;
let lw = labelWidth;
Expand Down Expand Up @@ -341,10 +342,10 @@ let updateBoundsFromLabel = function( bounds, ele, prefix ){
}

// shift by margin and expand by outline and border
let leftPad = marginX - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfError;
let rightPad = marginX + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfError;
let topPad = marginY - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfError;
let botPad = marginY + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfError;
let leftPad = marginX - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfErrorX;
let rightPad = marginX + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfErrorX;
let topPad = marginY - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfErrorY;
let botPad = marginY + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfErrorY;

lx1 += leftPad;
lx2 += rightPad;
Expand Down
29 changes: 23 additions & 6 deletions src/extensions/renderer/base/coord-ele-math/labels.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,14 @@ BRp.applyPrefixedLabelDimensions = function( ele, prefix ){

let labelDims = this.calculateLabelDimensions( ele, text );
let lineHeight = ele.pstyle('line-height').pfValue;
let size = ele.pstyle('font-size').pfValue;
let textWrap = ele.pstyle('text-wrap').strValue;
let lines = util.getPrefixedProperty( _p.rscratch, 'labelWrapCachedLines', prefix ) || [];
let numLines = textWrap !== 'wrap' ? 1 : Math.max(lines.length, 1);
let normPerLineHeight = labelDims.height / numLines;
let labelLineHeight = normPerLineHeight * lineHeight;
let labelLineHeight = size * lineHeight;

let width = labelDims.width;
let height = labelDims.height + (numLines - 1) * (lineHeight - 1) * normPerLineHeight;
let height = labelDims.height + (numLines - 1) * (lineHeight - 1) * size;

util.setPrefixedProperty( _p.rstyle, 'labelWidth', prefix, width );
util.setPrefixedProperty( _p.rscratch, 'labelWidth', prefix, width );
Expand All @@ -328,6 +328,7 @@ BRp.applyPrefixedLabelDimensions = function( ele, prefix ){
util.setPrefixedProperty( _p.rscratch, 'labelHeight', prefix, height );

util.setPrefixedProperty( _p.rscratch, 'labelLineHeight', prefix, labelLineHeight );
util.setPrefixedProperty( _p.rscratch, 'labelActualDescent', prefix, labelDims.labelActualDescent );
};

BRp.getLabelText = function( ele, prefix ){
Expand Down Expand Up @@ -487,6 +488,7 @@ BRp.calculateLabelDimensions = function( ele, text ){
let size = ele.pstyle('font-size').pfValue;
let family = ele.pstyle('font-family').strValue;
let weight = ele.pstyle('font-weight').strValue;
let textMetrics = ele.pstyle('text-metrics').strValue || "default";

let canvas = this.labelCalcCanvas;
let c2d = this.labelCalcCanvasContext;
Expand All @@ -509,23 +511,38 @@ BRp.calculateLabelDimensions = function( ele, text ){
let width = 0;
let height = 0;
let lines = text.split('\n');
let lineCount = lines.length;
let labelActualDescent = 0;
let labelActualAscent = 0;

for( let i = 0; i < lines.length; i++ ){
for( let i = 0; i < lineCount; i++ ){
let line = lines[i];
let metrics = c2d.measureText(line);
let w = Math.ceil(metrics.width);
let h = size;
if (textMetrics === "actual") {
if (i === 0) {
labelActualAscent = metrics.actualBoundingBoxAscent;
}
if (i === lineCount - 1) {
labelActualDescent = metrics.actualBoundingBoxDescent;
}
}

width = Math.max(w, width);
height += h;
}

if (textMetrics === "actual") {
height -= size - labelActualAscent - labelActualDescent;
}
width += padding;
height += padding;

return {
width,
height
height,
labelActualAscent,
labelActualDescent
};
};

Expand Down
7 changes: 5 additions & 2 deletions src/extensions/renderer/canvas/drawing-label-text.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ CRp.drawElementText = function( context, ele, shiftToOriginWithBb, force, prefix
if( !label || !label.value ){ return; }

let justification = r.getLabelJustification(ele);
let isTextMetricsActual = ele.pstyle( 'text-metrics' ).strValue === 'actual';

context.textAlign = justification;
context.textBaseline = 'bottom';
context.textBaseline = isTextMetricsActual ? 'alphabetic' : 'bottom';
} else {
let badLine = ele.element()._private.rscratch.badLine;
let label = ele.pstyle( 'label' );
Expand Down Expand Up @@ -198,6 +199,7 @@ CRp.drawText = function( context, ele, prefix, applyRotation = true, useEleOpaci
let pdash = prefix ? prefix + '-' : '';
let textW = util.getPrefixedProperty( rscratch, 'labelWidth', prefix );
let textH = util.getPrefixedProperty( rscratch, 'labelHeight', prefix );
let labelActualDescent = util.getPrefixedProperty( rscratch, 'labelActualDescent', prefix );
let marginX = ele.pstyle( pdash + 'text-margin-x' ).pfValue;
let marginY = ele.pstyle( pdash + 'text-margin-y' ).pfValue;

Expand Down Expand Up @@ -335,7 +337,8 @@ CRp.drawText = function( context, ele, prefix, applyRotation = true, useEleOpaci
if( lineWidth > 0 ){
context.lineWidth = lineWidth;
}


textY -= labelActualDescent;
if( ele.pstyle( 'text-wrap' ).value === 'wrap' ){
let lines = util.getPrefixedProperty( rscratch, 'labelWrapCachedLines', prefix );
let lineHeight = util.getPrefixedProperty( rscratch, 'labelLineHeight', prefix );
Expand Down
9 changes: 8 additions & 1 deletion src/style/properties.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const styfn = {};
valign: { enums: [ 'top', 'center', 'bottom' ] },
halign: { enums: [ 'left', 'center', 'right' ] },
justification: { enums: [ 'left', 'center', 'right', 'auto' ] },
textMetrics: { enums: [ 'default', 'actual' ] },
text: { string: true },
data: { mapping: true, regex: data( 'data' ) },
layoutData: { mapping: true, regex: data( 'layoutData' ) },
Expand Down Expand Up @@ -217,7 +218,9 @@ const styfn = {};
{ name: 'text-overflow-wrap', type: t.textOverflowWrap, triggersBounds: diff.any },
{ name: 'text-max-width', type: t.size, triggersBounds: diff.any },
{ name: 'text-outline-width', type: t.size, triggersBounds: diff.any },
{ name: 'line-height', type: t.positiveNumber, triggersBounds: diff.any }
{ name: 'line-height', type: t.positiveNumber, triggersBounds: diff.any },
{ name: 'bbox-margin-error-x', type: t.size, triggersBounds: diff.any },
{ name: 'bbox-margin-error-y', type: t.size, triggersBounds: diff.any },
];

let commonLabel = [
Expand All @@ -235,6 +238,7 @@ const styfn = {};
{ name: 'text-border-style', type: t.borderStyle, triggersBounds: diff.any },
{ name: 'text-background-shape', type: t.textBackgroundShape, triggersBounds: diff.any },
{ name: 'text-justification', type: t.justification },
{ name: 'text-metrics', type: t.textMetrics },
{ name: 'box-select-labels', type: t.bool, triggersBounds: diff.any },
];

Expand Down Expand Up @@ -671,6 +675,9 @@ styfn.getDefaultProperties = function(){
'underlay-padding': 10,
'underlay-shape': 'round-rectangle',
'underlay-corner-radius': 'auto',
'bbox-margin-error-x': 2,
'bbox-margin-error-y': 2,
'text-metrics': 'default',
'transition-property': 'none',
'transition-duration': 0,
'transition-delay': 0,
Expand Down