tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ 92dd0e5bfb2ca4456006da99437f4100e156d856 llimphi-compositor/tests/text_measure.rs Rust · 198 líneas · 7.5 KB Historia Quién tocó qué crudo

refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel ccab39f14 · Sergio ·

1//! Verifica que un párrafo largo, dentro de un bloque angosto, reserva el
2//! alto de **varias líneas** (no se aplasta en una). Es el regresor del bug
3//! "textos aplastados" de puriy: sin medición con parley, taffy le daba a la
4//! hoja de texto una sola línea de alto y las líneas envueltas se solapaban.
5
6use llimphi_compositor::{measure_text_node, mount, View};
7use llimphi_layout::taffy::prelude::*;
8use llimphi_layout::taffy::Size as TSize;
9use llimphi_layout::LayoutTree;
10
11#[derive(Clone)]
12enum Msg {}
13
14#[test]
15fn parrafo_largo_reserva_varias_lineas() {
16    // Bloque de 200px de ancho con un párrafo que claramente excede una línea.
17    let texto = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do \
18                 eiusmod tempor incididunt ut labore et dolore magna aliqua ut \
19                 enim ad minim veniam quis nostrud exercitation ullamco laboris.";
20    let block: View<Msg> = View::new(Style {
21        size: TSize { width: length(200.0_f32), height: auto() },
22        flex_direction: FlexDirection::Row,
23        flex_wrap: FlexWrap::Wrap,
24        ..Default::default()
25    })
26    .children(vec![View::new(Style {
27        size: TSize { width: auto(), height: auto() },
28        flex_shrink: 1.0,
29        ..Default::default()
30    })
31    .text_aligned(texto, 16.0_f32, vello::peniko::Color::BLACK, llimphi_text::Alignment::Start)]);
32
33    let mut layout = LayoutTree::new();
34    let mounted = mount(&mut layout, block);
35    let mut ts = llimphi_text::Typesetter::new();
36    let tmap = &mounted.text_measures;
37    assert_eq!(tmap.len(), 1, "debería haber exactamente una hoja de texto");
38
39    let computed = layout
40        .compute_with_measure(mounted.root, (800.0, 600.0), |nid, known, avail| match tmap.get(&nid)
41        {
42            Some(tm) => measure_text_node(&mut ts, tm, known, avail),
43            None => TSize::ZERO,
44        })
45        .expect("layout");
46
47    // El nodo de texto es el segundo en orden DFS (root, luego la hoja).
48    let leaf_id = mounted.nodes[1].id;
49    let rect = computed.get(leaf_id).expect("rect de la hoja");
50    // A 16px y ~1.2 de interlínea, una línea ≈ 19px. Con ~150px de texto en
51    // 200px de ancho deberían ser >= 4 líneas → bastante más de una.
52    assert!(
53        rect.h > 40.0,
54        "el párrafo se aplastó: alto={} (esperaba varias líneas)",
55        rect.h
56    );
57    assert!(rect.w <= 200.0 + 1.0, "no debería exceder el ancho del bloque");
58}
59
60#[test]
61fn no_wrap_mide_una_sola_linea_fase_7_1253() {
62    use llimphi_layout::taffy::AvailableSpace;
63    // Mismo texto largo, mismo ancho disponible (angosto): con `no_wrap` se
64    // mide en una sola línea (ancho completo, ignora el available); sin él,
65    // envuelve (más alto, ancho acotado al disponible).
66    let texto = "una linea larga que normalmente envuelve en varios renglones \
67                 cuando el ancho disponible es angosto de verdad";
68    let mk = |no_wrap: bool| llimphi_compositor::TextMeasure {
69        content: texto.to_string(),
70        size_px: 16.0,
71        alignment: llimphi_text::Alignment::Start,
72        italic: false,
73        font_family: None,
74        line_height: 1.2,
75        weight: 400.0,
76        max_lines: None,
77        ellipsis: false,
78        underline: false,
79        strikethrough: false,
80        spans: None,
81        letter_spacing: 0.0,
82        word_spacing: 0.0,
83        no_wrap,
84        overflow_wrap: false,
85    };
86    let mut ts = llimphi_text::Typesetter::new();
87    let known = TSize { width: None, height: None };
88    let avail = TSize {
89        width: AvailableSpace::Definite(160.0),
90        height: AvailableSpace::MaxContent,
91    };
92    let env = measure_text_node(&mut ts, &mk(false), known, avail);
93    let nw = measure_text_node(&mut ts, &mk(true), known, avail);
94    // Envuelto: ancho acotado al disponible y alto de varias líneas.
95    assert!(env.width <= 160.0 + 1.0, "wrap acota el ancho: {}", env.width);
96    assert!(env.height > 40.0, "wrap reserva varias líneas: {}", env.height);
97    // no_wrap: una sola línea → mucho más ancho que el disponible y bajo.
98    assert!(nw.width > 160.0, "no_wrap mide ancho completo: {}", nw.width);
99    assert!(
100        nw.height < env.height,
101        "no_wrap es una línea (más bajo que el envuelto): nw={} env={}",
102        nw.height,
103        env.height
104    );
105}
106
107#[test]
108fn line_height_mayor_reserva_mas_alto() {
109    let texto = "una línea de texto que envuelve en dos o tres renglones según \
110                 el ancho disponible para el bloque contenedor angosto";
111    let medir = |lh: f32| -> f32 {
112        let mut ts = llimphi_text::Typesetter::new();
113        let tm = llimphi_compositor::TextMeasure {
114            content: texto.to_string(),
115            size_px: 16.0,
116            alignment: llimphi_text::Alignment::Start,
117            italic: false,
118            font_family: None,
119            line_height: lh,
120            weight: 400.0,
121            max_lines: None,
122            ellipsis: false,
123            underline: false,
124            strikethrough: false,
125            spans: None,
126            letter_spacing: 0.0,
127            word_spacing: 0.0,
128            no_wrap: false,
129            overflow_wrap: false,
130        };
131        let known = TSize { width: Some(180.0_f32), height: None };
132        let avail = TSize {
133            width: AvailableSpace::Definite(180.0),
134            height: AvailableSpace::MaxContent,
135        };
136        measure_text_node(&mut ts, &tm, known, avail).height
137    };
138    let compacto = medir(1.0);
139    let comodo = medir(2.0);
140    assert!(
141        comodo > compacto * 1.5,
142        "line-height: 2 debería reservar bastante más alto que 1.0 (got {compacto} vs {comodo})"
143    );
144}
145
146#[test]
147fn overflow_wrap_parte_la_palabra_larga_fase_7_1254() {
148    use llimphi_layout::taffy::AvailableSpace;
149    // Una sola palabra sin espacios, más ancha que la caja angosta. Sin
150    // `overflow-wrap` parley la deja desbordar (mide más ancho que la caja);
151    // con `overflow-wrap` la parte para que entre (ancho acotado, varias líneas
152    // ⇒ más alto). Es el regresor directo de la Fase 7.1254.
153    let palabrota = "supercalifragilisticoexpialidosoineluctableantidisestablishmentariano";
154    let mk = |overflow_wrap: bool| llimphi_compositor::TextMeasure {
155        content: palabrota.to_string(),
156        size_px: 16.0,
157        alignment: llimphi_text::Alignment::Start,
158        italic: false,
159        font_family: None,
160        line_height: 1.2,
161        weight: 400.0,
162        max_lines: None,
163        ellipsis: false,
164        underline: false,
165        strikethrough: false,
166        spans: None,
167        letter_spacing: 0.0,
168        word_spacing: 0.0,
169        no_wrap: false,
170        overflow_wrap,
171    };
172    let mut ts = llimphi_text::Typesetter::new();
173    let known = TSize { width: None, height: None };
174    let avail = TSize {
175        width: AvailableSpace::Definite(80.0),
176        height: AvailableSpace::MaxContent,
177    };
178    let normal = measure_text_node(&mut ts, &mk(false), known, avail);
179    let roto = measure_text_node(&mut ts, &mk(true), known, avail);
180    // Sin overflow-wrap: la palabra desborda → ancho mayor que la caja.
181    assert!(
182        normal.width > 80.0,
183        "sin overflow-wrap la palabra desborda: {}",
184        normal.width
185    );
186    // Con overflow-wrap: se parte → ancho acotado a la caja y más alto.
187    assert!(
188        roto.width <= 80.0 + 1.0,
189        "overflow-wrap acota el ancho a la caja: {}",
190        roto.width
191    );
192    assert!(
193        roto.height > normal.height,
194        "overflow-wrap parte en varias líneas (más alto): roto={} normal={}",
195        roto.height,
196        normal.height
197    );
198}