flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
162.54k stars 26.72k forks source link

Font weight is too light in light mode and too heavy in dark mode #120857

Open anthonymoretti opened 1 year ago

anthonymoretti commented 1 year ago

Steps to Reproduce

  1. Execute flutter run on the code sample.
  2. Take a screenshot of the Flutter app.
  3. Compose a new message in iOS Mail with the same text.
  4. Take screenshots of iOS Mail in light mode and dark mode.
  5. Compare the text in the screenshots to the screenshot of the Flutter app.

Expected results: The font weights look the same.

Actual results: The font weight in Flutter is too light in light mode and too heavy in dark mode. This makes the app look quite different in one mode compared to the other.

Font weight is too light in light mode, and too heavy in dark mode

Code sample ```dart import 'package:flutter/material.dart'; void main() { runApp(const MainApp()); } class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( alignment: Alignment.center, height: 56.0, child: const Text( 'The quick brown fox jumps over the lazy dog', style: TextStyle( color: Colors.black, fontSize: 17.0, letterSpacing: -0.43, ), ), ), Container( alignment: Alignment.center, color: const Color.fromRGBO(28, 28, 30, 1), height: 56.0, child: const Text( 'The quick brown fox jumps over the lazy dog', style: TextStyle( color: Colors.white, fontSize: 17.0, letterSpacing: -0.43, ), ), ), ], ), backgroundColor: Colors.white, ), ); } } ```
``` [flutter] flutter doctor -v [✓] Flutter (Channel stable, 3.7.3, on macOS 13.2.1 22D68 darwin-x64, locale en-US) • Flutter version 3.7.3 on channel stable at /Users/anthonymoretti/Development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 9944297138 (7 days ago), 2023-02-08 15:46:04 -0800 • Engine revision 248290d6d5 • Dart version 2.19.2 • DevTools version 2.20.1 [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). If the Android SDK has been installed to a custom location, please use `flutter config --android-sdk` to update to that location. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (not installed) • Android Studio not found; download from https://developer.android.com/studio/index.html (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). [✓] VS Code (version 1.75.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (3 available) • iPhone 14 Pro (mobile) • ED506CAA-EB76-47D8-BBDC-751CB0EE7EC8 • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-2 (simulator) • macOS (desktop) • macos • darwin-x64 • macOS 13.2.1 22D68 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.100 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 2 categories. exit code 0 ```
HylkeReinders commented 1 year ago

Hi Anthony,

Just checked your screenshot and i didn't see a difference when checking the widths in Figma.

For the black:

Screenshot 2023-02-16 at 09 50 06

For the White:

Screenshot 2023-02-16 at 09 51 39

What I do see is that with the black text we have a dark border and that is the same for the white text color. That could be lighter? So it looks like the same width?

anthonymoretti commented 1 year ago

Hi Hylke

Thanks for looking into it. Yeah the border color in both light and dark modes needs to be darker, otherwise it looks like the weights are off. The change is especially noticeable when switching from light mode to dark mode and vice versa.

The border color of the letter "h" is a good example. If you compare the border color of each "h" to the one below it you can see how the border color of the bottom one is too light in both cases. If it could be changed to match the border color of the "h" above it that would fix it I think.

h - light mode

h - dark mode

huycozy commented 1 year ago

Hi @anthonymoretti Can you please provide screenshots for the comparison between Flutter and iOS native? Honestly, I've tried to look closely but haven't noticed the difference yet. Should I pay attention to the border of the letters?

anthonymoretti commented 1 year ago

Hi @huycozy Yes, it's the border of the letters, which is too light in both light and dark modes. It creates the effect that text is too thin in light mode and too bold in dark mode.

Here are the screenshots:

iOS Mail - light mode iOS Mail - dark mode Flutter
delfme commented 1 year ago

@huycozy please focus on the leg of "i" or on "k". First row (the thicker one) is native.

Screenshot 2023-02-16 at 14 22 03

I couldn't test and it is not clear if the issue affects only skia, impeller or both engines. However, I remembered this fix landed lately and it would be worth double-checking things work properly on Impeller https://github.com/flutter/flutter/issues/118613

anthonymoretti commented 1 year ago

Hi @delfme. This is with Impeller enabled, but I think it existed before too.

anthonymoretti commented 1 year ago

I realize it might be hard to see, so thought I'd try to make it easier. This is with Impeller, but I think it might have existed before too.

Border color comparison
huycozy commented 1 year ago

Thank you guys for more information. I double-check this and see that it's easier to see this issue in dark mode especially.

Demo

Screenshot 2023-02-17 at 12 01 52

Demo (raw images) | iOS light | iOS dark | Flutter | | --------------- | --------------- | --------------- | | |
Demo video https://user-images.githubusercontent.com/104349824/219555942-991f0710-5cd0-4d51-b7cf-209cf1918c67.MP4

But as @delfme mentioned above, this seems to be fixed with Impeller:

Flutter Skia Flutter Impeller

@anthonymoretti Can you retry on the latest master channel with Impeller and check if see the same result?

anthonymoretti commented 1 year ago

Thanks for looking. I tried on the latest master with Impeller now but it's identical to stable. A second very minor thing, in Flutter there appears to be an extra pixel between "k" and "b", so the total line length is different by one pixel.

The reason I noticed the problem in the first place is because the text in my app appears thinned out in light mode compared to dark mode, and it seems to be because of a font weight imbalance between the modes.

Native, Master, Stable

flutter doctor -v ``` [flutter] flutter doctor -v [✓] Flutter (Channel master, 3.8.0-13.0.pre.74, on macOS 13.2.1 22D68 darwin-x64, locale en-US) • Flutter version 3.8.0-13.0.pre.74 on channel master at /Users/anthonymoretti/Development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 298d8c76ba (89 minutes ago), 2023-02-16 21:28:30 -0800 • Engine revision 6e92c0c284 • Dart version 3.0.0 (build 3.0.0-245.0.dev) • DevTools version 2.21.1 [✗] Android toolchain - develop for Android devices ✗ Unable to locate Android SDK. Install Android Studio from: https://developer.android.com/studio/index.html On first launch it will assist you in installing the Android SDK components. (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). If the Android SDK has been installed to a custom location, please use `flutter config --android-sdk` to update to that location. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (not installed) • Android Studio not found; download from https://developer.android.com/studio/index.html (or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions). [✓] VS Code (version 1.75.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (3 available) • iPhone 14 Pro (mobile) • ED506CAA-EB76-47D8-BBDC-751CB0EE7EC8 • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-2 (simulator) • macOS (desktop) • macos • darwin-x64 • macOS 13.2.1 22D68 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.100 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 2 categories. exit code 0 ```
huycozy commented 1 year ago

Labeling the issue for the team's attention since the issue is also happening on skia (see https://github.com/flutter/flutter/issues/120857#issuecomment-1434118870 or above comments for the demo)

flutter doctor -v (stable and master) ```bash [✓] Flutter (Channel stable, 3.7.3, on macOS 13.0.1 22A400 darwin-x64, locale en-VN) • Flutter version 3.7.3 on channel stable at /Users/huynq/Documents/GitHub/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 9944297138 (17 hours ago), 2023-02-08 15:46:04 -0800 • Engine revision 248290d6d5 • Dart version 2.19.2 • DevTools version 2.20.1 [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-33, build-tools 31.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Users/huynq/Library/Java/JavaVirtualMachines/corretto-1.8.0_302/Contents/Home/bin/java • Java version OpenJDK Runtime Environment Corretto-8.302.08.1 (build 1.8.0_302-b08) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14B47b • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [✓] IntelliJ IDEA Community Edition (version 2022.1.1) • IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.75.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (3 available) • SM T225 (mobile) • R9JT3004VRJ • android-arm64 • Android 13 (API 33) • macOS (desktop) • macos • darwin-x64 • macOS 13.0.1 22A400 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.77 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. ``` ```bash [!] Flutter (Channel master, 3.8.0-13.0.pre.51, on macOS 13.0.1 22A400 darwin-x64, locale en-VN) • Flutter version 3.8.0-13.0.pre.51 on channel master at /Users/huynq/Documents/GitHub/flutter_master ! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision efde350812 (15 minutes ago), 2023-02-15 21:49:06 -0500 • Engine revision a966cf878f • Dart version 3.0.0 (build 3.0.0-240.0.dev) • DevTools version 2.21.1 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-33, build-tools 31.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14B47b • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] IntelliJ IDEA Community Edition (version 2022.1.1) • IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.75.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-x64 • macOS 13.0.1 22A400 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.100 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```
anthonymoretti commented 1 year ago

Can anyone suggest a workaround for this, or is it at all possible to make this P3?

For example, is there a way to access font weights between the existing constants? I could probably work around it if I could access values like FontWeight.w450 in light mode and FontWeight.w350 in dark mode (not necessarily those exact values, it would take experimenting).

The app looks washed out because of thin text in these two scenarios especially:

delfme commented 1 year ago

@anthonymoretti although maybe doable, the font variable trick is not a correct solution.

Idk how this is coded at engine level, but I guess fix should be just increasing/reducing an opacity value such that font border color will look darker in lightmode and lighter in darkmode. If so, it would take a few min.

anthonymoretti commented 1 year ago

@delfme Counterintuitively, the border needs to be darker in both modes 😉

delfme commented 1 year ago

Oh yes my bad. So fix is simpler coz we don’t need to distinguish between cases 👍

andrewescutia commented 10 months ago

Hi, I too am seeing the issue referenced in #127810 and was wanting a bit of clarification on how this will proceed. For the time being we have disabled the Impeller engine within our app as it has reduced the readability on some of our pages. I am uncertain if Impeller will be left as is since it appears it is closer to how native iOS renders text or if it will be adjusted to mirror Skia rendering. If the decision is to leave as is then we will proceed with adjusting font sizes, weights, etc. within our app to accommodate the change.

Thanks.

romanr commented 8 months ago

Isn't this a well-known perceptual issue, and it should be remedied by adjusting font-weight in dark modes?
Therefore it may be a higher-level issue, for Material 3?

Even if border/antialiasing is adjusted, it will still be perceptually different. When looking at a dark background with white text, the eye is more open and receptive to light, making any white text elements appear thicker than they actually are.

anthonymoretti commented 8 months ago

@romanr Yes it is, it's even discussed in the Material Design icons guidance, where they reduce the grade for light icons on a dark background: https://m3.material.io/styles/icons/applying-icons#3ad55207-1cb0-43af-8092-fad2762f69f7

But regardless of perception, if you look at the comparison between native iOS and Flutter in my earlier comment-1433892871 you can see an actual imbalance in Flutter.

delfme commented 8 months ago

Isn't this a well-known perceptual issue, and it should be remedied by adjusting font-weight in dark modes? Therefore it may be a higher-level issue, for Material 3?

Even if border/antialiasing is adjusted, it will still be perceptually different. When looking at a dark background with white text, the eye is more open and receptive to light, making any white text elements appear thicker than they actually are.

It is not eyes trick.

delfme commented 7 months ago

Tested it thoroughly at pixel level and confirm @anthonymoretti discovery. Text is too thin in lightmode and too thick in night mode. In light mode, where there is no impact from a dark background, is evident with bare eye on my iphone pro 13, ios 16.3.1

Cant we just add to border 0.X px in light mode and subtract 0.X px in dark mode? Might this be caused by flutter engine current limits with hairlines?

delfme commented 7 months ago

Fixed this. Just a temporary workaround.

Instead of

Text.rich(
            overflow: overflow,
            maxLines: maxLines,
            style: _textStyle,
            CustomTextSpan(
              text : $text,
              style: _textStyle,
              overflow: overflow,
              maxLines: maxLines,
            ),
          ),

Do this: put 2 texts inside a Stack, one will draw outerborder (adding the required extra px to make it thicker), and one will draw the inner color.

Stack(children: [
          // Draw the inner color
          Text.rich(
            overflow: overflow,
            maxLines: maxLines,
            style: _textStyle,
            CustomTextSpan(
              text : $text,
              style: _textStyle,
              overflow: overflow,
              maxLines: maxLines,
            ),
          ),

          // Draw the outline border
          Text.rich(
          overflow: overflow,
          softWrap: true,
          maxLines: maxLines,
          style: _textStyle.copyWith(
            foreground: Paint()
              ..style = PaintingStyle.stroke
              ..strokeWidth = 0.1
              ..color = Colors.black,
          ),
          CustomTextSpan(
            text : $text,
            style: _textStyle,
            overflow: overflow,
            maxLines: maxLines,
          ),
        ),

      ],);

Final result will be a ticker text.

Here is a result (second pic is thicker text): IMG_2666

IMG_2665

To reproduce same fix for dark mode, the outerborder color must be the one assigned to the parent widget's background color. This way the text will look thinner.

ManuelRauber commented 3 months ago

For example, is there a way to access font weights between the existing constants? I could probably work around it if I could access values like FontWeight.w450 in light mode and FontWeight.w350 in dark mode (not necessarily those exact values, it would take experimenting).

@anthonymoretti Depending on your font, you actually can. And I suspect, depending on the font, using FontWeight is wrong to change the weight of the font.

In my app, I'm using the "Inter" font, which is a variable font face. I did not read much on the Flutter typography page so I was using FontWeight to change the weight of the font.

However, I noticed, that w400 and w500 renders the exact same text. I used an image diff to check and there where no difference at all. I was going to open an issue for that.

But then I read on the documentation again:

When programming a variable font, use the FontVariation class to modify the font’s design axes. The FontVariation class conforms to the OpenType font variables spec.

Then, when you read for static fonts:

Use the following API to programmatically alter a static font (but remember that this only works if the font was designed to support the feature):

FontFeature to select glyphs FontWeight to modify weight FontStyle to italicize

For the variable font, FontWeight is not mentioned, so I suspect using that is not the right approach.

For testing I switched FontWeight.w500 (again, which did not result in a different rendering) to fontVariantions: [FontVariation('wght', 500)] and... it rendered in 500!

Since the font is variable, you can have any weight, like 450 or even 411 if you prefer that.

While there is possibly still an issue with font rendering overall, using the font variations correctly to bump the font weight a tiny bit seems much better then the double rendering workaround by @delfme

delfme commented 3 months ago

@ManuelRauber is San Francisco font variable? Have you got to use a variable font to workaround this issue? Coz result might not be as expected.

The fact is that text on iOS is broken in several minor aspects that all impact for their part. We created a TextWidget where we heavily manipulate text before rendering, not just the outline border mentioned above, but also letterspacing and height. And things get trickier when emojis are inside the text, coz they must be manipulated too in size, height, offset, letterspacing, and also padding (coz emoji on iOS are off-centered).

Until flutter team gets to fix text as a whole, we think there is no easy solution to that problem. Indeed, on iOS I can tell that an app is built in flutter only by looking at text. Involved issues have been open for 2-4 years, hence the fix is tricky and we cannot expect it anytime soon.

anthonymoretti commented 3 months ago

For example, is there a way to access font weights between the existing constants? I could probably work around it if I could access values like FontWeight.w450 in light mode and FontWeight.w350 in dark mode (not necessarily those exact values, it would take experimenting).

@anthonymoretti Depending on your font, you actually can. And I suspect, depending on the font, using FontWeight is wrong to change the weight of the font.

In my app, I'm using the "Inter" font, which is a variable font face. I did not read much on the Flutter typography page so I was using FontWeight to change the weight of the font.

However, I noticed, that w400 and w500 renders the exact same text. I used an image diff to check and there where no difference at all. I was going to open an issue for that.

But then I read on the documentation again:

When programming a variable font, use the FontVariation class to modify the font’s design axes. The FontVariation class conforms to the OpenType font variables spec.

Then, when you read for static fonts:

Use the following API to programmatically alter a static font (but remember that this only works if the font was designed to support the feature):

FontFeature to select glyphs

FontWeight to modify weight

FontStyle to italicize

For the variable font, FontWeight is not mentioned, so I suspect using that is not the right approach.

For testing I switched FontWeight.w500 (again, which did not result in a different rendering) to fontVariantions: [FontVariation('wght', 500)] and... it rendered in 500!

Since the font is variable, you can have any weight, like 450 or even 411 if you prefer that.

While there is possibly still an issue with font rendering overall, using the font variations correctly to bump the font weight a tiny bit seems much better then the double rendering workaround by @delfme

Thanks @ManuelRauber, unfortunately I'm using San Francisco.

zhangwen0510 commented 1 month ago

Any updates?

zhangwen0510 commented 2 weeks ago

Any updates, this feature is really important