ccab39f140
Re-sincroniza las fuentes desde el monorepo (estaba en vello 0.5/wgpu 24 y con la estructura vieja de eventloop) y suma el 3D: - bump del workspace a vello 0.7 / wgpu 27 / parley 0.6, + accesskit 0.24 / accesskit_winit 0.33 / vello_hybrid 0.0.9. - nuevos crates: llimphi-3d (voxels ray-march + mallas en un depth compartido, montable dentro de un View 2D vía set_viewport+scissor) y llimphi-voxel (world-gen, personajes, director de escenas) + shared/foreign-vox (puente .vox). - README: sección "Not just 2D — a 3D voxel engine" + GIF (docs/llimphi_voxel.gif). - excluido modules/allichay (arrastra deps fuera del alcance del front-door). - cargo check --workspace: verde. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
199 lines
7.5 KiB
Rust
199 lines
7.5 KiB
Rust
//! Verifica que un párrafo largo, dentro de un bloque angosto, reserva el
|
|
//! alto de **varias líneas** (no se aplasta en una). Es el regresor del bug
|
|
//! "textos aplastados" de puriy: sin medición con parley, taffy le daba a la
|
|
//! hoja de texto una sola línea de alto y las líneas envueltas se solapaban.
|
|
|
|
use llimphi_compositor::{measure_text_node, mount, View};
|
|
use llimphi_layout::taffy::prelude::*;
|
|
use llimphi_layout::taffy::Size as TSize;
|
|
use llimphi_layout::LayoutTree;
|
|
|
|
#[derive(Clone)]
|
|
enum Msg {}
|
|
|
|
#[test]
|
|
fn parrafo_largo_reserva_varias_lineas() {
|
|
// Bloque de 200px de ancho con un párrafo que claramente excede una línea.
|
|
let texto = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do \
|
|
eiusmod tempor incididunt ut labore et dolore magna aliqua ut \
|
|
enim ad minim veniam quis nostrud exercitation ullamco laboris.";
|
|
let block: View<Msg> = View::new(Style {
|
|
size: TSize { width: length(200.0_f32), height: auto() },
|
|
flex_direction: FlexDirection::Row,
|
|
flex_wrap: FlexWrap::Wrap,
|
|
..Default::default()
|
|
})
|
|
.children(vec![View::new(Style {
|
|
size: TSize { width: auto(), height: auto() },
|
|
flex_shrink: 1.0,
|
|
..Default::default()
|
|
})
|
|
.text_aligned(texto, 16.0_f32, vello::peniko::Color::BLACK, llimphi_text::Alignment::Start)]);
|
|
|
|
let mut layout = LayoutTree::new();
|
|
let mounted = mount(&mut layout, block);
|
|
let mut ts = llimphi_text::Typesetter::new();
|
|
let tmap = &mounted.text_measures;
|
|
assert_eq!(tmap.len(), 1, "debería haber exactamente una hoja de texto");
|
|
|
|
let computed = layout
|
|
.compute_with_measure(mounted.root, (800.0, 600.0), |nid, known, avail| match tmap.get(&nid)
|
|
{
|
|
Some(tm) => measure_text_node(&mut ts, tm, known, avail),
|
|
None => TSize::ZERO,
|
|
})
|
|
.expect("layout");
|
|
|
|
// El nodo de texto es el segundo en orden DFS (root, luego la hoja).
|
|
let leaf_id = mounted.nodes[1].id;
|
|
let rect = computed.get(leaf_id).expect("rect de la hoja");
|
|
// A 16px y ~1.2 de interlínea, una línea ≈ 19px. Con ~150px de texto en
|
|
// 200px de ancho deberían ser >= 4 líneas → bastante más de una.
|
|
assert!(
|
|
rect.h > 40.0,
|
|
"el párrafo se aplastó: alto={} (esperaba varias líneas)",
|
|
rect.h
|
|
);
|
|
assert!(rect.w <= 200.0 + 1.0, "no debería exceder el ancho del bloque");
|
|
}
|
|
|
|
#[test]
|
|
fn no_wrap_mide_una_sola_linea_fase_7_1253() {
|
|
use llimphi_layout::taffy::AvailableSpace;
|
|
// Mismo texto largo, mismo ancho disponible (angosto): con `no_wrap` se
|
|
// mide en una sola línea (ancho completo, ignora el available); sin él,
|
|
// envuelve (más alto, ancho acotado al disponible).
|
|
let texto = "una linea larga que normalmente envuelve en varios renglones \
|
|
cuando el ancho disponible es angosto de verdad";
|
|
let mk = |no_wrap: bool| llimphi_compositor::TextMeasure {
|
|
content: texto.to_string(),
|
|
size_px: 16.0,
|
|
alignment: llimphi_text::Alignment::Start,
|
|
italic: false,
|
|
font_family: None,
|
|
line_height: 1.2,
|
|
weight: 400.0,
|
|
max_lines: None,
|
|
ellipsis: false,
|
|
underline: false,
|
|
strikethrough: false,
|
|
spans: None,
|
|
letter_spacing: 0.0,
|
|
word_spacing: 0.0,
|
|
no_wrap,
|
|
overflow_wrap: false,
|
|
};
|
|
let mut ts = llimphi_text::Typesetter::new();
|
|
let known = TSize { width: None, height: None };
|
|
let avail = TSize {
|
|
width: AvailableSpace::Definite(160.0),
|
|
height: AvailableSpace::MaxContent,
|
|
};
|
|
let env = measure_text_node(&mut ts, &mk(false), known, avail);
|
|
let nw = measure_text_node(&mut ts, &mk(true), known, avail);
|
|
// Envuelto: ancho acotado al disponible y alto de varias líneas.
|
|
assert!(env.width <= 160.0 + 1.0, "wrap acota el ancho: {}", env.width);
|
|
assert!(env.height > 40.0, "wrap reserva varias líneas: {}", env.height);
|
|
// no_wrap: una sola línea → mucho más ancho que el disponible y bajo.
|
|
assert!(nw.width > 160.0, "no_wrap mide ancho completo: {}", nw.width);
|
|
assert!(
|
|
nw.height < env.height,
|
|
"no_wrap es una línea (más bajo que el envuelto): nw={} env={}",
|
|
nw.height,
|
|
env.height
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn line_height_mayor_reserva_mas_alto() {
|
|
let texto = "una línea de texto que envuelve en dos o tres renglones según \
|
|
el ancho disponible para el bloque contenedor angosto";
|
|
let medir = |lh: f32| -> f32 {
|
|
let mut ts = llimphi_text::Typesetter::new();
|
|
let tm = llimphi_compositor::TextMeasure {
|
|
content: texto.to_string(),
|
|
size_px: 16.0,
|
|
alignment: llimphi_text::Alignment::Start,
|
|
italic: false,
|
|
font_family: None,
|
|
line_height: lh,
|
|
weight: 400.0,
|
|
max_lines: None,
|
|
ellipsis: false,
|
|
underline: false,
|
|
strikethrough: false,
|
|
spans: None,
|
|
letter_spacing: 0.0,
|
|
word_spacing: 0.0,
|
|
no_wrap: false,
|
|
overflow_wrap: false,
|
|
};
|
|
let known = TSize { width: Some(180.0_f32), height: None };
|
|
let avail = TSize {
|
|
width: AvailableSpace::Definite(180.0),
|
|
height: AvailableSpace::MaxContent,
|
|
};
|
|
measure_text_node(&mut ts, &tm, known, avail).height
|
|
};
|
|
let compacto = medir(1.0);
|
|
let comodo = medir(2.0);
|
|
assert!(
|
|
comodo > compacto * 1.5,
|
|
"line-height: 2 debería reservar bastante más alto que 1.0 (got {compacto} vs {comodo})"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn overflow_wrap_parte_la_palabra_larga_fase_7_1254() {
|
|
use llimphi_layout::taffy::AvailableSpace;
|
|
// Una sola palabra sin espacios, más ancha que la caja angosta. Sin
|
|
// `overflow-wrap` parley la deja desbordar (mide más ancho que la caja);
|
|
// con `overflow-wrap` la parte para que entre (ancho acotado, varias líneas
|
|
// ⇒ más alto). Es el regresor directo de la Fase 7.1254.
|
|
let palabrota = "supercalifragilisticoexpialidosoineluctableantidisestablishmentariano";
|
|
let mk = |overflow_wrap: bool| llimphi_compositor::TextMeasure {
|
|
content: palabrota.to_string(),
|
|
size_px: 16.0,
|
|
alignment: llimphi_text::Alignment::Start,
|
|
italic: false,
|
|
font_family: None,
|
|
line_height: 1.2,
|
|
weight: 400.0,
|
|
max_lines: None,
|
|
ellipsis: false,
|
|
underline: false,
|
|
strikethrough: false,
|
|
spans: None,
|
|
letter_spacing: 0.0,
|
|
word_spacing: 0.0,
|
|
no_wrap: false,
|
|
overflow_wrap,
|
|
};
|
|
let mut ts = llimphi_text::Typesetter::new();
|
|
let known = TSize { width: None, height: None };
|
|
let avail = TSize {
|
|
width: AvailableSpace::Definite(80.0),
|
|
height: AvailableSpace::MaxContent,
|
|
};
|
|
let normal = measure_text_node(&mut ts, &mk(false), known, avail);
|
|
let roto = measure_text_node(&mut ts, &mk(true), known, avail);
|
|
// Sin overflow-wrap: la palabra desborda → ancho mayor que la caja.
|
|
assert!(
|
|
normal.width > 80.0,
|
|
"sin overflow-wrap la palabra desborda: {}",
|
|
normal.width
|
|
);
|
|
// Con overflow-wrap: se parte → ancho acotado a la caja y más alto.
|
|
assert!(
|
|
roto.width <= 80.0 + 1.0,
|
|
"overflow-wrap acota el ancho a la caja: {}",
|
|
roto.width
|
|
);
|
|
assert!(
|
|
roto.height > normal.height,
|
|
"overflow-wrap parte en varias líneas (más alto): roto={} normal={}",
|
|
roto.height,
|
|
normal.height
|
|
);
|
|
}
|