From d23c09e6949cb3f7f915e7ba953ae641af588c7d Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 11 Sep 2023 19:59:44 +0200 Subject: [PATCH 1/4] feat(dia.CellView)! separate alias and calc expression evaluation from attributes definition --- src/dia/CellView.mjs | 53 +++++-- src/dia/attributes/index.mjs | 272 +++++++++++++---------------------- 2 files changed, 140 insertions(+), 185 deletions(-) diff --git a/src/dia/CellView.mjs b/src/dia/CellView.mjs index c6da18946..f19cc793a 100644 --- a/src/dia/CellView.mjs +++ b/src/dia/CellView.mjs @@ -21,6 +21,9 @@ import { Point, Rect } from '../g/index.mjs'; import V from '../V/index.mjs'; import $ from 'jquery'; import { HighlighterView } from './HighlighterView.mjs'; +import { evalCalcAttribute, isCalcAttribute } from './attributes/calc.mjs'; +import { kebabCase } from 'lodash'; +import { aliases, calcAttributes } from './attributes/index.mjs'; const HighlightingTypes = { DEFAULT: 'default', @@ -542,12 +545,15 @@ export const CellView = View.extend({ var attrName, attrVal, def, i, n; var normalAttrs, setAttrs, positionAttrs, offsetAttrs; var relatives = []; + const rawAttrs = {}; // divide the attributes between normal and special for (attrName in attrs) { if (!attrs.hasOwnProperty(attrName)) continue; attrVal = attrs[attrName]; + attrName = this.translateAttributeName(attrName); + rawAttrs[attrName] = attrVal; def = this.getAttributeDefinition(attrName); - if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, attrs, this))) { + if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, rawAttrs, this))) { if (isString(def.set)) { normalAttrs || (normalAttrs = {}); normalAttrs[def.set] = attrVal; @@ -557,7 +563,7 @@ export const CellView = View.extend({ } } else { normalAttrs || (normalAttrs = {}); - normalAttrs[toKebabCase(attrName)] = attrVal; + normalAttrs[attrName] = attrVal; } } @@ -582,7 +588,7 @@ export const CellView = View.extend({ } return { - raw: attrs, + raw: rawAttrs, normal: normalAttrs, set: setAttrs, position: positionAttrs, @@ -590,24 +596,47 @@ export const CellView = View.extend({ }; }, + translateAttributeName: function(attrName) { + return aliases[attrName] || toKebabCase(attrName); + }, + + evalCalcAttributes: function(attrs, refBBox) { + for (let attrName in attrs) { + if (!attrs.hasOwnProperty(attrName)) continue; + let value = attrs[attrName]; + const calcType = calcAttributes[attrName]; + if (calcType > 0 && isCalcAttribute(value)) { + value = evalCalcAttribute(value, refBBox); + if (calcType === 2) { + value = Math.max(0, value); + } + attrs[attrName] = value; + } + } + return attrs; + }, + updateRelativeAttributes: function(node, attrs, refBBox, opt) { opt || (opt = {}); var attrName, attrVal, def; - var rawAttrs = attrs.raw || {}; + var evalAttrs = this.evalCalcAttributes(attrs.raw || {}, refBBox); var nodeAttrs = attrs.normal || {}; + for (const nodeAttrName in nodeAttrs) { + nodeAttrs[nodeAttrName] = evalAttrs[nodeAttrName]; + } var setAttrs = attrs.set; var positionAttrs = attrs.position; var offsetAttrs = attrs.offset; for (attrName in setAttrs) { - attrVal = setAttrs[attrName]; + attrVal = evalAttrs[attrName]; def = this.getAttributeDefinition(attrName); // SET - set function should return attributes to be set on the node, // which will affect the node dimensions based on the reference bounding // box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points - var setResult = def.set.call(this, attrVal, refBBox.clone(), node, rawAttrs, this); + var setResult = def.set.call(this, attrVal, refBBox.clone(), node, evalAttrs, this); if (isObject(setResult)) { assign(nodeAttrs, setResult); } else if (setResult !== undefined) { @@ -643,13 +672,13 @@ export const CellView = View.extend({ var positioned = false; for (attrName in positionAttrs) { - attrVal = positionAttrs[attrName]; + attrVal = evalAttrs[attrName]; def = this.getAttributeDefinition(attrName); // POSITION - position function should return a point from the // reference bounding box. The default position of the node is x:0, y:0 of // the reference bounding box or could be further specify by some // SVG attributes e.g. `x`, `y` - translation = def.position.call(this, attrVal, refBBox.clone(), node, rawAttrs, this); + translation = def.position.call(this, attrVal, refBBox.clone(), node, evalAttrs, this); if (translation) { nodePosition.offset(Point(translation).scale(sx, sy)); positioned || (positioned = true); @@ -667,12 +696,12 @@ export const CellView = View.extend({ if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) { var nodeBBox = V.transformRect(nodeBoundingRect, nodeMatrix).scale(1 / sx, 1 / sy); for (attrName in offsetAttrs) { - attrVal = offsetAttrs[attrName]; + attrVal = evalAttrs[attrName]; def = this.getAttributeDefinition(attrName); // OFFSET - offset function should return a point from the element // bounding box. The default offset point is x:0, y:0 (origin) or could be further // specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy` - translation = def.offset.call(this, attrVal, nodeBBox, node, rawAttrs, this); + translation = def.offset.call(this, attrVal, nodeBBox, node, evalAttrs, this); if (translation) { nodePosition.offset(Point(translation).scale(sx, sy)); offseted || (offseted = true); @@ -872,9 +901,9 @@ export const CellView = View.extend({ node = nodeData.node; processedAttrs = this.processNodeAttributes(node, nodeAttrs); - if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset) { + if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset && !processedAttrs.raw.ref) { // Set all the normal attributes right on the SVG/HTML element. - this.setNodeAttributes(node, processedAttrs.normal); + this.setNodeAttributes(node, this.evalCalcAttributes(processedAttrs.normal, opt.rootBBox)); } else { diff --git a/src/dia/attributes/index.mjs b/src/dia/attributes/index.mjs index 37d6b993a..4a6edded8 100644 --- a/src/dia/attributes/index.mjs +++ b/src/dia/attributes/index.mjs @@ -172,7 +172,7 @@ function isLinkView() { function contextMarker(context) { var marker = {}; // Stroke - // The context 'fill' is disregared here. The usual case is to use the marker with a connection + // The context 'fill' is disregarded here. The usual case is to use the marker with a connection // (for which 'fill' attribute is set to 'none'). var stroke = context.stroke; if (typeof stroke === 'string') { @@ -181,8 +181,7 @@ function contextMarker(context) { } // Opacity // Again the context 'fill-opacity' is ignored. - var strokeOpacity = context.strokeOpacity; - if (strokeOpacity === undefined) strokeOpacity = context['stroke-opacity']; + var strokeOpacity = context['stroke-opacity']; if (strokeOpacity === undefined) strokeOpacity = context.opacity; if (strokeOpacity !== undefined) { marker['stroke-opacity'] = strokeOpacity; @@ -201,67 +200,11 @@ function setPaintURL(def) { const attributesNS = { - xlinkShow: { - set: 'xlink:show' - }, - - xlinkRole: { - set: 'xlink:role' - }, - - xlinkType: { - set: 'xlink:type' - }, - - xlinkArcrole: { - set: 'xlink:arcrole' - }, - - xlinkTitle: { - set: 'xlink:title' - }, - - xlinkActuate: { - set: 'xlink:actuate' - }, - - xmlSpace: { - set: 'xml:space' - }, - - xmlBase: { - set: 'xml:base' - }, - - xmlLang: { - set: 'xml:lang' - }, - - preserveAspectRatio: { - set: 'preserveAspectRatio' - }, - - requiredExtension: { - set: 'requiredExtension' - }, - - requiredFeatures: { - set: 'requiredFeatures' - }, - - systemLanguage: { - set: 'systemLanguage' - }, - - externalResourcesRequired: { - set: 'externalResourceRequired' - }, - href: { set: setIfChangedWrapper('href') }, - xlinkHref: { + 'xlink:href': { set: setIfChangedWrapper('xlink:href') }, @@ -282,7 +225,7 @@ const attributesNS = { set: setPaintURL }, - sourceMarker: { + 'source-marker': { qualify: isPlainObject, set: function(marker, refBBox, node, attrs) { marker = assign(contextMarker(attrs), marker); @@ -290,7 +233,7 @@ const attributesNS = { } }, - targetMarker: { + 'target-marker': { qualify: isPlainObject, set: function(marker, refBBox, node, attrs) { marker = assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker); @@ -298,7 +241,7 @@ const attributesNS = { } }, - vertexMarker: { + 'vertex-marker': { qualify: isPlainObject, set: function(marker, refBBox, node, attrs) { marker = assign(contextMarker(attrs), marker); @@ -308,30 +251,21 @@ const attributesNS = { text: { qualify: function(_text, _node, attrs) { - return !attrs.textWrap || !isPlainObject(attrs.textWrap); + const textWrap = attrs['text-wrap']; + return !textWrap || !isPlainObject(textWrap); }, set: function(text, refBBox, node, attrs) { const $node = $(node); const cacheName = 'joint-text'; const cache = $node.data(cacheName); - const { - lineHeight, - annotations, - textVerticalAnchor, - eol, - displayEmpty - } = attrs; - let textPath = attrs.textPath; - // eval `x` if using calc() - let x = attrs.x; - if (isCalcAttribute(x)) { - x = evalCalcAttribute(x, refBBox); - } - // eval `font-size` if using calc() - let fontSize = attrs['font-size'] || attrs['fontSize']; - if (isCalcAttribute(fontSize)) { - fontSize = evalCalcAttribute(fontSize, refBBox); - } + const lineHeight = attrs['line-height']; + const textVerticalAnchor = attrs['text-vertical-anchor']; + const displayEmpty = attrs['display-empty']; + const fontSize = attrs['font-size']; + const annotations = attrs.annotations; + const eol = attrs.eol; + const x = attrs.x; + let textPath = attrs['text-path']; // Update the text only if there was a change in the string // or any of its attributes. const textHash = JSON.stringify([text, lineHeight, annotations, textVerticalAnchor, eol, displayEmpty, textPath, x, fontSize]); @@ -364,7 +298,7 @@ const attributesNS = { } }, - textWrap: { + 'text-wrap': { qualify: isPlainObject, set: function(value, refBBox, node, attrs) { var size = {}; @@ -406,13 +340,12 @@ const attributesNS = { if (text === undefined) text = attrs.text; if (text !== undefined) { const breakTextFn = value.breakText || breakText; - const fontSizeAttr = attrs['font-size'] || attrs.fontSize; wrappedText = breakTextFn('' + text, size, { - 'font-weight': attrs['font-weight'] || attrs.fontWeight, - 'font-size': isCalcAttribute(fontSizeAttr) ? evalCalcAttribute(fontSizeAttr, refBBox) : fontSizeAttr, - 'font-family': attrs['font-family'] || attrs.fontFamily, - 'lineHeight': attrs.lineHeight, - 'letter-spacing': 'letter-spacing' in attrs ? attrs['letter-spacing'] : attrs.letterSpacing + 'font-weight': attrs['font-weight'], + 'font-size': attrs['font-size'], + 'font-family': attrs['font-family'], + 'lineHeight': attrs['line-height'], + 'letter-spacing': attrs['letter-spacing'] }, { // Provide an existing SVG Document here // instead of creating a temporary one over again. @@ -460,15 +393,15 @@ const attributesNS = { } }, - lineHeight: { + 'line-height': { qualify: isTextInUse }, - textVerticalAnchor: { + 'text-vertical-anchor': { qualify: isTextInUse }, - textPath: { + 'text-path': { qualify: isTextInUse }, @@ -480,7 +413,7 @@ const attributesNS = { qualify: isTextInUse }, - displayEmpty: { + 'display-empty': { qualify: isTextInUse }, @@ -517,22 +450,22 @@ const attributesNS = { // if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box // otherwise, `refX` is the left coordinate of the bounding box - refX: { + 'ref-x': { position: positionWrapper('x', 'width', 'origin') }, - refY: { + 'ref-y': { position: positionWrapper('y', 'height', 'origin') }, // `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom // coordinate of the reference element. - refDx: { + 'ref-dx': { position: positionWrapper('x', 'width', 'corner') }, - refDy: { + 'ref-dy': { position: positionWrapper('y', 'height', 'corner') }, @@ -541,23 +474,23 @@ const attributesNS = { // val in 0..1 ref-width = 0.75 sets the width to 75% of the ref. el. width // val < 0 || val > 1 ref-height = -20 sets the height to the ref. el. height shorter by 20 - refWidth: { + 'ref-width': { set: setWrapper('width', 'width') }, - refHeight: { + 'ref-height': { set: setWrapper('height', 'height') }, - refRx: { + 'ref-rx': { set: setWrapper('rx', 'width') }, - refRy: { + 'ref-ry': { set: setWrapper('ry', 'height') }, - refRInscribed: { + 'ref-r-inscribed': { set: (function(attrName) { var widthFn = setWrapper(attrName, 'width'); var heightFn = setWrapper(attrName, 'height'); @@ -568,7 +501,7 @@ const attributesNS = { })('r') }, - refRCircumscribed: { + 'ref-r-circumscribed': { set: function(value, refBBox) { var isValuePercentage = isPercentage(value); value = parseFloat(value); @@ -588,29 +521,29 @@ const attributesNS = { } }, - refCx: { + 'ref-cx': { set: setWrapper('cx', 'width') }, - refCy: { + 'ref-cy': { set: setWrapper('cy', 'height') }, // `x-alignment` when set to `middle` causes centering of the subelement around its new x coordinate. // `x-alignment` when set to `right` uses the x coordinate as referenced to the right of the bbox. - xAlignment: { + 'x-alignment': { offset: offsetWrapper('x', 'width', 'right') }, // `y-alignment` when set to `middle` causes centering of the subelement around its new y coordinate. // `y-alignment` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox. - yAlignment: { + 'y-alignment': { offset: offsetWrapper('y', 'height', 'bottom') }, - resetOffset: { + 'reset-offset': { offset: function(val, nodeBBox) { return (val) ? { x: -nodeBBox.x, y: -nodeBBox.y } @@ -619,19 +552,19 @@ const attributesNS = { }, - refDResetOffset: { + 'ref-d-reset-offset': { set: dWrapper({ resetOffset: true }) }, - refDKeepOffset: { + 'ref-d-keep-offset': { set: dWrapper({ resetOffset: false }) }, - refPointsResetOffset: { + 'ref-points-reset-offset': { set: pointsWrapper({ resetOffset: true }) }, - refPointsKeepOffset: { + 'ref-points-keep-offset': { set: pointsWrapper({ resetOffset: false }) }, @@ -661,88 +594,81 @@ const attributesNS = { } }, - atConnectionLengthKeepGradient: { + 'at-connection-length-keep-gradient': { qualify: isLinkView, set: atConnectionWrapper('getTangentAtLength', { rotate: true }) }, - atConnectionLengthIgnoreGradient: { + 'at-connection-length-ignore-gradient': { qualify: isLinkView, set: atConnectionWrapper('getTangentAtLength', { rotate: false }) }, - atConnectionRatioKeepGradient: { + 'at-connection-ratio-keep-gradient': { qualify: isLinkView, set: atConnectionWrapper('getTangentAtRatio', { rotate: true }) }, - atConnectionRatioIgnoreGradient: { + 'at-connection-ratio-ignore-gradient': { qualify: isLinkView, set: atConnectionWrapper('getTangentAtRatio', { rotate: false }) } }; -attributesNS['xlink:href'] = attributesNS.xlinkHref; - -// Support `calc()` with the following SVG attributes -[ - 'transform', // g - 'd', // path - 'points', // polyline / polygon - 'cx', 'cy', // circle / ellipse - 'x1', 'x2', 'y1', 'y2', // line - 'x', 'y', // rect / text / image - 'dx', 'dy' // text -].forEach(attribute => { - attributesNS[attribute] = { - qualify: isCalcAttribute, - set: function setCalcAttribute(value, refBBox) { - return { [attribute]: evalCalcAttribute(value, refBBox) }; - } - }; -}); - -// Prevent "A negative value is not valid" error. -[ - 'width', 'height', // rect / image - 'r', // circle - 'rx', 'ry', // rect / ellipse - 'font-size', // text - 'stroke-width' // elements -].forEach(attribute => { - attributesNS[attribute] = { - qualify: isCalcAttribute, - set: function setCalcAttribute(value, refBBox) { - return { [attribute]: Math.max(0, evalCalcAttribute(value, refBBox)) }; - } - }; -}); - // Aliases -attributesNS.refR = attributesNS.refRInscribed; -attributesNS.refD = attributesNS.refDResetOffset; -attributesNS.refPoints = attributesNS.refPointsResetOffset; -attributesNS.atConnectionLength = attributesNS.atConnectionLengthKeepGradient; -attributesNS.atConnectionRatio = attributesNS.atConnectionRatioKeepGradient; -attributesNS.fontSize = attributesNS['font-size']; -attributesNS.strokeWidth = attributesNS['stroke-width']; +attributesNS['ref-r'] = attributesNS['ref-r-inscribed']; +attributesNS['ref-d'] = attributesNS['ref-d-reset-offset']; +attributesNS['ref-points'] = attributesNS['ref-points-reset-offset']; +attributesNS['at-connection-length'] = attributesNS['at-connection-length-keep-gradient']; +attributesNS['at-connection-ratio'] = attributesNS['at-connection-ratio-keep-gradient']; // This allows to combine both absolute and relative positioning // refX: 50%, refX2: 20 -attributesNS.refX2 = attributesNS.refX; -attributesNS.refY2 = attributesNS.refY; -attributesNS.refWidth2 = attributesNS.refWidth; -attributesNS.refHeight2 = attributesNS.refHeight; - -// Aliases for backwards compatibility -attributesNS['ref-x'] = attributesNS.refX; -attributesNS['ref-y'] = attributesNS.refY; -attributesNS['ref-dy'] = attributesNS.refDy; -attributesNS['ref-dx'] = attributesNS.refDx; -attributesNS['ref-width'] = attributesNS.refWidth; -attributesNS['ref-height'] = attributesNS.refHeight; -attributesNS['x-alignment'] = attributesNS.xAlignment; -attributesNS['y-alignment'] = attributesNS.yAlignment; +attributesNS['ref-x2'] = attributesNS['ref-x']; +attributesNS['ref-y2'] = attributesNS['ref-y']; +attributesNS['ref-width2'] = attributesNS['ref-width']; +attributesNS['ref-height2'] = attributesNS['ref-height']; export const attributes = attributesNS; +export const aliases = { + xlinkHref: 'xlink:href', + xlinkShow: 'xlink:show', + xlinkRole: 'xlink:role', + xlinkType: 'xlink:type', + xlinkArcrole: 'xlink:arcrole', + xlinkTitle: 'xlink:title', + xlinkActuate: 'xlink:actuate', + xmlSpace: 'xml:space', + xmlLang: 'xml:lang', + xmlBase: 'xml:base', + preserveAspectRatio: 'preserveAspectRatio', + requiredExtensions: 'requiredExtensions', + requiredFeatures: 'requiredFeatures', + systemLanguage: 'systemLanguage', + externalResourcesRequired: 'externalResourcesRequired', +}; + +// TODO: +// - move the code to cellView or move the cellView calcAttributes to this file +// - replace the values with named constants +export const calcAttributes = { + 'transform': 1, + 'x': 1, + 'y': 1, + 'cx': 1, + 'cy': 1, + 'x1': 1, + 'y1': 1, + 'x2': 1, + 'y2': 1, + 'points': 1, + 'd': 1, + 'r': 2, + 'rx': 2, + 'ry': 2, + 'width': 2, + 'height': 2, + 'stroke-width': 2, + 'font-size': 2, +}; From c3d25b36accd4a9f8e3333aa97a5a7c0575454d6 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 11 Sep 2023 20:37:41 +0200 Subject: [PATCH 2/4] update --- demo/links/src/links.js | 3 +- src/dia/CellView.mjs | 25 +++------------- src/dia/attributes/aliases.mjs | 17 +++++++++++ src/dia/attributes/calcAttributes.mjs | 29 ++++++++++++++++++ src/dia/attributes/index.mjs | 42 --------------------------- 5 files changed, 52 insertions(+), 64 deletions(-) create mode 100644 src/dia/attributes/aliases.mjs create mode 100644 src/dia/attributes/calcAttributes.mjs diff --git a/demo/links/src/links.js b/demo/links/src/links.js index bbaa1b344..ab36560f9 100644 --- a/demo/links/src/links.js +++ b/demo/links/src/links.js @@ -370,7 +370,8 @@ var el1 = new joint.shapes.standard.Path({ attrs: { body: { fill: '#31d0c6', - d: 'M 0 calc(0.45 * h) calc(0.25 * w) calc(0.45 * h) calc(0.25 * w) calc(0.75 * h) calc(0.75 * w) calc(0.75 * h) calc(0.75 * w) 0 calc(w) 0 calc(w) calc(h) 0 calc(h) z' + d: 'M 0 calc(0.45 * h) calc(0.25 * w) calc(0.45 * h) calc(0.25 * w) calc(0.75 * h) calc(0.75 * w) calc(0.75 * h) calc(0.75 * w) 0 calc(w) 0 calc(w) calc(h) 0 calc(h) z', + refD: null } } }); diff --git a/src/dia/CellView.mjs b/src/dia/CellView.mjs index f19cc793a..43ac591b5 100644 --- a/src/dia/CellView.mjs +++ b/src/dia/CellView.mjs @@ -21,9 +21,8 @@ import { Point, Rect } from '../g/index.mjs'; import V from '../V/index.mjs'; import $ from 'jquery'; import { HighlighterView } from './HighlighterView.mjs'; -import { evalCalcAttribute, isCalcAttribute } from './attributes/calc.mjs'; -import { kebabCase } from 'lodash'; -import { aliases, calcAttributes } from './attributes/index.mjs'; +import { aliases } from './attributes/aliases.mjs'; +import { evalCalcAttributes } from './attributes/calcAttributes.mjs'; const HighlightingTypes = { DEFAULT: 'default', @@ -600,28 +599,12 @@ export const CellView = View.extend({ return aliases[attrName] || toKebabCase(attrName); }, - evalCalcAttributes: function(attrs, refBBox) { - for (let attrName in attrs) { - if (!attrs.hasOwnProperty(attrName)) continue; - let value = attrs[attrName]; - const calcType = calcAttributes[attrName]; - if (calcType > 0 && isCalcAttribute(value)) { - value = evalCalcAttribute(value, refBBox); - if (calcType === 2) { - value = Math.max(0, value); - } - attrs[attrName] = value; - } - } - return attrs; - }, - updateRelativeAttributes: function(node, attrs, refBBox, opt) { opt || (opt = {}); var attrName, attrVal, def; - var evalAttrs = this.evalCalcAttributes(attrs.raw || {}, refBBox); + var evalAttrs = evalCalcAttributes(attrs.raw || {}, refBBox); var nodeAttrs = attrs.normal || {}; for (const nodeAttrName in nodeAttrs) { nodeAttrs[nodeAttrName] = evalAttrs[nodeAttrName]; @@ -903,7 +886,7 @@ export const CellView = View.extend({ if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset && !processedAttrs.raw.ref) { // Set all the normal attributes right on the SVG/HTML element. - this.setNodeAttributes(node, this.evalCalcAttributes(processedAttrs.normal, opt.rootBBox)); + this.setNodeAttributes(node, evalCalcAttributes(processedAttrs.normal, opt.rootBBox)); } else { diff --git a/src/dia/attributes/aliases.mjs b/src/dia/attributes/aliases.mjs new file mode 100644 index 000000000..b912fe307 --- /dev/null +++ b/src/dia/attributes/aliases.mjs @@ -0,0 +1,17 @@ +export const aliases = { + xlinkHref: 'xlink:href', + xlinkShow: 'xlink:show', + xlinkRole: 'xlink:role', + xlinkType: 'xlink:type', + xlinkArcrole: 'xlink:arcrole', + xlinkTitle: 'xlink:title', + xlinkActuate: 'xlink:actuate', + xmlSpace: 'xml:space', + xmlLang: 'xml:lang', + xmlBase: 'xml:base', + preserveAspectRatio: 'preserveAspectRatio', + requiredExtensions: 'requiredExtensions', + requiredFeatures: 'requiredFeatures', + systemLanguage: 'systemLanguage', + externalResourcesRequired: 'externalResourcesRequired', +}; diff --git a/src/dia/attributes/calcAttributes.mjs b/src/dia/attributes/calcAttributes.mjs new file mode 100644 index 000000000..0bd961e97 --- /dev/null +++ b/src/dia/attributes/calcAttributes.mjs @@ -0,0 +1,29 @@ +import { isCalcAttribute, evalCalcAttribute } from './calc.mjs'; + +const calcAttributesList = ['transform', 'x', 'y', 'cx', 'cy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size']; +const positiveValueList = ['r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size']; + +const calcAttributes = calcAttributesList.reduce((acc, attrName) => { + acc[attrName] = true; + return acc; +}, {}); + +const positiveValueAttributes = positiveValueList.reduce((acc, attrName) => { + acc[attrName] = true; + return acc; +}, {}); + +export function evalCalcAttributes(attrs, refBBox) { + for (let attrName in attrs) { + if (!attrs.hasOwnProperty(attrName)) continue; + let value = attrs[attrName]; + if (attrName in calcAttributes && isCalcAttribute(value)) { + value = evalCalcAttribute(value, refBBox); + if (attrName in positiveValueAttributes) { + value = Math.max(0, value); + } + attrs[attrName] = value; + } + } + return attrs; +} diff --git a/src/dia/attributes/index.mjs b/src/dia/attributes/index.mjs index 4a6edded8..8d6910414 100644 --- a/src/dia/attributes/index.mjs +++ b/src/dia/attributes/index.mjs @@ -630,45 +630,3 @@ attributesNS['ref-width2'] = attributesNS['ref-width']; attributesNS['ref-height2'] = attributesNS['ref-height']; export const attributes = attributesNS; - -export const aliases = { - xlinkHref: 'xlink:href', - xlinkShow: 'xlink:show', - xlinkRole: 'xlink:role', - xlinkType: 'xlink:type', - xlinkArcrole: 'xlink:arcrole', - xlinkTitle: 'xlink:title', - xlinkActuate: 'xlink:actuate', - xmlSpace: 'xml:space', - xmlLang: 'xml:lang', - xmlBase: 'xml:base', - preserveAspectRatio: 'preserveAspectRatio', - requiredExtensions: 'requiredExtensions', - requiredFeatures: 'requiredFeatures', - systemLanguage: 'systemLanguage', - externalResourcesRequired: 'externalResourcesRequired', -}; - -// TODO: -// - move the code to cellView or move the cellView calcAttributes to this file -// - replace the values with named constants -export const calcAttributes = { - 'transform': 1, - 'x': 1, - 'y': 1, - 'cx': 1, - 'cy': 1, - 'x1': 1, - 'y1': 1, - 'x2': 1, - 'y2': 1, - 'points': 1, - 'd': 1, - 'r': 2, - 'rx': 2, - 'ry': 2, - 'width': 2, - 'height': 2, - 'stroke-width': 2, - 'font-size': 2, -}; From b5e720d60ecb915f39934d4cc48176946b7e7050 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 11 Sep 2023 20:43:55 +0200 Subject: [PATCH 3/4] update --- src/config/index.mjs | 5 ++++- src/dia/CellView.mjs | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/config/index.mjs b/src/config/index.mjs index 71ea2ce81..9cf99b1fd 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -11,5 +11,8 @@ export const config = { defaultTheme: 'default', // The maximum delay required for two consecutive touchend events to be interpreted // as a double-tap. - doubleTapInterval: 300 + doubleTapInterval: 300, + // Should be the camel case attributes converted to dash case attributes. + // e.g. `strokeWidth` -> `stroke-width` + supportCamelCaseAttributes: true, }; diff --git a/src/dia/CellView.mjs b/src/dia/CellView.mjs index 43ac591b5..866e426d7 100644 --- a/src/dia/CellView.mjs +++ b/src/dia/CellView.mjs @@ -595,8 +595,14 @@ export const CellView = View.extend({ }; }, - translateAttributeName: function(attrName) { - return aliases[attrName] || toKebabCase(attrName); + translateAttributeName: function(name) { + if (name in aliases) { + return aliases[name]; + } + if (config.supportCamelCaseAttributes) { + return toKebabCase(name); + } + return name; }, updateRelativeAttributes: function(node, attrs, refBBox, opt) { From 9c6ce1c4be82ba989c9d8faf71922a650801e25a Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Mon, 11 Sep 2023 20:46:17 +0200 Subject: [PATCH 4/4] udpate --- src/dia/attributes/calcAttributes.mjs | 31 +++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/dia/attributes/calcAttributes.mjs b/src/dia/attributes/calcAttributes.mjs index 0bd961e97..ae35b36bb 100644 --- a/src/dia/attributes/calcAttributes.mjs +++ b/src/dia/attributes/calcAttributes.mjs @@ -1,7 +1,34 @@ import { isCalcAttribute, evalCalcAttribute } from './calc.mjs'; -const calcAttributesList = ['transform', 'x', 'y', 'cx', 'cy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size']; -const positiveValueList = ['r', 'rx', 'ry', 'width', 'height', 'stroke-width', 'font-size']; +const calcAttributesList = [ + 'transform', + 'x', + 'y', + 'cx', + 'cy', + 'x1', + 'y1', + 'x2', + 'y2', + 'points', + 'd', + 'r', + 'rx', + 'ry', + 'width', + 'height', + 'stroke-width', + 'font-size', +]; +const positiveValueList = [ + 'r', + 'rx', + 'ry', + 'width', + 'height', + 'stroke-width', + 'font-size', +]; const calcAttributes = calcAttributesList.reduce((acc, attrName) => { acc[attrName] = true;