Skip to content

fix(Textbox): minWidth of text, measure lines before wrapping#8994

Closed
ShaMan123 wants to merge 28 commits into
masterfrom
more-wrap-line-cleanup
Closed

fix(Textbox): minWidth of text, measure lines before wrapping#8994
ShaMan123 wants to merge 28 commits into
masterfrom
more-wrap-line-cleanup

Conversation

@ShaMan123

@ShaMan123 ShaMan123 commented Jun 7, 2023

Copy link
Copy Markdown
Contributor

Motivation

#8990

Wrapping line logic is unreadable and very hard to trace back.
It is needed (by me at least) for overriding so it must be clear and scoped.

Description

minWidth should be shared between lines.
In case there is a text line that is long after a short one, the short line does not know it has more available size to grow to.
This is visible with the resizing control, see attached videos.

I suggest you read this PR commit by commit since they are self explanatory IMO, or not, you can simply read as is.
I consolidated all measuring logic (moved infix measuring to the measuring loop) and extracted it to measureLinesForWrapping, now running before wrapping and not as part of wrapping.
Besides that I cleaned up logic to be readable and to use as much consts and as less switches as possible.

Changes

No changes to functionality.
Exposed measureLinesForWrapping

BREAKING: _wrapLine signature

Gist

In Action

Master

Parcel.Sandbox.-.Google.Chrome.2023-06-08.12-31-11_Trim.mp4

Fixed

Parcel.Sandbox.-.Google.Chrome.2023-06-08.12-36-28_Trim.mp4

@github-actions

github-actions Bot commented Jun 7, 2023

Copy link
Copy Markdown
Contributor

Build Stats

file / KB (diff) bundled minified
fabric 923.055 (+1.488) 303.743 (+0.688)

@ShaMan123 ShaMan123 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stable and clean

@ShaMan123
ShaMan123 requested a review from melchiar June 7, 2023 01:28

@ShaMan123 ShaMan123 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DONE

@ShaMan123 ShaMan123 added the text label Jun 7, 2023
@ShaMan123
ShaMan123 requested a review from asturur June 7, 2023 01:31

@ShaMan123 ShaMan123 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DONE DONE DONE!

Comment thread src/shapes/Textbox.ts
if (i === 0 || lineWidthAfter - additionalSpace > maxWidth) {
// push a new line, we spread to protect `data` from mutation
lines.push([...graphemes]);
lineWidth = width;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lineWidth = width;
lineWidth = infixWidth + width;

If someone decides to override measureLineForWrapping and return an infix we should respect it.

something like:

some words some words some words some words 
some words some words ....
    a line with an infix at the start

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and then we should push the infix if it exists

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? an infix in this case is just something between parts of text. and is either nothing or a space right now.

measureLines shouldn't change the lines imho, if someone does that is going on its own road with no guarantee of support

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's abandon this idea.

measureLines shouldn't change the lines imho, if someone does that is going on its own road with no guarantee of support

I don't understand this

Comment thread src/shapes/Textbox.ts Outdated
Comment thread src/shapes/Textbox.ts Outdated
@ShaMan123

ShaMan123 commented Jun 7, 2023

Copy link
Copy Markdown
Contributor Author

I now understand what I dislike about text logic.
The lifecycle of measuring, layout, rendering is a complete mess.
Some measuring is done here some there.
It should be strict measuring, layout and finally rendering.
But that is a big effort.

@melchiar

melchiar commented Jun 7, 2023

Copy link
Copy Markdown
Contributor

I now understand what I dislike about text logic. The lifecycle of measuring, layout, rendering is a complete mess. Some measuring is done here some there. It should be strict measuring, layout and finally rendering. But that is a big effort.

In an ideal world measurements would also be cached, though that mean adding cache invalidation and a lot more work

@ShaMan123

Copy link
Copy Markdown
Contributor Author

Not really,
initDimensions is the place everything originates from and it calls clearCache

@melchiar

melchiar commented Jun 7, 2023

Copy link
Copy Markdown
Contributor

I guess the real question then is how much of a performance hit measuring actually causes. Even if it's small these methods get called many times especially when there's lots of text

@ShaMan123

Copy link
Copy Markdown
Contributor Author

horrible English, sorry:

I don't think so. I was expecting __charBounds to be populated after running initDimensions, but it is not

@asturur

asturur commented Jun 7, 2023

Copy link
Copy Markdown
Member

I don't think so. I was expecting __charBounds to be populated after running initDimensions, but it is not.
Also __lineWidth, __lineHeight all that mess, why not measure everything and store it?

Because charbounds has been added years after __lineWidth and __lineHeight already existed.
I would expect charBounds to be populated, not sure why isn't.
It could be that we measure the chars only if kerning information is necessary.
If a word is all of the same color and we don't need to do kerning manually we measure the whole word and we don't get charbounds.
I don't remember sincerly.

Many PRs aren't merged because in order:

  • i m not adding features easily
  • when we focused on TS we lost track of a lot of old work
  • we are not adding anything anyway now
  • too many changes all together in general i just get more not wanting changes

@ShaMan123

Copy link
Copy Markdown
Contributor Author

I understand.
My point is it is really hard to work on text.
It is a no touch thing. And that is a shame. Because it is really powerful and has lots of potential.

@asturur

asturur commented Jun 7, 2023

Copy link
Copy Markdown
Member

@ShaMan123 going back to the focus of merging this PR, there are some red flags for me.
Together with the split you changed a bunch of for loops in reduce, added some safeguard mutation, and things i don't see yet.
Are those needed by you? if you need to split the method, split the method and remove the rest. We just fixed a bug in text yesterday because we changed some code with some code code we considered equivalent and we didn't see the bug for weeks

If the changes are required because you are splitting the class method not in half, but in many parts and taking some of them in a different order from the original, then we are not simply extracting some code in a method.

@asturur

asturur commented Jun 7, 2023

Copy link
Copy Markdown
Member

I understand. My point is it is really hard to work on text. It is a no touch thing. And that is a shame. Because it is really powerful and has lots of potential.

yes Text is hard, is fragile and the main focus after 6.0 should be RTL and vertical text ( that you started long ago ), and proper emoji support.
All the rest is really minor imho.

@ShaMan123

Copy link
Copy Markdown
Contributor Author

After extracting measuring the rest seems really simple/natural but I get your point.
This is the first time I understand _wrapLine. I read it many times and never got it.
The reason I made reduce instead of loops was for variable scoping.
I prefer adding tests instead of re-introducing the switches of ifs and cases.
I can also check coverage and make it 100% if that makes you feel better.

Please suggest a way forward for this PR.

@ShaMan123 ShaMan123 changed the title chore(Textbox): More wrap line cleanup fix(Textbox): minWidth of text, measure lines before wrapping Jun 8, 2023
@ShaMan123

Copy link
Copy Markdown
Contributor Author

I have found and fixed a bug so now I have a good excuse to merge this
Updated description and added a test that is broken on master

@ShaMan123 ShaMan123 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the measuring logic and managed to fix a bug due to it

Comment thread src/shapes/Textbox.ts
Comment on lines +271 to +273
if (minWidth + reservedSpace > this.dynamicMinWidth) {
this.dynamicMinWidth = minWidth - additionalSpace + reservedSpace;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part I don't understand.
When I do I can make this and maxWidth a bit more tidy and add some comments

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i remember correctly maxWidth is the largest space we have at our disposal.
The larges between what we want the texbox to be, or the size of the smallest word.

Comment thread src/shapes/Textbox.ts
return value.split(this._wordJoiners);
}

measureLinesForWrapping(lines: string[]) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the naming

Comment thread src/shapes/Textbox.ts
infix: string | null;
infixWidth: number;
}[],
minWidth: 0,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure this is useful

Comment thread test/unit/textbox.js
var line2 = textbox._wrapText([''], 100, 0);
assert.deepEqual(line2, [[]], 'wrapping with splitByGrapheme');
});
QUnit.test('wrapping respects max line width', function (assert) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broken on master!

@ShaMan123
ShaMan123 requested a review from melchiar June 8, 2023 06:20
@ShaMan123

Copy link
Copy Markdown
Contributor Author

If you don't like reduce and prefer for loops I will do that.

Comment thread src/shapes/Textbox.ts
_wrapText(lines: string[], desiredWidth: number, reservedSpace = 0) {
const wrapped: string[][] = [];
this.isWrapping = true;
const { lines: data, minWidth } = this.measureLinesForWrapping(lines);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should cache this and recalc only when text/styles change.
This will make resizing a lot faster

@asturur

asturur commented Jun 9, 2023

Copy link
Copy Markdown
Member

So i put the master code side to side with this PR an i can't understand wich part you extracted.
It seems to me you didn't. I checked the old code and it does this:

It wrap a single line at time.
it takes the line, split all the words, measure each word.
now that knows all the words length in a single line ( but not across lines ) split the single line in one or more line for the textbox wrapper.

the pr change more between last 2 days, and now is completely changing the process of text wrapping in order to early catch the minimum word width to avoid extra breaking in lines, since a long word on the last line could make you wrapping in the early lines not optimal.

I wouldn't call it even a bug, but i understand we want to fix it.

That is a bug and can be fixed, and that is the only part i understand of this PR.
All the other changes are obscure to me.

I can propose, let's see where the bugfix bring us, let's check again the need of dividing the measurement with the word measurement exposed when the bugfix is merged

@asturur

asturur commented Jun 9, 2023

Copy link
Copy Markdown
Member

let me know i i understood the largest word width / min width bug
i tried to fix it here just moving code around.
#9004
My laptopm is discharing and i dont have a charger ( issue i should fix tomorrow ).
Let's fix the bug first and then try to explain what you want from the extra measure method

@ShaMan123

Copy link
Copy Markdown
Contributor Author

You understand the width breaking line issue correctly

@github-actions

Copy link
Copy Markdown
Contributor

Coverage after merging more-wrap-line-cleanup into master will be

83.74%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
index.node.ts7.69%100%0%14.29%17, 20, 23, 35, 38, 41
src
   ClassRegistry.ts100%100%100%100%
   Collection.ts94.71%94.64%86.67%97.09%101, 104, 207–208, 233–234
   CommonMethods.ts96.55%87.50%100%100%10
   Intersection.ts100%100%100%100%
   Observable.ts87.23%85.29%84.62%89.36%144–145, 170–171, 39–40, 48, 57, 91, 99
   Point.ts100%100%100%100%
   Shadow.ts98.36%95.65%100%100%178
   cache.ts97.06%90%100%100%57
   config.ts75%66.67%66.67%82.76%130, 138, 138, 138, 138, 138–140, 151–153
   constants.ts100%100%100%100%
src/Pattern
   Pattern.ts92.31%91.89%90%93.55%118, 129, 138, 31, 94
src/brushes
   BaseBrush.ts100%100%100%100%
   CircleBrush.ts0%0%0%0%108, 108, 108, 110, 112, 114–116, 118–121, 128–129, 136, 138, 23–24, 32–36, 40–44, 51–54, 62–66, 68, 76, 76, 76, 76, 76–77, 79, 79, 79–82, 84, 92–93, 95, 97–99
   PatternBrush.ts97.06%87.50%100%100%21
   PencilBrush.ts91.01%82.35%100%93.75%122–123, 152, 152–154, 176, 176, 276, 280, 285–286, 68–69, 84–85
   SprayBrush.ts0%0%0%0%107, 107, 107, 107, 107–108, 110–111, 118–119, 121, 123–127, 136, 140–141, 141, 149, 149, 149–152, 154–157, 161–162, 164, 166–169, 17, 172, 179, 18, 180, 182, 184–185, 187, 194–195, 197–198, 20, 201, 201, 208, 208, 21, 212, 22, 22, 22–24, 28, 37, 44, 51, 58, 65, 84–86, 94–96, 98–99
src/canvas
   Canvas.ts79.05%77.54%83.05%79.57%1000–1001, 1001, 1001–1003, 1005–1006, 1006, 1006, 1008, 1016, 1016, 1016–1018, 1018, 1018, 1024–1025, 1033–1034, 1034, 1034–1035, 1040, 1042, 1073–1075, 1078–1079, 1083–1084, 1197–1199, 1202–1203, 1276, 1395, 1515, 161, 186, 296–297, 300–304, 309, 332–333, 338–343, 36, 363, 363, 363–364, 364, 364–365, 373, 378–379, 379, 379–380, 382, 391, 397–398, 398, 398, 40, 441, 449, 453, 453, 453–454, 456, 538–539, 539, 539–540, 546, 546, 546–548, 568, 570, 570, 570–571, 571, 571, 574, 574, 574–575, 578, 587–588, 590–591, 593, 593–594, 596–597, 609–610, 610, 610–611, 613–618, 624, 631, 668, 668, 668, 670, 672–677, 683, 689, 689, 689–690, 692, 695, 700, 713, 741, 741, 799–800, 800, 800–801, 803, 806–807, 807, 807–808, 810–811, 814, 814–816, 819–820, 890, 902, 909, 930, 962, 983–984
   SelectableCanvas.ts94.42%92.24%94.23%96.15%1115, 1115–1116, 1119, 1139, 1139, 1188, 1196, 1315, 1317, 1319–1320, 519, 694–695, 697–698, 698, 698, 747–748, 809–810, 863–865, 897, 902–903, 930–931
   StaticCanvas.ts96.73%92.88%100%98.52%1037–1038, 1038, 1038–1039, 1159, 1169, 1223–1224, 1227, 1262–1263, 1339, 1348, 1348, 1352, 1352, 1399–1400, 309–310, 326, 694, 706–707
   TextEditingManager.ts84.31%71.43%91.67%88%17–18, 18, 18–19, 19, 19
src/canvas/DOMManagers
   CanvasDOMManager.ts95.52%70%100%100%21–22, 29
   StaticCanvasDOMManager.ts100%100%100%100%
   util.ts86.67%80.56%83.33%93.94%14, 26, 63–64, 67, 67, 74, 93–94
src/color
   Color.ts94.96%91.67%96.30%96.05%233, 258–259, 267–268, 48
   color_map.ts100%100%100%100%
   constants.ts100%100%100%100%
   util.ts85.71%76.92%100%89.74%55–56, 56, 58, 58, 58–59, 61–62, 89
src/controls
   Control.ts93.33%87.88%91.67%97.78%175, 240, 327, 327, 362
   changeWidth.ts100%100%100%100%
   commonControls.ts100%100%100%100%
   controlRendering.ts81.63%78%100%84.78%106, 111, 121, 121, 45, 50, 61, 61, 65–72, 81–82
   drag.ts100%100%100%100%
   fireEvent.ts88.89%75%100%100%13
   polyControl.ts5.97%0%0%11.11%100, 105, 119, 119, 119, 119, 119, 121–124, 124, 127, 134, 17, 25–29, 29, 29, 29, 29, 29, 29, 29, 50–56, 56, 56, 56, 56, 58, 63–64, 66, 76, 82–83, 83, 83–84, 88–90, 90, 90, 90, 90, 92
   rotate.ts19.57%12.50%50%21.43%41, 45, 51, 51, 51–52, 55–57, 59, 59, 59, 59, 59–61, 61, 61–63, 65, 65, 65–67, 67, 67–68, 73, 73, 73–74, 76, 78, 80–81
   scale.ts93.57%92.94%100%93.67%129–130, 132–134, 148–149, 181–183, 42
   scaleSkew.ts78.79%64.29%100%85.71%27, 29, 29, 29, 31, 33, 35
   skew.ts91.03%79.31%100%97.67%131–132, 163–164, 171, 177, 179
   util.ts100%100%100%100%
   wrapWithFireEvent.ts100%100%100%100%
   wrapWithFixedAnchor.ts100%100%100%100%
src/env
   browser.ts84.21%77.78%50%100%14, 17
   index.ts100%100%100%100%
   node.ts74.07%33.33%66.67%88.89%

@asturur asturur closed this Jun 18, 2023
@ShaMan123

Copy link
Copy Markdown
Contributor Author

Not sure I agree with closing
We should extract more stuff from here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants