Files
Unlucky-Launcher/crates/design/src/typography/emphasized.rs
T
AskaEth 83e19e58f0 fix(design): M61 r1 收口 —— 官方 emphasized/弹簧/高程 + 冻结测试
reviewer r1 判 p2-2items(fix-then-merge),本轮按条目收口:

P2-1 冻结测试补全(变异实测证明原来可静默漂移):
- tests/type_scale_pins.rs:baseline 15 档 (size,line_height,weight,tracking,family)
  与 Expressive 强调 15 档逐值对拍,期望值独立于实现重写
- tests/token_pins.rs:49 条 tone(light+dark 全表)、7 条 easing 曲线参数、
  16 档时长、9 组弹簧、28 个 IconKind 下标、形状阶梯
- 自证:TitleMedium 16→17 与 EmphasizedDecelerate 换曲线两次变异均 FAILED

P2-2 emphasized 口径事实错误(用户点名):
- 取回 A 级官方值(androidx TypeScaleTokens v0_103 的 *Emphasized* 四元组):
  label-*/title-medium/title-small 强调是 700(此前给 600)、body-large 0.5→0.15、
  title-medium 0.2→0.15、body-medium 0.2→0.25、display-large -0.2→0
- TypeRole::emphasized() 改为官方表(typography/emphasized.rs);
  原「只加一档字重」规则改名 emphasized_simplified() 并标注非官方
- 顺带按官方订正 baseline tracking(display-large -0.2、body-medium 0.2、
  title-medium 0.2)与 TitleLarge 的 Brand 族归属
- crate 文档新增「baseline MD3 vs M3 Expressive 差异清单」+ 未支持能力 gap

弹簧改为两套 token 结构(用户追加令):
- motion::spring(scheme × category × speed) 二维表 12 格:
  standard spatial ζ=0.9 (k 1400/700/300)、expressive spatial ζ=0.6~0.8 (k 800/380/200)、
  effects 两套同值 ζ=1.0 (k 3800/1600/800),均取 A 级 MotionTokens 原值
- Spring 改存阻尼比(与 Compose 令牌同口径),9 个具名常量

中文字重覆盖(用户追加令):
- FontFace 带 WeightCoverage(可变区间/静态表),Platform::resolve_weight 给
  Native/Nearest/Synthetic 三档结论;雅黑无 500、苹方无 700 两处 gap 显式登记

component_map 高程按官方回调(P2 并入 finding):
- FilledCard L2→L0、OutlinedCard L1→L0、Menu L4→L2、Dialog L5→L3(Menu 圆角同时
  按 MenuTokens 改 4dp);新增官方值 pin 测试 + R4「全屏都在飘」守门断言

P3 四条:
- 同族容器撞色:彩度按共享基准等比压缩(Palettes.reference_chroma),
  primary/secondary-container 不再同值,且强调色覆盖时表面逐像素不变
- femtovg 引用改函数名(行号已漂);删掉 contrast_aa 的死代码后门
- 骨架数值跟 M63:nav 56→44、titlebar 40→48,control 40/touch 48 换算依据入文档
- 图标折算口径订正为「居中描边,视觉外扩 <1px」
2026-11-01 15:58:19 +08:00

89 lines
4.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 官方 M3 **Expressive** 强调排版表(15 档,A 级来源)。
//!
//! 来源:`androidx.compose.material3.tokens.TypeScaleTokens` v0_103 的
//! `<Role>Emphasized{Size,LineHeight,Tracking,Weight}` 四元组。要点:
//!
//! - **字号与行高与 baseline 完全相同**(Expressive 的强调不改变排版高度);
//! - 只动**字重**与**字距**:display/headline/title-large/body-* → Medium(500);
//! title-medium / title-small / label-* → Bold(700)(baseline 是 500);
//! - display-large 的字距从 −0.2 回 0;body-large 0.5 → 0.15;title-medium 0.2 → 0.15;
//! body-medium 0.2 → 0.25(唯一一个「强调后字距变大」的档)。
use super::{style, FontFamily, TypeRole, TypeStyle, Weight};
/// 取某档的官方强调样式。非 [`TypeRole`] 的每个变体都有值(15/15,无缺口)。
pub(super) const fn style_of(role: TypeRole) -> TypeStyle {
match role {
TypeRole::DisplayLarge => style(57.0, 64.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::DisplayMedium => style(45.0, 52.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::DisplaySmall => style(36.0, 44.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::HeadlineLarge => style(32.0, 40.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::HeadlineMedium => style(28.0, 36.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::HeadlineSmall => style(24.0, 32.0, Weight::Medium, 0.0, FontFamily::Display),
// TitleLarge 官方用 Brand 族(与 Display 同栈)。
TypeRole::TitleLarge => style(22.0, 28.0, Weight::Medium, 0.0, FontFamily::Display),
TypeRole::TitleMedium => style(16.0, 24.0, Weight::Bold, 0.15, FontFamily::Text),
TypeRole::TitleSmall => style(14.0, 20.0, Weight::Bold, 0.1, FontFamily::Text),
TypeRole::BodyLarge => style(16.0, 24.0, Weight::Medium, 0.15, FontFamily::Text),
TypeRole::BodyMedium => style(14.0, 20.0, Weight::Medium, 0.25, FontFamily::Text),
TypeRole::BodySmall => style(12.0, 16.0, Weight::Medium, 0.4, FontFamily::Text),
TypeRole::LabelLarge => style(14.0, 20.0, Weight::Bold, 0.1, FontFamily::Text),
TypeRole::LabelMedium => style(12.0, 16.0, Weight::Bold, 0.5, FontFamily::Text),
TypeRole::LabelSmall => style(11.0, 16.0, Weight::Bold, 0.5, FontFamily::Text),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emphasized_keeps_size_and_line_height() {
for role in TypeRole::ALL {
let base = role.style();
let emph = style_of(role);
assert_eq!(
base.size_px,
emph.size_px,
"{} 强调档不该改字号",
role.name()
);
assert_eq!(
base.line_height_px,
emph.line_height_px,
"{} 强调档不该改行高",
role.name()
);
}
}
#[test]
fn emphasized_weights_follow_the_official_split() {
// 官方两档:正文/展示类 → Medium(500);小标题与标签类 → Bold(700)。
for role in TypeRole::ALL {
let expect = match role {
TypeRole::TitleMedium
| TypeRole::TitleSmall
| TypeRole::LabelLarge
| TypeRole::LabelMedium
| TypeRole::LabelSmall => Weight::Bold,
_ => Weight::Medium,
};
assert_eq!(
style_of(role).weight,
expect,
"{} 强调档字重不对",
role.name()
);
}
}
#[test]
fn display_large_tracking_returns_to_zero() {
assert_eq!(TypeRole::DisplayLarge.style().letter_spacing_px, -0.2);
assert_eq!(TypeRole::DisplayLarge.emphasized().letter_spacing_px, 0.0);
assert_eq!(TypeRole::BodyLarge.style().letter_spacing_px, 0.5);
assert_eq!(TypeRole::BodyLarge.emphasized().letter_spacing_px, 0.15);
}
}