Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented May 16, 2023

platformTextScaler doesn't need to be per window, assuming the SP -> DiP mapping is always the same on the same device (unless the user changes the preference), and does not depend on the display device's pixel density (it's confirmed).

Design doc: https://docs.google.com/document/d/1-DlElw3zWRDlqoc9z4YfkU9PbBwRTnc7NhLM97ljGwk/edit#

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added platform-android platform-web Code specifically for the web engine labels May 16, 2023
@LongCatIsLooong LongCatIsLooong force-pushed the android-sync-text-scale branch from f76030e to d58bb16 Compare May 16, 2023 01:42
@LongCatIsLooong LongCatIsLooong force-pushed the android-sync-text-scale branch 6 times, most recently from eb43895 to 1ce8f1c Compare May 16, 2023 20:52
@LongCatIsLooong LongCatIsLooong force-pushed the android-sync-text-scale branch from 1ce8f1c to 37132f2 Compare May 16, 2023 21:28
@LongCatIsLooong LongCatIsLooong changed the title WIP Implementing TextScaler for nonlinear text scaling May 16, 2023
@LongCatIsLooong LongCatIsLooong marked this pull request as ready for review May 16, 2023 23:50
_PlatformConfiguration _configuration = const _PlatformConfiguration();
// The expression is evaluated when `platformTextScaler` is accessed for the
// first time.
late final bool _platformTextScalerImplemented = _getScaledFontSize(14) != -1;
Copy link
Member

Choose a reason for hiding this comment

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

Why these magic numbers: "14" and "-1"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

14 is arbitrarily chosen. -1 is the error code for unimplemented. I'll add that to the comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

even better, define a constant static const int _kUnimplementedErrorCode = -1

/// A callback that is invoked whenever [textScaleFactor] changes value.
/// Deprecated. Will be removed in a future version of Flutter.
///
/// This field is renamed to [onTextScalerChanged].
Copy link
Member

Choose a reason for hiding this comment

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

nit: it is not just a rename, right? The semantics also changed, presumably, it fires when textScaler changes and not when textscalefactor changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now it is just a rename. It is invoked when the user changes the text scaling value. But in this PR we're not exposing textScaleFactor in public APIs so I thought the name "textScaleFactor" could be a bit confusing.

/// [onTextScaleFactorChanged] callback can be used to monitor such changes.
///
/// Instead of directly using this getter, applications should typically use
/// `MediaQuery.scaledFontSizeOf` instead to retrive the scaled font size in a
Copy link
Member

Choose a reason for hiding this comment

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

put MediaQuery.scaledFontSizeOf in [] to link to it in docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that hasn't been added to the framework yet so the name is kinda fictional

Copy link
Contributor

Choose a reason for hiding this comment

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

should this only be mentioned when it is actually in the framework?

///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this callback is invoked.
TextScaler get platformTextScaler {
Copy link
Member

Choose a reason for hiding this comment

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

Just to double check: This is on the PlatformDispatcher because it is a global thing for the entire app? This is not going to be a per-view setting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hasn't been confirmed yet, I'll add a comment once it is.

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong May 18, 2023

Choose a reason for hiding this comment

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

It's been confirmed that this is per Android device not per monitor. I was also told device manufacturers will not be able to override the text scaling curve: maybe we can avoid using FFI/JNI and just hard-code the formula in dart.
For that we will need the Android build version and potentially some other information (it sounds like the curve is going to be different on TVs / watches than on phones). I think I'll ask for more information before proceeding since that sounds like a more performant and easier to implement solution than this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I add the TextScaler interface to the framework, what would be the easiest way to move it to dart:ui?

}

// Returns a negative number when there's an error:
// -1 : GetScaledFontSize is not implemented on the current platform.
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if there's a way to make errors a little more structured to make this API a little more ergonomic and avoid magic numbers like -1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an FFI call so using a primitive type is the easiest imo otherwise it would probably be a bit less efficient. The codomain can't go negative anyways.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if -1 and -2 were constants in dart and constants on the other side of ffi. They would still be magic numbers over the wire but would be logically constants. If we documented that they needed to change together than I think that would be better. I find myself wishing for proto generated constants (comment above) and a LINT.IFCHANGED in google3.

If you do use constants do not forget to update the documentation on line platform_configuration.h link 230 and engine.h line 308.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed error code -2. Is there a way to share the error code definition between java and dart? The -1 literal is used once in each language so would comments that explain the return value suffice?

throw StateError('GetScaledFontSize is not implemented on the platform. This should not be reached.');
// Fallback to linear scaling when there's an error. Relevant error messages
// should already be printed to stderr.
case final double errorCode:
Copy link
Member

Choose a reason for hiding this comment

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

Why does this case exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currently the android implementation returns -2 if the delegate is not set, for example.

Comment on lines 121 to 123
/// Deprecated. Will be removed in a future version of Flutter.
///
/// This field is renamed to [onTextScalerChanged].
Copy link
Member

Choose a reason for hiding this comment

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

I believe, doc comments do not need to be duplicated in the web_ui

}

abstract class TextScaler {
/// Creates a TextScaler.
Copy link
Member

Choose a reason for hiding this comment

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

remove duplicated doc?

}
}

final class _LinearTextScaler implements TextScaler {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to share the implementation of this with regular dart:ui? This is just a straight-up copy, right? Would make this easier to maintain...

Copy link
Member

Choose a reason for hiding this comment

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

Same with _ClampedTextScaler.

Copy link
Member

Choose a reason for hiding this comment

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

(Makes me wonder if dart:ui should maybe just expose the _getScaledFontSize and the TestScaler stuff lives in the framework)...

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong May 17, 2023

Choose a reason for hiding this comment

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

Some stuff are still depending on text scale factor. If we move TextScaler to the framework then we'd have to expose textScaleFactor as part of the public API.

}

final class _SystemTextScaler extends TextScaler {
const _SystemTextScaler(this.textScaleFactor);
Copy link
Member

Choose a reason for hiding this comment

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

Why is the systemTextScaler dependent on the textScaleFactor?

@LongCatIsLooong LongCatIsLooong marked this pull request as draft May 18, 2023 17:48
_PlatformConfiguration _configuration = const _PlatformConfiguration();
// The expression is evaluated when `platformTextScaler` is accessed for the
// first time.
late final bool _platformTextScalerImplemented = _getScaledFontSize(14) != -1;
Copy link
Contributor

Choose a reason for hiding this comment

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

even better, define a constant static const int _kUnimplementedErrorCode = -1

/// [onTextScaleFactorChanged] callback can be used to monitor such changes.
///
/// Instead of directly using this getter, applications should typically use
/// `MediaQuery.scaledFontSizeOf` instead to retrive the scaled font size in a
Copy link
Contributor

Choose a reason for hiding this comment

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

should this only be mentioned when it is actually in the framework?


/// Returns a new [TextScaler] that restricts the scaled font size to within
/// the range `[minScaleFactor * fontSize, maxScaleFactor * fontSize]`.
TextScaler clamp(double minScaleFactor, double maxScaleFactor) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remind me again why we need this method? Also I imagine an app developer would probably more care about the clamping the font size after the scaling instead of clamping the scaling factor?

// should already be printed to stderr.
case final double errorCode:
assert(false, 'GetScaledFontSize failed with $errorCode');
return fontSize * textScaleFactor;
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 a fallback behavior? shouldn't this be handled in embedding?

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

Leaving it to the Android embedder people (i.e. @reidbaker) to give approval if they're ok with the changes there.

return unscaledFontSize;
}

final int unscaledFloor = unscaledFontSize.floor();
Copy link
Member

Choose a reason for hiding this comment

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

I still think we should leave a comment in the code explaining this.

return cachedValue;
}

final double unscaledFontSizeInt = unscaledFontSize.toDouble();
Copy link
Member

Choose a reason for hiding this comment

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

nit: the variable has an Int suffix, but is typed double - shouldn't those two match?

// `unscaledFontSize`.
//
// Currently this is only implemented on newer versions of Android (SDK level
// 34, using the `TypedValue#applyDimension` API). This function returns -1 on
Copy link
Member

Choose a reason for hiding this comment

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

I am slightly confused about the error codes. Line 1366 made it seem as if -1 means you provided an invalid configuration ID, this documents that -1 means the platform doesn't support non-linear text scaling. Is it intentional that both use the same error code?

Copy link
Member

Choose a reason for hiding this comment

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

see also line 1394 below.

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 think I forgot to update the comments. Updated now.

// (`_configuration.configurationId`). Using an incorrect id could result in
// an unrecoverable error.
//
// Returns -1 when the specified configurationId does not match any configuration.
Copy link
Member

Choose a reason for hiding this comment

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

document here that if the platform hasn't provided a configurationId int the _PlatformConfiguration it means this feature is not supported and _getScaledFontSize must not be called?

///
/// @return The scaled font size in logical pixels, or -1 when the given
/// configuration_id did not match a valid configuration.
///
Copy link
Member

Choose a reason for hiding this comment

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

May be worthwhile to call out here as well that this must only be called if you received a non-null configurationId from . And that otherwise the platform doesn't support this kind of scaling.

///
/// @return The scaled font size in logical pixels, or -1 if the given
/// configuration_id did not match a valid configuration.
///
Copy link
Member

Choose a reason for hiding this comment

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

May be worthwhile to call out here as well that this must only be called if you received a non-null configurationId from . And that otherwise the platform doesn't support this kind of scaling.

///
/// @return The scaled font size in logical pixels, or -1 if the given
/// configuration_id did not match a valid configuration.
///
Copy link
Member

Choose a reason for hiding this comment

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

same

private static final String PLATFORM_BRIGHTNESS = "platformBrightness";
private static final String CONFIGURATION_ID = "configurationId";

// When hasNonlinearTextScalingSupport() returns false, this will not be initialized.
Copy link
Member

Choose a reason for hiding this comment

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

Is this because Java will only initialize static fields on first access and when hasNonlinearTextScalingSupport is false this is never accessed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

* Flutter framework invokes a function to access the configuration with a generation identifier,
* this queue finds the configuration with that identifier and also cleans up configurations that
* are no longer needed.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Somewhere (maybe here?) can we leave a breadcrumb to the future Android API that could replace all this and make this simpler? Just as a reminder for future readers of this code.

@LongCatIsLooong
Copy link
Contributor Author

Hi @reidbaker could you take a look when you get a chance? I think I went through all comments but let me know if I missed any.

verify(executor).send(eq("flutter/settings"), messageCaptor.capture(), isNull());
}

@Test
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this test have a config = 34?

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'll give that a try. But iirc Robolectric does not support that api level yet and the test will fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it gave me

io.flutter.embedding.engine.systemchannels.SettingsChannelTest > configurationQueueWorks FAILED
    java.lang.IllegalArgumentException: API level 34 is not available
        at org.robolectric.plugins.UnknownSdk.verifySupportedSdk(UnknownSdk.java:45)
        at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:298)
        at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:66)
        at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:244)

Copy link
Contributor

Choose a reason for hiding this comment

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

But you are calling an api 34 method. If roboletric does not support api 34 then update the dependency. Or at minimum use config minSdk to api 33 so that when api 34 is added this test starts running against api 34.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

None of these methods directly or indirectly called is API 34 only I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

If this code does not indirectly call 34 then shouldnt there be a test for the opposite of setDisplayMetricsDoesNothingOnAPILevel33. Something like setDisplayMetricsDoesXyzBehaviorOnApiLevel34?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

robolectric/robolectric#8404, it seems there's no api 34 support yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

edit: I was mistaken about roboletric api 34 support.

OK, can you file a follow up bug to add a test when we have a roboletric version that supports api 34?

@LongCatIsLooong LongCatIsLooong added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 18, 2023
@auto-submit auto-submit bot merged commit 5741f69 into flutter:main Aug 18, 2023
@LongCatIsLooong LongCatIsLooong deleted the android-sync-text-scale branch August 18, 2023 22:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

autosubmit Merge PR when tree becomes green via auto submit App platform-android platform-web Code specifically for the web engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants