1use alloc::collections::BTreeMap;
45use alloc::format;
46use alloc::string::String;
47use alloc::vec::Vec;
48
49use text::fontconf::FontConf;
50use text::fontdb::{FontDb, FDFF_ITALIC, FDFF_OBLIQUE, FDFF_VAR};
51use text::VarAxis;
52
53use super::consts::*;
54use super::value::RangeF;
55use super::win::Shared;
56use super::Ui;
57
58const ROW_H: u32 = 28;
59const SEARCH_CAP: u32 = 128;
60const SIZES: [f64; 16] = [8.0, 9.0, 10.0, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 28.0, 36.0, 48.0, 72.0, 96.0];
61const RANGE_PT: RangeF = RangeF::new(1.0, 1638.0, 1.0, 1, 0);
62const SAMPLE: &str = "AaBbYyZz 0123456789";
63const VAR_KEEP: usize = 64;
65
66#[derive(Clone, Debug, PartialEq)]
68pub struct FontSel {
69 pub family: String,
70 pub weight: u32,
71 pub italic: bool,
72 pub size: f64,
74 pub axes: Vec<([u8; 4], f32)>,
78}
79
80impl FontSel {
81 pub fn new(family: &str, size: f64) -> FontSel {
83 FontSel { family: String::from(family), weight: 400, italic: false, size, axes: Vec::new() }
84 }
85
86 pub fn axis(&self, tag: [u8; 4]) -> Option<f32> {
89 self.axes.iter().find(|a| a.0 == tag).map(|a| a.1)
90 }
91
92 pub fn set_axis(&mut self, tag: [u8; 4], v: f32) {
94 self.axes.retain(|a| a.0 != tag);
95 if !v.is_nan() {
96 self.axes.push((tag, v));
97 }
98 }
99}
100
101impl Default for FontSel {
102 fn default() -> FontSel {
103 FontSel::new("sans-serif", 11.0)
104 }
105}
106
107struct StyleRow {
110 face: i32,
111 name: String,
112 weight: u32,
113 italic: bool,
114 axes: Vec<([u8; 4], f32)>,
115}
116
117type VarKey = (String, u32, bool, u64, Vec<([u8; 4], u32)>);
119
120#[derive(Default)]
122struct Pick {
123 open: bool,
124 search: String,
125 filt: Vec<u32>,
126 sel: i32,
127 style: i32,
128 size: i32,
129 work: FontSel,
130 rect: Option<Shared<WinRect>>,
131 fresh: bool, }
133
134#[derive(Default)]
136pub(crate) struct S {
137 db: Option<FontDb>,
138 conf: Option<FontConf>,
139 loaded: BTreeMap<(String, u32, bool), (i32, String)>,
140 vloaded: Vec<(VarKey, i32, String)>,
142 picks: BTreeMap<u32, Pick>,
143 lang_n: u32,
145 registered: Vec<String>,
147}
148
149pub fn font_language() -> String {
151 let code = super::i18n::language_code();
152 super::i18n::user_languages()
153 .into_iter()
154 .find(|u| u.split(['-', '_']).next().is_some_and(|p| p.eq_ignore_ascii_case(&code)))
155 .unwrap_or(code)
156}
157
158fn var_key(sel: &FontSel) -> VarKey {
160 let mut axes: Vec<([u8; 4], u32)> = sel.axes.iter().map(|a| (a.0, a.1.to_bits())).collect();
161 axes.sort_unstable();
162 (sel.family.to_lowercase(), sel.weight, sel.italic, sel.size.to_bits(), axes)
163}
164
165fn row_axes(axes: &[VarAxis], coords: &[f32], italic: bool, opsz_named: bool) -> Vec<([u8; 4], f32)> {
168 let has_ital = axes.iter().any(|a| &a.tag == b"ital" && a.max >= 1.0);
169 let mut out = Vec::new();
170 for (a, &v) in axes.iter().zip(coords) {
171 let mapped = match &a.tag {
172 b"wght" => continue,
173 b"opsz" if !opsz_named => continue,
174 b"opsz" => f32::NAN,
175 b"ital" if italic && a.max >= 1.0 => 1.0,
176 b"slnt" if italic && !has_ital && a.min < 0.0 => a.min,
177 _ => a.def,
178 };
179 if !(mapped - v).abs().le(&0.001) {
180 out.push((a.tag, v));
181 }
182 }
183 out
184}
185
186fn axis_range(a: &VarAxis) -> RangeF {
188 let span = (a.max - a.min) as f64;
189 let (step, dec) = if span >= 50.0 { (1.0, 0) } else if span >= 2.0 { (0.1, 1) } else { (0.01, 2) };
190 RangeF::new(a.min as f64, a.max as f64, step, dec, 0)
191}
192
193fn has_ci(hay: &str, needle: &str) -> bool {
194 needle.is_empty() || hay.to_lowercase().contains(&needle.to_lowercase())
195}
196
197fn style_label(weight: u32, italic: bool) -> &'static str {
199 let tr = super::i18n::tr;
200 match (weight >= 600, italic) {
201 (false, false) => tr("Standard"),
202 (false, true) => tr("Kursiv"),
203 (true, false) => tr("Fett"),
204 (true, true) => tr("Fett Kursiv"),
205 }
206}
207
208impl Ui {
209 fn fonts_db(&mut self) -> &mut FontDb {
210 self.m.fonts.db.get_or_insert_with(|| {
211 let mut db = FontDb::new();
212 let cache = plat::env("jfgui_FONTDIRS").is_none().then_some("cache://fontdb.fdb");
213 if let Some(c) = cache {
214 db.load_cache(c);
215 }
216 #[cfg(not(target_arch = "wasm32"))]
217 db.scan(0);
218 if let Some(c) = cache {
219 db.save_cache(c);
220 }
221 db
222 })
223 }
224
225 pub fn fonts(&mut self) -> &FontDb {
227 self.fonts_db()
228 }
229
230 pub fn font_conf(&mut self) -> &FontConf {
232 self.m.fonts.conf.get_or_insert_with(FontConf::system)
233 }
234
235 fn font_entry(&mut self, sel: &FontSel) -> (i32, String) {
238 let key = (sel.family.to_lowercase(), sel.weight, sel.italic);
239 if let Some((id, fam)) = self.m.fonts.loaded.get(&key).cloned()
240 && id >= 0
241 && self.text.valid(id as u32)
242 {
243 if sel.axes.is_empty() || self.text.var_axes(id as u32).is_empty() {
245 return (id, fam);
246 }
247 let v = self.text.vary(id as u32, &sel.axes);
248 return (if v >= 0 { v } else { id }, fam);
249 }
250 let vk = var_key(sel);
251 if let Some(e) = self.m.fonts.vloaded.iter().find(|e| e.0 == vk)
252 && e.1 >= 0
253 && self.text.valid(e.1 as u32)
254 {
255 return (e.1, e.2.clone());
256 }
257 self.fonts_db();
258 self.font_conf();
259 let lang = font_language();
260 let s = &mut self.m.fonts;
261 let (Some(db), Some(conf)) = (s.db.as_mut(), s.conf.as_ref()) else { return (-1, String::new()) };
262 let Some((fi, syn, fam)) = db.resolve(conf, &sel.family, &lang, sel.weight, sel.italic) else {
263 s.loaded.insert(key, (-1, String::new()));
264 return (-1, String::new());
265 };
266 if db.face(fi).is_none_or(|f| f.flags & FDFF_VAR == 0) {
267 let id = db.load_face(&mut self.text, fi, syn);
268 s.loaded.insert(key, (id, fam.clone()));
269 return (id, fam);
270 }
271 let id = db.load_var(&mut self.text, fi, syn, sel.weight, sel.italic, Some(sel.size as f32), &sel.axes);
272 s.vloaded.retain(|e| e.0 != vk);
273 s.vloaded.push((vk, id, fam.clone()));
274 if s.vloaded.len() > VAR_KEEP {
275 let (_, old, _) = s.vloaded.remove(0);
278 let used = s.vloaded.iter().any(|e| e.1 == old)
279 || s.loaded.values().any(|e| e.0 == old)
280 || self.look.fonts.contains(&(old as u32));
281 if old >= 0 && !used && !self.text.var_coords(old as u32).is_empty() {
282 self.text.unload(old as u32);
283 }
284 }
285 (id, fam)
286 }
287
288 pub fn font_load(&mut self, sel: &FontSel) -> Option<u32> {
290 let (id, _) = self.font_entry(sel);
291 (id >= 0).then_some(id as u32)
292 }
293
294 pub fn font_resolved(&mut self, sel: &FontSel) -> String {
296 self.font_entry(sel).1
297 }
298
299 pub fn font_register(&mut self, family: &str, weight: u32, italic: bool, font: u32) {
303 let s = &mut self.m.fonts;
304 s.loaded.insert((family.to_lowercase(), weight, italic), (font as i32, String::from(family)));
305 if !s.registered.iter().any(|f| f.eq_ignore_ascii_case(family)) {
306 s.registered.push(String::from(family));
307 }
308 }
309
310 pub fn font_registered(&self) -> Vec<String> {
312 self.m.fonts.registered.clone()
313 }
314
315 pub(crate) fn font_registered_face(&self, family: &str, weight: u32, italic: bool) -> Option<u32> {
317 let s = &self.m.fonts;
318 if !s.registered.iter().any(|f| f.eq_ignore_ascii_case(family)) {
319 return None;
320 }
321 s.loaded.get(&(family.to_lowercase(), weight, italic)).filter(|e| e.0 >= 0 && self.text.valid(e.0 as u32)).map(|e| e.0 as u32)
322 }
323
324 pub(crate) fn font_is_registered(&self, family: &str) -> bool {
326 self.m.fonts.registered.iter().any(|f| f.eq_ignore_ascii_case(family))
327 }
328
329 #[cfg_attr(jfgui_dev, track_caller)]
331 pub fn text_in_font(&mut self, font: u32, size_pt: f64, s: &str) {
332 let _src = self.src_scope();
333 let s = super::basic::cstr(s);
334 let dpi = self.wp.rt.dpi as f64;
335 let px = (size_pt * 4.0 / 3.0 * dpi / 96.0 * 256.0).clamp(256.0, 2048.0 * 256.0) as i32;
336 let prev = self.text.get_font();
337 self.text.set_font(font, px);
338 let w = self.text.width(s);
339 let asc = self.text.ascent();
340 let lh = self.text.line_h().max(asc + self.text.descent());
341 let r = self.place(w, lh);
342 let c = self.text_color(TS_BODY);
343 let y = r[1] + ((r[3] - lh) >> 1) + asc;
344 self.cmd_text(r[0], y, s.as_bytes(), font, px, c, r[2]);
345 self.text.set_font(prev.0, prev.1);
346 self.wp.ctx.last_state = 0;
347 self.sem(K_TEXT, 0, Some(s), 0);
348 }
349
350 pub(crate) fn fonts_frame(&mut self) {
352 let n = super::i18n::language() + 1;
353 if self.m.fonts.lang_n != n {
354 self.m.fonts.lang_n = n;
355 self.text.set_fallback_lang(&font_language());
356 }
357 }
358
359 fn font_filter(&mut self, search: &str) -> Vec<u32> {
361 let db = self.fonts_db();
362 (0..db.family_count()).filter(|&i| has_ci(db.family_name(i), search)).map(|i| i as u32).collect()
363 }
364
365 fn font_family_index(&mut self, family: &str) -> i32 {
367 let db = self.fonts_db();
368 (0..db.family_count()).find(|&i| db.family_name(i).eq_ignore_ascii_case(family)).map_or(-1, |i| i as i32)
369 }
370
371 fn font_family_list(&mut self, p: &mut Pick, w: i32, h: i32, close: bool) -> bool {
374 let mut changed = false;
375 self.next_size(w, 0);
376 let mut s = core::mem::take(&mut p.search);
377 let ev = self.textbox(self.id("search"), Some(super::i18n::tr("Suchen")), &mut s, SEARCH_CAP);
378 p.search = s;
379 if ev & EV_ACT != 0 {
380 p.filt = self.font_filter(&p.search.clone());
381 let lid = self.id("list");
382 self.scroll_to(lid, -1, 0);
383 p.sel = if p.filt.is_empty() { -1 } else { 0 };
384 if let Some(&f) = p.filt.first() {
385 p.work.family = String::from(self.fonts_db().family_name(f as usize));
386 changed = true;
387 }
388 }
389 let n = p.filt.len() as i32;
390 let names: Vec<String> = {
391 let db = self.fonts_db();
392 p.filt.iter().map(|&f| String::from(db.family_name(f as usize))).collect()
393 };
394 self.next_size(w, h);
395 let lid = self.id("list");
396 let mut sel = p.sel;
397 let (first, end) = self.list_begin(lid, &mut sel, n, ROW_H);
398 let mut clicked = -1;
399 for row in first..end.min(n) {
400 if self.list_item(row, &names[row as usize]) & EV_ACT != 0 {
401 clicked = row;
402 }
403 }
404 let (lev, _) = self.list_end(&mut sel);
405 p.sel = sel;
406 if clicked >= 0 {
407 p.sel = clicked;
408 p.work.family = names[clicked as usize].clone();
409 changed = true;
410 if close {
411 self.popup_close();
412 }
413 } else if lev & EV_ACT != 0 && (0..n).contains(&p.sel) {
414 p.work.family = names[p.sel as usize].clone();
415 changed = true;
416 }
417 changed
418 }
419
420 fn font_list_reset(&mut self, p: &mut Pick) {
422 p.search.clear();
423 p.filt = self.font_filter("");
424 let fi = self.font_family_index(&p.work.family);
425 p.sel = p.filt.iter().position(|&f| f as i32 == fi).map_or(-1, |k| k as i32);
426 let lid = self.id("list");
427 self.scroll_to(lid, -1, ((p.sel - 4).max(0) as u32 * ROW_H) as i32);
428 }
429
430 #[cfg_attr(jfgui_dev, track_caller)]
432 pub fn font_picker(&mut self, id: u32, sel: &mut FontSel) -> u32 {
433 let _src = self.src_scope();
434 let mut p = self.m.fonts.picks.remove(&id).unwrap_or_default();
435 self.id_push(id);
436 let mut out = 0;
437 self.box_begin(0, BOX_H | BOX_XCENTER, 8);
438 let bid = self.id("family");
439 self.next_size(220, 0);
440 let label = sel.family.clone();
441 let ev = self.dropdown_button(bid, &label);
442 let open = ev & EV_OPEN != 0;
443 if open != p.open {
444 p.open = open;
445 p.work = sel.clone();
446 if open {
447 self.font_list_reset(&mut p);
448 let sid = self.id("search");
449 self.focus(sid);
450 }
451 }
452 if self.flyout_begin(bid, FL_BELOW | FL_MINW) && self.font_family_list(&mut p, 260, 320, true) {
453 if !sel.family.eq_ignore_ascii_case(&p.work.family) {
454 sel.axes.clear(); }
456 sel.family = p.work.family.clone();
457 out |= EV_ACT;
458 }
459 self.flyout_end();
460 let mut size = sel.size;
461 self.next_size(64, 0);
462 if self.number_box_f(self.id("size"), &mut size, &RANGE_PT) & EV_ACT != 0 && size != sel.size {
463 sel.size = size;
464 out |= EV_ACT;
465 }
466 self.box_end();
467 self.id_pop();
468 self.m.fonts.picks.insert(id, p);
469 out
470 }
471
472 #[cfg_attr(jfgui_dev, track_caller)]
474 pub fn font_dialog(&mut self, id: u32, open: &Shared<i32>, sel: &mut FontSel) -> u32 {
475 let _src = self.src_scope();
476 let mut p = self.m.fonts.picks.remove(&id).unwrap_or_default();
477 let rect = p.rect.get_or_insert_with(|| Shared::new(WinRect::new(0, 0, 600, 0, 0))).clone();
478 let mut out = 0;
479 self.id_push(id);
480 let shown = open.get() != 0;
481 if shown && !p.open {
482 p.work = sel.clone();
483 p.style = -1;
484 p.size = SIZES.iter().position(|&s| s == p.work.size).map_or(-1, |k| k as i32);
485 p.fresh = true;
486 }
487 p.open = shown;
488 if self.window_begin(id, super::i18n::tr("Schriftart"), Some(open), WF_DIALOG | WF_MODAL | WF_AUTOHEIGHT, Some(&rect)) {
489 if p.fresh {
490 p.fresh = false;
491 self.font_list_reset(&mut p);
492 let sid = self.id("search");
493 self.focus(sid);
494 }
495 self.box_begin(0, BOX_V | PAD_12, 10);
496 self.box_begin(0, BOX_H, 12);
497 self.next_size(-1, 0);
499 self.box_begin(0, BOX_V, 6);
500 self.text(TS_BODY_STRONG, super::i18n::tr("Schriftart"));
501 let fam0 = p.work.family.clone();
502 if self.font_family_list(&mut p, -1, 220, false) {
503 p.style = -1;
504 if !fam0.eq_ignore_ascii_case(&p.work.family) {
505 p.work.axes.clear(); }
507 }
508 self.box_end();
509 self.next_size(150, 0);
512 self.box_begin(0, BOX_V, 6);
513 self.text(TS_BODY_STRONG, super::i18n::tr("Schriftschnitt"));
514 let fi = self.font_family_index(&p.work.family);
515 let styles = self.font_styles(fi);
516 let var = styles.iter().any(|r| r.face >= 0 && !r.axes.is_empty())
517 || self.font_var_face(&p.work).is_some();
518 if var {
519 p.style = self.font_style_match(&p.work, &styles);
521 } else if p.style < 0 {
522 p.style = styles
524 .iter()
525 .enumerate()
526 .min_by_key(|(_, s)| (s.italic != p.work.italic) as i32 * 10000 + (s.weight as i32 - p.work.weight as i32).abs())
527 .map_or(-1, |(k, _)| k as i32);
528 if let Some(st) = styles.get(p.style.max(0) as usize).filter(|_| p.style >= 0) {
530 p.work.weight = st.weight;
531 p.work.italic = st.italic;
532 }
533 }
534 let sid = self.id("styles");
535 let mut ssel = p.style;
536 let n = styles.len() as i32;
537 self.next_size(-1, 258);
538 let (first, end) = self.list_begin(sid, &mut ssel, n, ROW_H);
539 for row in first..end.min(n) {
540 self.list_item(row, &styles[row as usize].name);
541 }
542 self.list_end(&mut ssel);
543 if ssel != p.style && (0..n).contains(&ssel) {
544 p.style = ssel;
545 let st = &styles[ssel as usize];
546 p.work.weight = st.weight;
547 p.work.italic = st.italic;
548 p.work.axes = st.axes.clone();
549 }
550 self.box_end();
551 self.next_size(90, 0);
553 self.box_begin(0, BOX_V, 6);
554 self.text(TS_BODY_STRONG, super::i18n::tr("Größe"));
555 let mut size = p.work.size;
556 self.next_size(-1, 0);
557 if self.number_box_f(self.id("size"), &mut size, &RANGE_PT) & EV_ACT != 0 {
558 p.work.size = size;
559 p.size = SIZES.iter().position(|&s| s == size).map_or(-1, |k| k as i32);
560 }
561 let zid = self.id("sizes");
562 let mut zsel = p.size;
563 self.next_size(-1, 220);
564 let (first, end) = self.list_begin(zid, &mut zsel, SIZES.len() as i32, ROW_H);
565 for row in first..end.min(SIZES.len() as i32) {
566 self.list_item(row, &format!("{}", SIZES[row as usize]));
567 }
568 self.list_end(&mut zsel);
569 if zsel != p.size && (0..SIZES.len() as i32).contains(&zsel) {
570 p.size = zsel;
571 p.work.size = SIZES[zsel as usize];
572 }
573 self.box_end();
574 self.box_end();
575 if var {
577 self.font_axes(&mut p.work);
578 }
579 self.text(TS_BODY_STRONG, super::i18n::tr("Vorschau"));
581 self.next_size(-1, 96);
582 self.box_begin(0, BOX_V | BOX_CARD | BOX_CLIP | BOX_CENTER | pad(12), 4);
583 let work = p.work.clone();
584 match self.font_load(&work) {
585 Some(f) => self.text_in_font(f, work.size, SAMPLE),
586 None => self.text(TS_BODY | TX_SECONDARY, super::i18n::tr("Keine passende Schrift installiert")),
587 }
588 self.box_end();
589 let fam = self.font_resolved(&work);
590 if !fam.is_empty() && !fam.eq_ignore_ascii_case(&work.family) {
591 let note = format!("{} {}", super::i18n::tr("Angezeigt in:"), fam);
592 self.text(TS_CAPTION | TX_SECONDARY, ¬e);
593 }
594 self.box_end();
595 let r = self.dialog_buttons(Some(super::i18n::tr("OK")), None, Some(super::i18n::tr("Abbrechen")));
596 if r == DLG_PRIMARY {
597 *sel = p.work.clone();
598 out |= EV_ACT;
599 }
600 }
601 self.window_end();
602 self.id_pop();
603 self.m.fonts.picks.insert(id, p);
604 out
605 }
606
607 fn font_styles(&mut self, fi: i32) -> Vec<StyleRow> {
610 if fi < 0 {
611 return [(400, false), (400, true), (700, false), (700, true)]
612 .iter()
613 .map(|&(w, it)| StyleRow { face: -1, name: String::from(style_label(w, it)), weight: w, italic: it, axes: Vec::new() })
614 .collect();
615 }
616 let db = self.fonts_db();
617 let mut rows = Vec::new();
618 for k in db.family_range(fi as usize) {
619 let Some(f) = db.face(k) else { continue };
620 let (fw, it, style) = (f.weight as u32, f.flags & (FDFF_ITALIC | FDFF_OBLIQUE) != 0, f.style.clone());
621 let name = if style.is_empty() { String::from(style_label(fw, it)) } else { style };
622 match db.variations(k) {
623 Some(v) if !v.instances.is_empty() => {
624 let ko = v.axes.iter().position(|a| &a.tag == b"opsz");
626 let opsz_named = ko.is_some_and(|ko| v.instances.iter().any(|i| i.coords.get(ko) != v.instances[0].coords.get(ko)));
627 for ins in &v.instances {
628 let val = |tag: &[u8; 4]| v.axes.iter().position(|a| &a.tag == tag).and_then(|i| ins.coords.get(i).copied());
629 let weight = val(b"wght").map_or(fw, |w| (w + 0.5).clamp(1.0, 1000.0) as u32);
630 let italic = it || val(b"ital").is_some_and(|x| x >= 0.5) || val(b"slnt").is_some_and(|x| x < 0.0);
631 let label = if ins.name.is_empty() { String::from(style_label(weight, italic)) } else { ins.name.clone() };
632 let axes = row_axes(&v.axes, &ins.coords, italic, opsz_named);
633 rows.push(StyleRow { face: k as i32, name: label, weight, italic, axes });
634 }
635 }
636 _ => rows.push(StyleRow { face: k as i32, name, weight: fw, italic: it, axes: Vec::new() }),
637 }
638 }
639 rows
640 }
641
642 fn font_var_face(&mut self, sel: &FontSel) -> Option<usize> {
644 let db = self.fonts_db();
645 let (fi, _) = db.find(&sel.family, sel.weight, sel.italic)?;
646 (db.face(fi)?.flags & FDFF_VAR != 0).then_some(fi)
647 }
648
649 fn font_style_match(&mut self, sel: &FontSel, rows: &[StyleRow]) -> i32 {
651 let face = self.font_var_face(sel).map_or(-1, |f| f as i32);
652 let size = Some(sel.size as f32);
653 let db = self.fonts_db();
654 let cur = if face >= 0 { db.var_values(face as usize, sel.weight, sel.italic, size, &sel.axes).map(|v| v.0) } else { None };
655 for (k, r) in rows.iter().enumerate() {
656 let same = match &cur {
657 Some(c) if r.face == face => {
658 db.var_values(face as usize, r.weight, r.italic, size, &r.axes).is_some_and(|v| v.0.iter().zip(c).all(|(a, b)| (a - b).abs() < 0.01))
659 }
660 Some(_) => false,
661 None => r.weight == sel.weight && r.italic == sel.italic,
662 };
663 if same {
664 return k as i32;
665 }
666 }
667 -1
668 }
669
670 fn font_axes(&mut self, w: &mut FontSel) {
673 let Some(face) = self.font_var_face(w) else { return };
674 let size = Some(w.size as f32);
675 let db = self.fonts_db();
676 let Some(v) = db.variations(face).cloned() else { return };
677 let Some((vals, _)) = db.var_values(face, w.weight, w.italic, size, &w.axes) else { return };
678 self.text(TS_BODY_STRONG, super::i18n::tr("Achsen"));
679 self.id_push_str("axis");
680 self.gridbox_begin(0, &[0, -1, 80, 0], 0, 8);
681 let mut changed: Option<(usize, f32)> = None;
682 for (k, a) in v.axes.iter().enumerate() {
683 if a.hidden {
684 continue;
685 }
686 let rf = axis_range(a);
687 self.text(TS_BODY, &a.name);
688 let mut x = vals[k] as f64;
689 self.next_size(-1, 0);
690 if self.slider_f(self.id_n(k as u32), &mut x, &rf) & EV_ACT != 0 {
691 changed = Some((k, x as f32));
692 }
693 let mut y = vals[k] as f64;
694 self.next_size(-1, 0);
695 if self.number_box_f(self.id_n(k as u32 + 100), &mut y, &rf) & EV_ACT != 0 && y != vals[k] as f64 {
696 changed = Some((k, y as f32));
697 }
698 if &a.tag == b"opsz" {
699 let mut auto = w.axis(*b"opsz").is_none() as i32;
700 if self.checkbox(self.id("opsz_auto"), super::i18n::tr("Automatisch"), &mut auto) & EV_ACT != 0 {
701 w.set_axis(*b"opsz", if auto != 0 { f32::NAN } else { vals[k] });
702 }
703 } else {
704 self.text(TS_BODY, "");
705 }
706 }
707 self.gridbox_end();
708 self.id_pop();
709 let Some((k, x)) = changed else { return };
710 let a = &v.axes[k];
711 let x = x.clamp(a.min, a.max);
712 match &a.tag {
713 b"wght" => {
714 w.weight = (x + 0.5).clamp(1.0, 1000.0) as u32;
715 w.set_axis(a.tag, if (x - w.weight as f32).abs() < 0.001 { f32::NAN } else { x });
716 }
717 b"opsz" => w.set_axis(a.tag, x),
718 t => {
719 if t == b"ital" {
720 w.italic = x >= 0.5;
721 } else if t == b"slnt" && !v.axes.iter().any(|b| &b.tag == b"ital") {
722 w.italic = x < 0.0;
723 }
724 w.set_axis(a.tag, x);
725 let face2 = self.font_var_face(w);
727 let db = self.fonts_db();
728 let mut bare = w.clone();
729 bare.set_axis(a.tag, f32::NAN);
730 if let Some(f2) = face2
731 && let Some((m, _)) = db.var_values(f2, bare.weight, bare.italic, Some(bare.size as f32), &bare.axes)
732 && (m[k] - x).abs() < 0.001
733 {
734 w.set_axis(a.tag, f32::NAN);
735 }
736 }
737 }
738 }
739}