Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite measureText in writeTextToCanvas #9765

Merged
merged 8 commits into from
Aug 30, 2021
Merged

Rewrite measureText in writeTextToCanvas #9765

merged 8 commits into from
Aug 30, 2021

Conversation

ebogo1
Copy link
Contributor

@ebogo1 ebogo1 commented Aug 29, 2021

Part of #9473.

This PR rewrites the code from Source/ThirdParty/measureText as a helper function in writeTextToCanvas. The point of the helper function is to extend CanvasRenderingContext2D.measureText() with minx and height properties (and truncate bounding box ascent/descent while we're there).

The code is very inspired by the original third party function, but I believe stock ascent/descent values are good for what we're doing and I also think the loop to find minx was wrong in the third party version. We lose support for writeTextToCanvas on Firefox for Android though:

image

@lilleyse Let me know what you think - if it's too close to the original I can rework it some more.

Tested the Labels Sandcastle on:

  • Linux Firefox, Chrome
  • macOS Chrome, Safari
@cesium-concierge
Copy link

Thanks for the pull request @ebogo1!

  • ✔️ Signed CLA found.
  • CHANGES.md was not updated.
    • If this change updates the public API in any way, please add a bullet point to CHANGES.md.
  • ❔ Changes to third party files were made.
    • Looks like a file in one of our ThirdParty folders (ThirdParty/, Source/ThirdParty/) has been added or modified. Please verify that it has a section in LICENSE.md and that its license information is up to date with this new version.
  • ❔ Unit tests were not updated.
    • Make sure you've updated tests to reflect your changes, added tests for any new code, and ran the code coverage tool.

Reviewers, don't forget to make sure that:

  • Cesium Viewer works.
  • Works in 2D/CV.
  • Works (or fails gracefully) in IE11.
@ebogo1 ebogo1 requested a review from lilleyse August 29, 2021 15:23
@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 29, 2021

Noticing a bunch of
Unknown mime type for cesium/measure-text/Build/Documentation/... in the CI logs; should measureText() be marked @private to avoid this?

@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 29, 2021

There are definitely discrepancies between different browsers (noticeable with the "Offset label by distance" option in the Labels Sandcastle) -

Linux Firefox on this branch:
image

Linux Firefox on main
image

Linux Chrome on this branch:
image

Linux Chrome on main:
image

@lilleyse
Copy link
Contributor

Can this be simplified further by using actualBoundingBoxLeft and actualBoundingBoxRight? Ideally we wouldn't need to draw the text to a canvas at all to determine its bounding box.

Here's another relevant StackOverflow issue: https://stackoverflow.com/questions/60347194/how-to-fit-text-to-a-precise-width-on-html-canvas. Assuming we still need a fallback for browsers that don't support actualBoundingBox****, including Firefox for Mobile and browsers that haven't been updated since early 2020, how do those solutions compare to the one here?

Maybe it's my lack of Canvas2D experience showing, but the overall approach taken in writeTextToCanvas feels pretty awkward. Is it possible for a canvas to "auto-expand" when content is written to it instead of trying to determine the perfect width/height upfront? The code was written 9 years ago. Maybe things have changed?

@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 29, 2021

Can this be simplified further by using actualBoundingBoxLeft and actualBoundingBoxRight? Ideally we wouldn't need to draw the text to a canvas at all to determine its bounding box.

Good call; seems to work fine with minx = actualBoundingBoxLeft | 0.

how do those solutions compare to the one here?

It's written differently but the idea is the same; iterating one pixel at a time in the direction of that's being checked (i.e. up/down/left/right).

It looks like the two options are a tradeoff: either we have a cleaner implementation but have inconsistent results in different browsers (and lose some browser support), or have the messier "brute force" implementation but get consistent browser results. If we use the brute force method as a fallback for unsupported browsers, there's also the question of whether the inconsistent results are acceptable; to me they look a bit bad in the above screenshots. I think if there's a fallback in the code at all we may as well use it all the time for consistency.

@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 30, 2021

I think if there's a fallback in the code at all we may as well use it all the time for consistency.

Talked with @lilleyse offline and we agreed. Since the browser implementations are inconsistent we'll use the pixel counting method for now (opened #9767).

var width4 = width * 4;
var i, j;

var ascent, descent;
// Find the number of rows (from the top) until the first non-white pixel
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had to expand these because eslint doesn't like empty code blocks in loops..

@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 30, 2021

looks like CI failed due to the #9769 timeouts..

Source/Core/writeTextToCanvas.js Show resolved Hide resolved
Source/Core/writeTextToCanvas.js Outdated Show resolved Hide resolved
Source/Core/writeTextToCanvas.js Outdated Show resolved Hide resolved
Comment on lines -75 to -77
// textBaseline needs to be set before the measureText call. It won't work otherwise.
// It's magic.
context2D.textBaseline = defaultValue(options.textBaseline, "bottom");
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this no longer relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure why it was - the measureText function does not use the context's baseline anywhere. Maybe 9 years ago it worked differently? I didn't notice any changes when I removed this line.

Source/Core/writeTextToCanvas.js Show resolved Hide resolved
@lilleyse
Copy link
Contributor

Labels, Map Pins, and Clustering sandcastles look identical to main. The glyph spacing still leaves something to be desired, especially on Firefox, but that's out of scope for this PR.

@ebogo1
Copy link
Contributor Author

ebogo1 commented Aug 30, 2021

@lilleyse Should be good for final review. I pushed a small commit to the gulpfile now that there's no third party measureText file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants