Skip to content

Commit

Permalink
attributes.textWrap: enable calc() expression for width and height (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kumilingus authored May 31, 2022
1 parent bff7079 commit 085efce
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 32 deletions.
42 changes: 34 additions & 8 deletions docs/src/joint/api/dia/attributes/textWrap.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
<p>Valid only for <code data-lang="svg">&lt;text&gt;</code> subelements.</p>

<p>Similar to the <code>text</code> <a href="#dia.attributes.text">attribute</a>, except the provided string is automatically wrapped to fit within the reference bounding box.</p>
<p>It enables the text wrapping for the <code>text</code> <a href="#dia.attributes.text">attribute</a> (automatically breaks the lines to fit within the reference bounding box).</p>

<p>Expects an object with a <code>text</code> property and optional <code>width</code> and <code>height</code>, which can adjust the final size of the wrapped text. Negative values decrease the dimension (e.g. to add padding around the wrapped text); positive values increase the dimension. Percentage values are accepted as well.</p>
<p>It expects an object with several optional properties.</p>

<p>If the text cannot fit into the bounding box as specified, the overflowing words are cut off. If the <code>ellipsis</code> option is provided, an ellipsis string is inserted before the cutoff. If no words can fit into the bounding box at all, no text is inserted. See <a href="#util.breakText">util.breakText</a> for more info.</p>
<p><code>width</code> and <code>height</code> adjust the final size of the wrapped text.
<ul>
<li>If not provided, the text is wrapped to fit the reference bounding box</li>
<li>It can be a <a href="#dia.attributes.calc">calc()</a> expression (e.g. <code>'calc(w - 10)'</code>)</li>
<li>Percentage expression (e.g. <code>'50%'</code> is half the width or height)</li>
<li>A positive number is an absolute dimension (e.g <code>50</code> fits the text within 50 pixels)</li>
<li>A negative number is a relative dimension (e.g. <code>-10</code> is an equivalent to <code>'calc(w - 10)'</code> resp. <code>'calc(h - 10)'</code>)</li>
<li>Use <code>null</code> to disable wrapping in a given dimension</li>
</ul>
</p>

<pre><code>textWrap: {
<p>If the text cannot fit into the bounding box as specified, the overflowing words are cut off.
If the <code>ellipsis</code> option is provided, an ellipsis string is inserted before the cutoff.
If no words can fit into the bounding box at all, no text is inserted.
</p>

<pre><code>element1.attr('label', {
text: 'Text to wrap.',
textWrap: {
width: 'calc(w - 10)', // reference width minus 10
height: null', // no height restriction
}
});

element2.attr('label', {
text: 'lorem ipsum dolor sit amet consectetur adipiscing elit',
width: -10, // reference width minus 10
height: '50%', // half of the reference height
ellipsis: true // could also be a custom string, e.g. '...!?'
}</code></pre>
textWrap: {
width: -10, // reference width minus 10
height: '50%', // half of the reference height
ellipsis: true // could also be a custom string, e.g. '...!?'
}
});</code></pre>

<p>For more info see <a href="#util.breakText">util.breakText</a>.</p>
31 changes: 23 additions & 8 deletions src/dia/attributes/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -348,31 +348,46 @@ const attributesNS = {
textWrap: {
qualify: isPlainObject,
set: function(value, refBBox, node, attrs) {
var size = {};
// option `width`
var width = value.width || 0;
var size = {};
if (isPercentage(width)) {
size.width = refBBox.width * parseFloat(width) / 100;
} else if (width <= 0) {
size.width = refBBox.width + width;
} else if (isCalcAttribute(width)) {
size.width = Number(evalCalcAttribute(width, refBBox));
} else {
size.width = width;
if (value.width === null) {
// breakText() requires width to be specified.
size.width = Infinity;
} else if (width <= 0) {
size.width = refBBox.width + width;
} else {
size.width = width;
}
}
// option `height`
var height = value.height || 0;
if (isPercentage(height)) {
size.height = refBBox.height * parseFloat(height) / 100;
} else if (height <= 0) {
size.height = refBBox.height + height;
} else if (isCalcAttribute(height)) {
size.height = Number(evalCalcAttribute(height, refBBox));
} else {
size.height = height;
if (value.height === null) {
// if height is not specified breakText() does not
// restrict the height of the text.
} else if (height <= 0) {
size.height = refBBox.height + height;
} else {
size.height = height;
}
}
// option `text`
var wrappedText;
var text = value.text;
if (text === undefined) text = attrs.text;
if (text !== undefined) {
wrappedText = breakText('' + text, size, {
const breakTextFn = value.breakText || breakText;
wrappedText = breakTextFn('' + text, size, {
'font-weight': attrs['font-weight'] || attrs.fontWeight,
'font-size': attrs['font-size'] || attrs.fontSize,
'font-family': attrs['font-family'] || attrs.fontFamily,
Expand Down
26 changes: 21 additions & 5 deletions test/jointjs/dia/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ QUnit.module('Attributes', function() {
spy.resetHistory();
ns.textWrap.set.call(cellView, {}, bbox, node, {});
assert.equal(node.textContent, '-'); // Vectorizer empty line has `-` character with opacity 0
assert.ok(!spy.called || spy.calledWith('', sinon.match.instanceOf(g.Rect)));

// text via `text` attribute
spy.resetHistory();
Expand All @@ -79,19 +78,36 @@ QUnit.module('Attributes', function() {

// width & height absolute
spy.resetHistory();
ns.textWrap.set.call(cellView, { text: 'text', width: -20, height: -30 }, bbox, node, {});
assert.ok(!spy.called || spy.calledWith('', sinon.match(function(obj) {
ns.textWrap.set.call(cellView, { text: 'text', width: -20, height: -30, breakText: spy }, bbox, node, {});
assert.ok(spy.calledWith(sinon.match.string, sinon.match(function(obj) {
return obj.width === WIDTH - 20 && obj.height === HEIGHT - 30;
})));

// width & height relative
spy.resetHistory();
ns.textWrap.set.call(cellView, { text: 'text', width: '50%', height: '200%' }, bbox, node, {});
assert.ok(!spy.called || spy.calledWith('', sinon.match(function(obj) {
ns.textWrap.set.call(cellView, { text: 'text', width: '50%', height: '200%', breakText: spy }, bbox, node, {});
assert.ok(spy.calledWith(sinon.match.string, sinon.match(function(obj) {
return obj.width === WIDTH / 2 && obj.height === HEIGHT * 2;
})));

// width & height no restriction
spy.resetHistory();
ns.textWrap.set.call(cellView, { text: 'text', width: null, height: null, breakText: spy }, bbox, node, {});
assert.ok(spy.calledWith(sinon.match.string, sinon.match(function(obj) {
return obj.width === Infinity && obj.height === undefined;
})));

// width & height calc()
spy.resetHistory();
ns.textWrap.set.call(cellView, { text: 'text', width: 'calc(w-11)', height: 'calc(h-13)', breakText: spy }, bbox, node, {});
assert.ok(spy.calledWith(sinon.match.string, sinon.match(function(obj) {
return obj.width === refBBox.width - 11 && obj.height === refBBox.height - 13;
})));

spy.restore();
});


QUnit.test('x', function(assert) {
var TestElement = joint.dia.Element.define('Test', {
size: {
Expand Down
33 changes: 22 additions & 11 deletions types/joint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2802,14 +2802,21 @@ export namespace util {

export function parseCssNumeric(val: any, restrictUnits: string | string[]): { value: number, unit?: string } | null;

export function breakText(text: string, size: { width: number, height?: number }, attrs?: attributes.NativeSVGAttributes, opt?: {
svgDocument?: SVGElement;
separator?: string | any;
eol?: string;
ellipsis?: boolean | string;
hyphen?: string | RegExp;
maxLineCount?: number;
}): string;
type BreakTextFunction = (
text: string,
size: { width: number, height?: number },
attrs?: attributes.NativeSVGAttributes,
opt?: {
svgDocument?: SVGElement;
separator?: string | any;
eol?: string;
ellipsis?: boolean | string;
hyphen?: string | RegExp;
maxLineCount?: number;
}
) => string;

export var breakText: BreakTextFunction;

export function sanitizeHTML(html: string): string;

Expand Down Expand Up @@ -3748,13 +3755,17 @@ export namespace attributes {
}

interface SVGAttributeTextWrap {
text?: string;
width?: string | number;
height?: string | number;
width?: string | number | null;
height?: string | number | null;
ellipsis?: boolean | string;
hyphen?: string;
maxLineCount?: number;
breakText?: util.BreakTextFunction;
[key: string]: any;
/**
* @deprecated use SVGAttributes.text instead
**/
text?: string;
}

interface SVGAttributes extends NativeSVGAttributes {
Expand Down

0 comments on commit 085efce

Please sign in to comment.