edgeparse_core/pdf/
font_style.rs1pub fn resolve_base_font_name(
5 base_font: Option<&str>,
6 descriptor_font_name: Option<&str>,
7) -> String {
8 if let Some(name) = base_font {
9 let trimmed = name.trim();
10 if !trimmed.is_empty() && trimmed != "Unknown" {
11 return trimmed.to_string();
12 }
13 }
14 if let Some(name) = descriptor_font_name {
15 let trimmed = name.trim();
16 if !trimmed.is_empty() {
17 return trimmed.to_string();
18 }
19 }
20 "Unknown".to_string()
21}
22
23pub fn weight_from_font_name(name: &str) -> Option<f64> {
33 let lower = name.to_lowercase();
34
35 if let Some(idx) = lower.find("wght") {
36 let rest = &lower[idx + 4..];
37 let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
38 if let Ok(raw) = digits.parse::<u64>() {
39 if (1..=1000).contains(&raw) {
40 return Some(raw as f64);
41 }
42 if raw > 65_536 {
44 let css = (raw as f64) / 65536.0;
45 if (1.0..=1000.0).contains(&css) {
46 return Some(css.round());
47 }
48 }
49 }
50 }
51
52 if lower.contains("black") || lower.contains("heavy") {
53 return Some(900.0);
54 }
55 if lower.contains("bold") || lower.contains("semibold") || lower.contains("demibold") {
56 return Some(700.0);
57 }
58 if lower.contains("medium") {
59 return Some(500.0);
60 }
61 if lower.contains("light") {
62 return Some(300.0);
63 }
64 if lower.contains("thin") || lower.contains("hairline") {
65 return Some(100.0);
66 }
67 if lower.contains("regular") || lower.contains("book") || lower.contains("normal") {
68 return Some(400.0);
69 }
70 None
71}
72
73pub fn resolve_font_weight(
75 font_name: &str,
76 descriptor_font_weight: Option<f64>,
77 stem_v: f64,
78) -> f64 {
79 if let Some(w) = descriptor_font_weight {
82 if (1.0..=1000.0).contains(&w) {
83 return w;
84 }
85 }
86 if let Some(w) = weight_from_font_name(font_name) {
87 return w;
88 }
89 if stem_v >= 140.0 {
91 700.0
92 } else if stem_v >= 100.0 {
93 500.0
94 } else {
95 400.0
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn base_font_preferred() {
105 assert_eq!(
106 resolve_base_font_name(Some("Helvetica-Bold"), Some("Other")),
107 "Helvetica-Bold"
108 );
109 }
110
111 #[test]
112 fn falls_back_to_descriptor_name() {
113 assert_eq!(
114 resolve_base_font_name(None, Some("AAAAAA+.SFNS-Regular")),
115 "AAAAAA+.SFNS-Regular"
116 );
117 assert_eq!(
118 resolve_base_font_name(Some("Unknown"), Some("DescName")),
119 "DescName"
120 );
121 }
122
123 #[test]
124 fn weight_from_classic_names() {
125 assert_eq!(weight_from_font_name("Helvetica-Bold"), Some(700.0));
126 assert_eq!(weight_from_font_name("Arial-Black"), Some(900.0));
127 assert_eq!(weight_from_font_name("Courier"), None);
128 }
129
130 #[test]
131 fn weight_from_wght_token_css() {
132 assert_eq!(weight_from_font_name("Family_wght600_opsz"), Some(600.0));
133 }
134
135 #[test]
136 fn weight_from_wght_16_16() {
137 assert_eq!(weight_from_font_name("Font_wght45875200"), Some(700.0));
139 }
140
141 #[test]
142 fn resolve_prefers_fontweight_over_stemv() {
143 let w = resolve_font_weight("AAAA+.SFNS-Regular_wght2580000", Some(600.0), 262.0);
145 assert!((w - 600.0).abs() < 1e-6, "w={w}");
146 }
147
148 #[test]
149 fn resolve_uses_name_when_no_fontweight() {
150 let w = resolve_font_weight("Helvetica-Bold", None, 80.0);
151 assert!((w - 700.0).abs() < 1e-6);
152 }
153
154 #[test]
155 fn resolve_uses_fontweight_when_no_name_signal() {
156 let w = resolve_font_weight("CustomEmbed", Some(600.0), 262.0);
157 assert!((w - 600.0).abs() < 1e-6);
158 }
159}