Repository: clientIO/joint (JavaScript)

Issue:
[Bug]: textWrap is not calculating properly text size when using custom fonts / size (Bootstrap repro)
### What happened?

It seems that textWrap isn't working as expected when using css styling. A simple repro: https://codesandbox.io/s/textwrap-bug-76f2md?file=/src/index.js

Note that commenting the bootstrap import fixes the issue.

![image](https://github.com/clientIO/joint/assets/89727/5d4872d0-c46d-4dd6-990e-14028cc0bf3f)


### Version

3.7.5

### What browsers are you seeing the problem on?

Firefox, Chrome, Safari

### What operating system are you seeing the problem on?

Mac


Discussion: Now you must explicitly set all font-related attributes in the `<text>` element for the `textWrap` function to work correctly. Back then, this decision was made consciously for performance reasons.

Now it is probably a good time to use the `getComputedStyle()` function to retrieve the actual font attributes needed to calculate the text wrap.

@kumilingus is this going to make it in the next patch release (3.7.6?) or will it land in 4.0.0? If the later, is there any workaround that I can apply?
Sorry, haven't made the decision yet.

A workaround is to re-define the `textWrap` attribute. It can be done per element, as shown below.

```js
class MyRect extends joint.dia.Element {

    static attributes = {
        textWrap: {
            qualify: joint.util.isPlainObject,
            set: function(value, refBBox, node, attrs) {
                const computedFontSize = parseFloat(getComputedStyle(node).fontSize);
                const computedAttrs = { ...attrs };
                computedAttrs.fontSize = computedFontSize;
                joint.dia.attributes.textWrap.set.call(this, value, refBBox, node, computedAttrs);
            }
        }
    };
}
```

Here's updated version of your codesandbox:
https://codesandbox.io/s/textwrap-bug-forked-svgjq9?file=/src/index.js
Hi! I was finally able to get to this. I tried it, but it doesn't seem to be working as expected. Resizing the cell makes the text overflow the width.

![image](https://github.com/clientIO/joint/assets/89727/7ad082c9-c3bc-4711-978d-4698ca18f118)

The break text depends not only on the font-size, but also on font-family, font-weight, letter-spacing.
```js
  static attributes = {
    textWrap: {
      qualify: joint.util.isPlainObject,
      set: function (value, refBBox, node, attrs) {
        const computedStyles = getComputedStyle(node);
        const fontSize = parseFloat(computedStyles.fontSize);
        const fontFamily = computedStyles.fontFamily;
        const computedAttrs = { ...attrs, fontSize, fontFamily };
        joint.dia.attributes.textWrap.set.call(
          this,
          value,
          refBBox,
          node,
          computedAttrs
        );
      }
    }
  };
```

Updated example: https://codesandbox.io/s/textwrap-bug-forked-svgjq9?file=/src/index.js
Oh, I see 🤔. I was about to ask why / how the different attributes are handled, then I remembered I can see the code 😂. I had a look at the `textWrap:set` function. I see that the following attributes are taken into account when computing the width / height of the text: 

* font-size
* font-family
* font-weight
* line-height
* letter-spacing

I think the following extra attributes should be taken into account:

* word-spacing
* text-transform (text might be capitalized, which would change the width and height of non monospaced fonts)
* text-indent (first line of text might be indented)
* white-space (the handling of white spaces might affect the width)
* text-shadow (I agree that this one might be an edge case, but shadows can affect the width)
I think that only `text-transform` applies to SVG.

IMHO `text-shadow` does not affect the text. It only adds a shadow next to the text. But it is a matter of interpretation.

Note that there is no `line-height` in SVG. It's a property that we introduced in the `Vectorizer.text()` method.



Fix waits for #2335. For the time being please use the workaround.

It's currently not guaranteed that the `font-size` is already set on the SVGNode (because it is a special attribute - because of `calc()`). i.e. the computed style might return incorrect font size (whatever the font size is before the font-size attribute is set).

Another workaround that I've used in our project for a similar problem (body style, including font family/size/weight affected the SVG text but did not get taken into account when calculating wrapping) is to set the text attributes explicitly with references to CSS variables. For me this solved the text wrapping without being forced to duplicate values between the CSS file and the TypeScript file containing the shape definition.

E.g.
```
{
  text: {
    fontFamily: 'var(--my-body-font-family)',
    fontWeight: 'var(--my-body-font-weight)',
    fontSize: 'var(--my-body-font-size)',
    textWrap: {
      width: '97%' // padding
    }
  }
}
```

Recent versions of Bootstrap already define CSS vars, so you might be able to use them as-is.
Nice, I didn't know this is even possible: https://jsfiddle.net/kumilingus/qc9geah6/

```css
:root {
  --rect-color: red;  
}
```

```svg
<svg>
  <rect width="100" height="100" fill="var(--rect-color)" />
</svg>
```
I thought this was going to land in 4.0 since it's a breaking change 🤔
maybe in a future 4.x release?
The necessary breaking change was made and the issue can be fixed with a patch now (`4.0.x`).



Fix (patch):
diff --git a/packages/joint-core/src/dia/attributes/text.mjs b/packages/joint-core/src/dia/attributes/text.mjs
index 79928344f..26d9bd867 100644
--- a/packages/joint-core/src/dia/attributes/text.mjs
+++ b/packages/joint-core/src/dia/attributes/text.mjs
@@ -123,12 +123,16 @@ const textAttributesNS = {
             if (text === undefined) text = attrs.text;
             if (text !== undefined) {
                 const breakTextFn = value.breakText || breakText;
+                const computedStyles = getComputedStyle(node);
+
                 wrappedText = breakTextFn('' + text, size, {
-                    'font-weight': attrs['font-weight'],
-                    'font-size': attrs['font-size'],
-                    'font-family': attrs['font-family'],
+                    'font-weight': computedStyles.fontWeight,
+                    'font-family': computedStyles.fontFamily,
+                    'text-transform': computedStyles.textTransform,
+                    'font-size': computedStyles.fontSize,
+                    'letter-spacing': computedStyles.letterSpacing,
+                    // The `line-height` attribute in SVG is JoinJS specific.
                     'lineHeight': attrs['line-height'],
-                    'letter-spacing': attrs['letter-spacing']
                 }, {
                     // Provide an existing SVG Document here
                     // instead of creating a temporary one over again.