Repository: flutter
Issue: FontWeight should subsume `wght` in FontVariation
Labels: framework,engine,a: typography,P3,team-engine,triaged-engine

### Problem

Flutter has two ways to specify the font weight in a TextStyle: you can pass either a `FontWeight` value or a `FontVariation` with variation `wght`. For example these look like `TextStyle(fontWeight: FontWeight.w500)` or `TextStyle(fontVariations: [FontVariation('wght', 500)])`.

The trouble is that, at present, neither of these subsumes the job of the other, and nor do they interact well with each other.

* Setting only `fontWeight` doesn't work if the font that gets used turns out to offer variable weight. This is a pitfall that even Flutter's own Material implementation falls into: https://github.com/flutter/flutter/issues/109308#issuecomment-2067918226

* Setting only a `wght` variation doesn't work, I believe, if the font that gets used turns out *not* to offer variable weight.

* Setting both a `fontWeight` and a `wght` variation can have interacting effects:
  https://github.com/flutter/flutter/issues/136779
  https://github.com/zulip/zulip-flutter/issues/500
  These effects don't follow any documented pattern, and don't seem desirable.

In general it's impossible for an app to know in advance whether the relevant font will offer variable weight: if it's displaying any kind of user-controlled text, the text may be in a variety of scripts, which may not be covered by the app's primary font and may fall back to a variety of other fonts.

As a result, there doesn't seem to be any existing way for a Flutter app to reliably control the font weight of its text, if any of the fonts it uses offer variable weight.


### Proposed solution

(**See [revised proposal below](https://github.com/flutter/flutter/issues/148026#issuecomment-2108758224)** from @Hixie. The changes to `FontWeight` are the same, but a `wght` FontVariation retains a separate role, for backwards compatibility and for use in a particular edge case.)

When `fontWeight` is specified, interpret it as the exact font weight to use, regardless of whether the font offers variable weight and regardless of whether a `wght` variation is also present. Consult any `wght` variation only as a fallback, for compatibility, when `fontWeight` is absent.

In order to support the same fine-grained range of possible weights currently expressed with a `wght` FontVariation, add a constructor `const FontWeight(this.value)` that accepts an int. (The `FontWeight` type is already a class with a field `int value`.)

This is the same API that [CSS offers](https://github.com/flutter/flutter/issues/136779#issuecomment-1947599504): there's just one attribute `font-weight`, it accepts any number from 1 to 1000, and it works regardless of whether the font ultimately used offers variable weight. This design seems to have worked out well for CSS.


### Implementation

I think most of the work to implement this would be in the engine. The framework would pass the engine a single font-weight value from 1 to 1000, rather than both a `fontWeight` encoded as an int from 0 to 8 and a `wght` variation from 1 to 1000; the engine then needs to use that single value when it comes time to actually select fonts.

Fundamentally this is the same sort of job the engine is already successfully handling: if the app said `FontWeight.w500`, and the only otherwise-matching fonts are at weights 400 and 700 (the usual "normal" and "bold" values), then the engine has to somehow pick one of those. What's new is just that the weight might be 517 instead of 500.

For the exact behavior to use with that single weight, one natural model to follow is what CSS specifies for the web. That behavior is described on MDN [here](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide#weight) and [here](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight). It's found in the CSS spec [here](https://drafts.csswg.org/css-fonts/#missing-weights) and at the step beginning "font-weight is matched next" within the [font-matching algorithm](https://drafts.csswg.org/css-fonts/#font-matching-algorithm) (beginning a little before [this example](https://drafts.csswg.org/css-fonts/#ex-weight-400)).

Alternatively, if it turns out there's some relatively easy change that can be made to the existing code to handle this, but it has some other behavior that's different from CSS's, then that's probably fine too.