3 puntos por GN⁺ 2024-03-22 | 3 comentarios | Compartir por WhatsApp
  • Dropflow es un motor de layout CSS creado para explorar los estándares básicos de CSS: inline, block, float, positioning y, a futuro, table; flexbox y grid no son parte del objetivo
  • Con Node y node-canvas puede usarse en el backend para generar PDF e imágenes, y en el navegador puede renderizar texto enriquecido con saltos de línea en canvas
  • El layout de texto admite texto bidireccional y RTL, fallback de fuentes por unidad de grafema, diacríticos con color, registro de fuentes OpenType/TrueType y shaping basado en HarfBuzz
  • El soporte de CSS abarca más de 30 propiedades: funcionan float, clear, display: inline-block, position: relative, overflow, z-index, zoom, entre otras; table, absolute, fixed, transform, etc. están en etapa de planificación
  • El mejor rendimiento se obtiene con la API hyperscript que evita el parsing de HTML/CSS y con objetos style reutilizables; en una MacBook Pro 2019 convierte The Little Prince, con más de 500 párrafos, de HTML a imagen en menos de 160 ms

Alcance de layout CSS que cubre Dropflow

  • Dropflow es un motor de layout CSS creado para explorar el alcance de los foundational CSS standards
    • Su foco es inline, block, float, positioning y, a futuro, tables
    • flexbox y grid no son parte del objetivo
  • Cuenta con una implementación de layout de texto de alta calidad y puede mostrar muchos idiomas del mundo
  • Se presentan dos casos de uso
    • Generar PDF o imágenes en el backend usando Node y node-canvas
    • Renderizar texto enriquecido con salto de línea en canvas dentro del navegador

Funciones principales

  • Soporta más de 30 propiedades CSS, incluidas propiedades complejas como float
  • Tiene una amplia cobertura de funciones relacionadas con texto
    • Texto bidireccional y texto RTL
    • Fallback de fuentes por unidad de grafema
    • Diacríticos con color
    • Manejo deseable de saltos de línea, por ejemplo llevando el padding inicial a la siguiente línea
    • Shaping optimizado
  • Además de HTML/CSS, también admite la API hyperscript h(), que permite pasar estilos como objetos
  • Se pueden registrar buffers OpenType/TrueType, y el registro es obligatorio
  • <img> admite JPEG, BMP, PNG y GIF, aunque el soporte puede variar según el backend
  • Los estilos heredados y en cascada no se calculan dos veces
  • Está completamente tipado, tiene muchas pruebas y apunta a funcionar rápido

Estado del soporte CSS

  • Entre los elementos que funcionan en inline formatting están color, direction, font-family, font-size, font-stretch, font-style, font-weight, line-height, text-align, vertical-align, white-space, word-break, overflow-wrap, word-wrap, word-spacing, entre otros
  • En inline formatting, font-variant, letter-spacing, tab-size, text-decoration y unicode-bidi están planificados
  • En block formatting funcionan clear y float
    • horizontal-tb, vertical-lr y vertical-rl de writing-mode están parcialmente implementados
    • Está implementado para BFC, pero todavía no para IFC
  • En boxes and positioning funcionan varias propiedades de cajas y posicionamiento
    • background-clip, background-color, border-color, border-style, border-width
    • top, right, bottom, left
    • box-sizing
    • display: block, inline, inline-block, flow-root, none
    • height, margin, padding, overflow, width, z-index, zoom
    • position: relative
  • En boxes and positioning, display: table, min/max-height, min/max-width, position: absolute, position: fixed y transform están planificados

Flujo básico de uso

  • Dropflow funciona sobre un DOM con estilos heredados y calculados, de la misma forma que un navegador
  • El flujo típico es el siguiente
    • Registrar fuentes con FontFace antes del layout
    • Crear objetos de estilo con flow.style()
    • Crear el DOM con flow.h()
    • Preparar el árbol a renderizar con flow.dom()
    • Ejecutar el layout y el paint sobre todo el canvas con renderToCanvas()
  • También ofrece una API HTML, pero solo se recomienda cuando el rendimiento no es importante o con fines de aprendizaje
    • El parsing agrega tiempo adicional y aumenta mucho el tamaño del bundle
    • Se indica que el parsing de HTML es rápido gracias a @fb55
    • Actualmente solo se admite el atributo HTML style; class todavía no funciona

Características de rendimiento

  • El rendimiento es un objetivo principal, segundo en importancia después de la exactitud
  • Se presentan métricas de rendimiento basadas en ejemplos
    • Convertir de HTML a imagen 8 párrafos con varios spans inline y distintas fuentes tarda 9 ms en una MacBook Pro 2019 y 13 ms en una MacBook Pro 2012
    • Convertir de HTML a imagen The Little Prince, con más de 500 párrafos, tarda menos de 160 ms en una MacBook Pro 2019 y menos de 250 ms en una MacBook Pro 2012
    • Generar una palabra de 10 caracteres y ejecutar solo el layout tarda menos de 25 µs en una MacBook Pro 2019 y menos de 50 µs en una MacBook Pro 2012
  • El rendimiento más rápido se obtiene al crear el DOM directamente con la API hyperscript y omitir los pasos habituales de parsing de HTML/CSS
  • Reutilizar objetos style aporta beneficios adicionales
  • Hacer reflow con distintos anchos es más rápido que volver a crear el árbol de layout

Composición de la API

  • Los pasos básicos son dos
    • Registrar fuentes
    • Crear el DOM con la API Hyperscript o la API Parse
  • En usos simples, el DOM puede renderizarse directamente en un canvas
    • renderToCanvas(el, canvas) usa el width y height del canvas como tamaño del viewport y renderiza todo el layout
  • Con APIs de más bajo nivel se puede conservar el layout
    • Cargar recursos dependientes
    • Crear el layout del DOM
    • Hacer reflow del layout
    • Hacer paint en un destino como un canvas HTML5
  • Este enfoque puede usarse para hacer reflow a otros tamaños, no pintar layouts invisibles u obtener intrinsics

Manejo de fuentes

  • El primer paso de un programa con Dropflow es registrar las fuentes que se seleccionarán mediante propiedades CSS de font
  • Como Dropflow no busca fuentes del sistema, hay que crear y agregar al menos una vez un FontFace
  • La API de registro de fuentes implementa un subconjunto de la CSS Font Loading API y agrega un método no estándar loadSync
  • Del lado del servidor, las URL file:/// pueden cargarse de forma síncrona con readFileSync
  • Los ArrayBuffer se cargan de inmediato en el constructor, igual que en el navegador
  • registerNotoFonts registra todas las familias de fuentes Noto Sans
    • Las fuentes las distribuye FontSource y las aloja jsDelivr
    • Es un import grande porque hay más de 200 fuentes Noto Sans y las cadenas unicodeRange de las fuentes CJK son extensas
    • Se indica que para uso en producción en navegador es mejor registrar fuentes individuales
    • Para Latin se registran fuentes italic, y para todos los scripts se registran normal 400 y bold 700
  • El chino, el coreano y el japonés comparten puntos de código Unicode comunes, pero los caracteres pueden renderizarse de forma distinta, por lo que, cuando sea posible, conviene usar una fuente específica para el idioma

Layout, reflow y paint

  • layout(el) crea un layout que incluye box tree, fragmentation tree y glyphs
    • El box tree se corresponde en general con el DOM tree, pero puede tener más boxes por contenido de texto anónimo o menos por display: none
  • reflow(layout, width = 640, height = 480) posiciona las boxes, divide el texto en líneas y deja el layout listo para paint
    • Margin collapsing de block boxes
    • Pasar texto a HarfBuzz
    • Iterar fallback de fuentes
    • Reshaping según saltos de línea y break points
    • Posicionamiento de floats y manejo de clear
    • Ubicación de shaped text spans y backgrounds según direction y la dirección del texto
    • Cálculo de intrinsics para contenidos float, inline-block y absolute
    • Procesamiento de position después del normal flow
  • Actualmente los destinos de paint soportan Canvas y SVG
    • paintToCanvas pinta en un canvas del navegador, node-canvas o un context similar que cumpla con el estándar
    • paintToSvg crea una cadena SVG e incluye reglas @font-face que referencian las URL pasadas a FontFace
    • paintToSvgElements no agrega <svg> ni reglas @font-face, para usarlo al dibujar dentro de un SVG existente
    • paintToHtml genera una lista plana de elementos con posición absoluta; no se recomienda su uso, pero puede ser útil durante el desarrollo

API DOM y hooks de entorno

  • El HTMLElement raíz obtenido con Hyperscript y la API Parse ofrece métodos para encontrar elementos por tag name, id y class, similares a la familia querySelector del navegador
    • query(selector) devuelve un HTMLElement o null
    • queryAll(selector) devuelve HTMLElement[]
  • Un HTMLElement puede tener una render box conectada
    • Normalmente es una, pero puede haber varias cuando se mezclan contenidos inline y block
    • BlockContainer se crea para absolute positioned element, floated element, inline-block y block-level element
    • ReplacedBox se crea para imágenes
  • Dropflow está diseñado para poder adaptarse a distintos entornos
    • En el navegador carga fuentes e imágenes con fetch y registra los buffers de fuentes en document.fonts
    • En Nodejs puede cargar fuentes de forma síncrona con fs.readFileSync
    • Si hay un backend canvas y node-canvas, llama a registerFont de node-canvas
    • Como node-canvas no soporta buffers de fuentes, hay que usar URL file://
  • Para usar @napi-rs/canvas o skia-canvas, se requieren unas pocas líneas de código que conecten flow.environment.registerFont con la API de registro de fuentes correspondiente
  • El entorno tiene 6 hooks
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

Shaping de texto basado en HarfBuzz

  • El layout de glyphs lo realiza HarfBuzz compilado a WebAssembly
  • Apunta a una exactitud difícil de obtener con un enfoque basado en posicionar spans de texto mediante la API measureText
  • Por ejemplo, en Google Sheets, si solo la "V" de "AV" se pinta de otro color, desaparece el kerning y las letras quedan más separadas que originalmente
    • Esto ocurre porque se hacen dos llamadas a measureText y fillText por letra, lo que elimina el contextual glyph advance
  • Dropflow usa HarfBuzz en boundaries de shaping más gruesos, no en cada punto donde cambia el color, para soportar fuentes con mayor exactitud
  • HarfBuzz compilado a WebAssembly puede alcanzar métricas de rendimiento similares a las de measureText de CanvasRenderingContext2D
    • Se indica que no es tan rápido como measureText, pero tampoco es mucho más lento
    • También se indica que ninguno de los dos es el cuello de botella dominante del stack de layout de texto
  • El layout de texto basado en measureText, para funcionar rápido, necesita una caché de palabras; se indica que las apps de GSuite usan este enfoque
    • La caché de palabras no puede soportar fuentes con efectos que cruzan espacios
    • Para soportar esas fuentes se necesita una búsqueda binaria en los break index del párrafo, y se indica que eso es mucho más lento que pasar el párrafo completo a HarfBuzz
    • Los diacríticos con color no son posibles con measureText

Proyectos dependientes

  • Dropflow no tiene dependencias en package.json, pero aprovecha el trabajo de varios proyectos
  • Las dependencias JavaScript están incorporadas al proyecto y fueron modificadas en distintos grados para mantener el foco y evitar problemas de dependency-of-dependency
  • Los proyectos principales son los siguientes

3 comentarios

 
winterjung 2024-03-23

El título original era "Show HN: Dropflow, a CSS layout engine for node or <canvas>". Ahora aparece como "GN⁺: Presentamos en HN: motor de diseño CSS para Node o <canvas>, Dropflow</canvas>".

 
dlehals2 2024-03-22

Como hay una etiqueta en el título, se rompe la parte del título en la página de detalle... jeje, hay que hacer escape...

 
GN⁺ 2024-03-22
Opiniones de Hacker News
  • Hoy en día, la forma estándar de generar PDFs atractivos en el backend es levantar un navegador headless y convertir HTML/CSS a PDF mediante APIs del navegador, pero ejecutar instancias de navegador en el servidor y escalarlas para grandes volúmenes de trabajo tiene un costo considerable
    Esta herramienta cambia las reglas del juego: ahora se pueden diseñar y generar PDFs con HTML/CSS sin el overhead del navegador

    • Es sorprendente que la web haya llegado hasta aquí. Que la mejor forma de crear documentos PDF bonitos sea ejecutar un navegador web tal cual en el servidor habría sido difícil de imaginar en los 90 o los 2000
    • Usar un navegador tiene la ventaja de que el PDF generado usa vectores y fuentes. En cambio, con Canvas probablemente casi todo terminaría como imagen dentro del PDF, aunque para la mayoría de los usos quizá no sea un gran problema
    • He usado https://ekoopmans.github.io/html2pdf.js/ y funciona bastante bien
    • Me confunde un poco. En el backend de un proyecto personal llevo bastante tiempo usando la biblioteca Prawn para generar PDFs: https://github.com/prawnpdf/prawn
      Aunque los PDFs que genero definitivamente no son bonitos, así que quizá esa sea la diferencia
    • He creado renderizadores de PDF para algunos clientes, y el mayor requisito en PDF era la accesibilidad
      Todos necesitaban cumplir con ADA, así que sería difícil cambiar a un renderizador Canvas, porque se perdería la accesibilidad
  • Se ve realmente bueno. En mi trabajo anterior desarrollé una plataforma de aprendizaje inmersivo en línea, donde capacitábamos en idiomas extranjeros a personal del Departamento de Defensa con Oculus Quest 2, usando WebXR, Three.js, etc.
    Con una vez de Unity3D fue suficiente, y tampoco quería pasar por revisiones de tiendas de apps. Teníamos nuestro propio parque de dispositivos, así que estaba bien
    Uno de los mayores desafíos fue crear el flujo de trabajo para el contenido didáctico, y me tocó hacerlo casi solo. Los instructores de idiomas reales creaban PDFs con PowerPoint, luego los subían con un editor dedicado que hice a la base de datos de contenidos para desplegarlos en el entorno de entrenamiento, y después los renderizaba con PDFJS en elementos Canvas para usarlos como texturas de rectángulos 3D
    Si hubiera existido una herramienta así, la gente no habría tenido que rodear el problema creando materiales en PowerPoint. El enfoque con PowerPoint mejoró mucho la velocidad del flujo de trabajo frente a un intento anterior de hacer que crearan imágenes en Photoshop, pero si hubiera podido crear un editor de letreros dentro de la app, habría sido mejor, porque eliminaría el ciclo de “adivinar cómo se verá en el entorno → exportar a PDF → subir a la DB → ver cómo se ve realmente”
    Aunque de todos modos no habría importado mucho, porque no teníamos un equipo de desarrollo de negocio ni de marketing que supiera vender productos en vez de solo servicios

  • Estaba buscando algo así para mi proyecto https://htwins.net/scale2 y otros trabajos que usan SVG o Canvas

    • Me encantaría leer un making-of sobre cómo lo construyeron
  • Si estás batallando con Flexbox, puedes usar una herramienta que simplifica el proceso para no tener que preocuparte por tantas propiedades al crear layouts responsivos: https://flexboxcss.com

    • Es el primer sitio real que veo usando diseño neumórfico. Tiene buena estética, y no lo había visto en la práctica
  • Excelente. Herramientas así son muy importantes porque hacen comprensible la caja mágica que es el motor de renderizado del navegador
    Si pudiéramos crear una especificación completa y legible por máquina para el renderizado de HTML y CSS, podríamos generar renderizadores. Los comportamientos particulares de cada navegador podrían quedar como extensiones encima. Sería bueno que algo como https://github.com/tawesoft/html5spec llegara a una forma utilizable por motores reales

    • Esto me recordó que Ladybird es un navegador escrito desde cero, y de hecho también ha sido bastante útil para comprobar que las especificaciones no tengan bugs
  • Está bastante cerca de algo que me venía preguntando últimamente. Estaba pensando si CSS y SVG podrían usarse como una capa de abstracción sobre bibliotecas gráficas y de UI
    Es la primera vez que oigo de node-canvas, y parece cubrir la parte de dibujo. Esta herramienta parece poder encargarse de la parte de layout, que es todo lo que necesito de una biblioteca de UI
    También me da curiosidad qué tan difícil habrá sido implementar CSS. He oído que es bastante complejo

    • Hay otro proyecto llamado Sciter que también usa CSS para apuntar a bibliotecas gráficas nativas: https://sciter.com
      Implementar CSS fue difícil, pero la mayor barrera fue que el conocimiento no está bien expuesto
      La parte más difícil es el layout de texto. Manejar glifos y recorrer en sentido inverso para RTL es un dolor de cabeza, y los saltos de línea también se vuelven realmente complejos. Es aún más difícil porque el conocimiento necesario no está reunido en un solo lugar
      Después de terminar el layout de bloques al inicio, durante varios años tuve que trabajar solo unas horas por semana mientras aprendía los detalles del text shaping y la itemización, qué hacer y qué evitar. Aprendí mucho leyendo el código fuente de Pango [1], y el resto lo fui armando con búsquedas en Google
      Fuera de eso, las especificaciones del W3C cubren casi todo. El estándar CSS2 [2] fue uno de los documentos más hermosos que he leído. Es internamente consistente y conciso, resultado de años de reflexión, prueba y error. CSS3 también es excelente, pero CSS2 es la base de todo
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • Es una gran contribución al mundo. Parece el típico caso en el que todos piensan “alguien debería hacer $X”, pero nadie lo había hecho
    Como alguien a quien le gusta usar CSS para layouts, hoy en día dependo sobre todo de Flexbox y Grid. Entiendo perfectamente que todavía no estén soportados, pero me pregunto si hay planes de soportarlos algún día. Si los hay, ¿cómo podrían ayudar otras personas?

  • Realmente impresionante. Creo que la mayoría no sabe lo difícil que es convertir HTML a PNG de forma programática
    Por la diferencia entre Node y el navegador, o la diferencia entre HTML y Canvas, terminas chocando con muchísimos problemas pequeños

  • Se ve útil. Es difícil imaginar cuánto trabajo implica primero entender CSS y luego construir un motor de layout encima