Skip to content

Remove Y-axis rounding for lines in LibTxt and floating point hack rounding in framework #31707

Description

@GaryQian

Text is currently being rounded in two places:

  • LibTxt rounds as a legacy feature (from blink) to better align vertical layouts with pixel boundaries to produce sharper text on low-DPI devices. This is no longer necessary as device DPI has increased significantly in recent years, and we now prefer more accurate layout.

  • TextPainter applies a rounding we call _applyFloatingPointHack to metrics. The reasoning is probably best explained by the todo:

    // Unfortunately, using full precision floating point here causes bad layouts
    // because floating point math isn't associative. If we add and subtract
    // padding, for example, we'll get different values when we estimate sizes and
    // when we actually compute layout because the operations will end up associated
    // differently. To work around this problem for now, we round fractional pixel
    // values up to the nearest whole pixel value. The right long-term fix is to do
    // layout using fixed precision arithmetic.

We should remove such rounding sooner rather than later, as it will result in < 0.5px (logical) shifts across almost all text in Flutter. Such a change should eventually be made, and the earlier, the less disruption it can cause.

Activity

  1. added
    a: typographyText rendering, possibly libtxt
    c: API breakBackwards-incompatible API changes
    a: qualityA truly polished experience
    engineflutter/engine related. See also e: labels.
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Apr 27, 2019
  2. GaryQian commented on Apr 27, 2019

    @GaryQian
    ContributorAuthor
  3. Hixie commented on May 2, 2019

    @Hixie
    Contributor

    I recommend trying to just remove it all and seeing what happens. I would not be overly surprised if nothing noticeably changed in the common case.

  4. added this to the milestone on May 2, 2019
  5. knopp commented on May 2, 2019

    @knopp
    Member

    Removing _applyFloatingPointHack would be very nice indeed. I suspect it would improve jumpy text in CupertinoNavigationBar when sliding back. However I do remember trying to remove it a while ago and immediately got hit by tiny overflows.

  6. GaryQian commented on May 2, 2019

    @GaryQian
    ContributorAuthor

    Removing _applyFloatingPointHack is more likely to cause overflows than removing the rounding in LibTxt. Flutter often lays out with the ground truth being the LibTxt metrics, whereas the framework-side rounding are operations on top of this already rounded ground truth.

    Changing either will require a fairly time consuming pass of adjusting hard-coded test values across all of our tests and goldens.

    I removed rounding in LibTxt as an experiment, and I am seeing about ~150 broken values in txt_unittests and 104 broken tests in the framework tests. It causes the text to visually shift slightly, which is most noticeable when there are many lines of text, although this shifting can be considered an improvement in both accuracy and precision.

  7. GaryQian commented on May 2, 2019

    @GaryQian
    ContributorAuthor

    Screenshot_2019-05-02-13-44-45-585_io flutter demo gallery

    This is what happens with a simple removal of _applyFloatingPointHack with no other changes.

    Removing rounding in LibTxt looks harmless in terms of layout though and I should be able to proceed with that in a fairly straightforwards (if not tedious) manner.

    Removing both instances of rounding results in 120 broken framework tests, and incorrect layout.

  8. knopp commented on May 2, 2019

    @knopp
    Member

    If you remove the _applyFloatingPointHack then you need to make sure in each layout calculation that you don't overflow. Also it would be nice to ensure that a Text widget will not shift next widget to subpixel position, as that might cause blurry rendering of images on lower dpi devices. Although maybe that already happens when devicePixelRatio is not integer and widgets don't land on pixel boundaries.

  9. Hixie commented on May 3, 2019

    @Hixie
    Contributor

    I don't understand why removing the hack would make the "SUBMIT" text wrap. We should be setting the width of the paragraph to exactly the width we get back from the layout, which shouldn't trigger another layout of the paragraph. In fact, the fact that it's even possible for this to happen is concerning, because there shouldn't be a codepath by which we can lay out the text twice. We should lay it out once, get the metrics, and paint it, without any possibility of the metrics or layout changing again.

  10. jason-simmons commented on May 3, 2019

    @jason-simmons
    Member

    The second pass of layout is done by TextPainter.layout after the first pass obtains the maxIntrinsicWidth. See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/painting/text_painter.dart#L431

    If you remove _applyFloatingPointHack, then the second pass of layout may result in word wrap because Paragraph::layout in the engine is truncating the width passed into the framework:
    https://github.com/flutter/engine/blob/master/third_party/txt/src/txt/paragraph.cc#L486

    The floor(width) originated from an attempt to be consistent with Blink's behavior (including the interaction between _applyFloatingPointHack and Blink). See flutter-team-archive/engine#5962 and #18665.

  11. 10 remaining items

  12. olof-dev commented on Sep 21, 2020

    @olof-dev
    Contributor

    Can I check what the status of this is? Strange rounding is precisely the type of thing that I suspect might be behind a number of small-but-noticeable text rendering errors I've seen with baseline-alignments. Two example scenarios:

    1. For a Text widget with a given font, at a certain font size the relative alignment of the text on the baseline might be correct, but at a different font size it can be fractionally below the baseline. Screenshot, with the same font at size 17.0 and size 20.0 (heavily zoomed in in the emulator, with debugPaintBaselinesEnabled = true):

    TextBaselines

    1. Incorrect baseline alignments between text and widgets in a Text.rich. This seems to happen particularly when there are multiple lines involved. Despite text and the WidgetSpans being set to be aligned by alphabetic baseline, the alignment can be correct on some lines while incorrect on others (with the exact same text and widgets in the different WidgetSpans).

    It could be that these issues are caused by something else, but the mismatch in baseline alignment is in both cases very small, and rounding somewhere seems a likely issue.

  13. GaryQian commented on Mar 14, 2022

    @GaryQian
    ContributorAuthor

    cc @mdebbar @jason-simmons

    This issue still appears to be a problem after SkParagraph. Is this still viable to fix/remedy?

  14. shilangyu commented on Aug 10, 2022

    @shilangyu

    Is it currently possible to get the real size without the hack being applied to it? Assuming one would be willing to accept the dangers of floating point

  15. LongCatIsLooong commented on Aug 10, 2023

    @LongCatIsLooong
    Contributor

    There're still a few cleanup tasks left, but the rounding is now disabled.

  16. added
    r: fixedIssue is closed as already fixed in a newer version
    on Aug 11, 2023
  17. github-actions commented on Aug 25, 2023

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  18. locked as resolved and limited conversation to collaborators on Aug 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: qualityA truly polished experiencea: typographyText rendering, possibly libtxtc: API breakBackwards-incompatible API changesc: contributor-productivityTeam-specific productivity, code health, technical debt.engineflutter/engine related. See also e: labels.frameworkflutter/packages/flutter repository. See also f: labels.r: fixedIssue is closed as already fixed in a newer versionteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions