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」
89 lines
4.0 KiB
Rust
89 lines
4.0 KiB
Rust
//! 官方 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);
|
||
}
|
||
}
|