tawasuyu

tawasuyu/llimphi

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

Rust 124Markdown 82TOML 78Shell 12149 Definiciones

⎇ main widgets/qr/src/lib.rs Rust · 107 líneas · 4.1 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) b575c63f1 · Sergio ·

1//! `llimphi-widget-qr` — pinta una matriz de código QR.
2//!
3//! **Existe porque había tres copias del mismo pintor.** `qillqa-mesa-llimphi`,
4//! el módulo Mesa de `agora-app` y el diente `Mi carné` pintaban cada uno su
5//! versión, y la de `agora-app` lo dice en su propio comentario: «la misma
6//! composición que `qillqa-mesa-llimphi::qr_painter`». Ese es el patrón que
7//! `shared/qr` ya había cortado del lado de la codificación —matriz y PNG, una
8//! sola vez— y que del lado del dibujo seguía abierto.
9//!
10//! Y las tres copias no eran idénticas, que es el costo real de duplicar: sólo
11//! **una** tenía el solape de medio píxel entre módulos. Sin él el redondeo deja
12//! hilos blancos y **el lector falla** — un defecto arreglado en un lugar y
13//! vivo en los otros dos. Acá está una vez y arreglado.
14//!
15//! **Data-agnóstico**, como el resto de los widgets: recibe el lado y los
16//! módulos, no un tipo de `shared/qr`. Quien tenga una `QrMatriz` pasa
17//! `(m.ancho, &m.modulos)`; quien la tenga de otra forma, también.
18
19#![forbid(unsafe_code)]
20#![no_std]
21
22extern crate alloc;
23
24use alloc::vec::Vec;
25use llimphi_compositor::llimphi_layout::taffy::prelude::{length, percent, Size, Style};
26use llimphi_compositor::peniko::Color;
27use llimphi_compositor::View;
28
29/// Módulos de **zona tranquila** alrededor del código.
30///
31/// Cuatro es lo que pide la norma. Una de las tres copias usaba dos, que es
32/// menos margen del que muchos lectores necesitan para encontrar el símbolo
33/// sobre un fondo que no controlan.
34pub const ZONA_TRANQUILA: usize = 4;
35
36/// La vista del QR, con alto fijo en píxeles.
37///
38/// El código se pinta **siempre blanco sobre negro, sin teñir con la paleta**:
39/// un lector de QR no sabe de temas, y un código en los colores del tema oscuro
40/// es un cuadrado bonito que no se lee.
41pub fn qr_view<Msg: Clone + Send + Sync + 'static>(
42    ancho: usize,
43    modulos: &[bool],
44    alto_px: f32,
45) -> View<Msg> {
46    qr_view_ext(ancho, modulos, alto_px, ZONA_TRANQUILA)
47}
48
49/// Como [`qr_view`], eligiendo la zona tranquila.
50pub fn qr_view_ext<Msg: Clone + Send + Sync + 'static>(
51    ancho: usize,
52    modulos: &[bool],
53    alto_px: f32,
54    borde: usize,
55) -> View<Msg> {
56    let modulos: Vec<bool> = modulos.to_vec();
57    View::new(Style {
58        size: Size { width: percent(1.0_f32), height: length(alto_px) },
59        flex_shrink: 0.0,
60        ..Default::default()
61    })
62    // Es una imagen y su contenido no se puede leer en voz alta: el lector de
63    // pantalla necesita que alguien diga qué hay adentro, y quien lo sabe es el
64    // llamador. Acá sólo se declara el rol.
65    .role(llimphi_compositor::Role::Image)
66    .paint_with(move |scene, _ts, rect| {
67        use llimphi_compositor::peniko::kurbo::{Affine, Rect};
68        use llimphi_compositor::peniko::Fill;
69
70        if ancho == 0 || modulos.len() < ancho * ancho {
71            return;
72        }
73        let lado_mod = (ancho + borde * 2) as f64;
74        // Cuadrado y centrado: un QR estirado no se lee.
75        let lado_px = rect.w.min(rect.h) as f64;
76        let paso = lado_px / lado_mod;
77        let x0 = rect.x as f64 + (rect.w as f64 - lado_px) * 0.5;
78        let y0 = rect.y as f64 + (rect.h as f64 - lado_px) * 0.5;
79
80        scene.fill(
81            Fill::NonZero,
82            Affine::IDENTITY,
83            Color::WHITE,
84            None,
85            &Rect::new(x0, y0, x0 + lado_px, y0 + lado_px),
86        );
87        for my in 0..ancho {
88            for mx in 0..ancho {
89                if !modulos[my * ancho + mx] {
90                    continue;
91                }
92                let x = x0 + (mx + borde) as f64 * paso;
93                let y = y0 + (my + borde) as f64 * paso;
94                // **El medio píxel de solape.** Sin él el redondeo deja hilos
95                // blancos entre módulos y el lector no engancha el símbolo. Es
96                // el arreglo que sólo una de las tres copias tenía.
97                scene.fill(
98                    Fill::NonZero,
99                    Affine::IDENTITY,
100                    Color::BLACK,
101                    None,
102                    &Rect::new(x, y, x + paso + 0.5, y + paso + 0.5),
103                );
104            }
105        }
106    })
107}