3 pontos por GN⁺ 2024-03-22 | 3 comentários | Compartilhar no WhatsApp
  • O Dropflow é um mecanismo de layout CSS criado para explorar os padrões fundamentais de CSS como inline, block, float, positioning e, futuramente, table; flexbox e grid não são o foco
  • Pode ser usado no backend com Node e node-canvas para geração de PDF e imagens e, no navegador, para renderizar texto rico com quebra de linha em canvas
  • O layout de texto oferece suporte a texto bidirecional e RTL, fallback de fonte por grafema, diacríticos coloridos, registro de fontes OpenType/TrueType e shaping baseado em HarfBuzz
  • O suporte a CSS cobre mais de 30 propriedades; funcionam float, clear, display: inline-block, position: relative, overflow, z-index, zoom etc., enquanto table, absolute, fixed, transform e outros ainda estão planejados
  • O melhor desempenho vem da API hyperscript e de objetos de estilo reutilizáveis que pulam o parsing de HTML/CSS; em um MacBook Pro 2019, converte The Little Prince com mais de 500 parágrafos de HTML para imagem em menos de 160 ms

Escopo do layout CSS coberto pelo Dropflow

  • O Dropflow é um mecanismo de layout CSS criado para explorar o escopo dos padrões fundamentais de CSS
    • O foco é inline, block, float, positioning e, futuramente, tables
    • flexbox e grid não são o objetivo
  • Ele traz uma implementação de layout de texto de alta qualidade e consegue exibir vários idiomas do mundo
  • São apresentados dois casos de uso
    • Geração de PDF ou imagens no backend com Node e node-canvas
    • Renderização de texto rico com quebra de linha em canvas no navegador

Principais recursos

  • Suporta mais de 30 propriedades CSS, incluindo propriedades complexas como float
  • Os recursos ligados a texto são amplos
    • Texto bidirecional e texto RTL
    • Fallback de fonte por grafema
    • Diacríticos coloridos
    • Tratamento desejável de quebra de linha, por exemplo levando o padding inicial para a linha seguinte
    • Shaping otimizado
  • Além de HTML/CSS, também suporta a API hyperscript h(), na qual os estilos são passados como objetos
  • Buffers OpenType/TrueType podem ser registrados, e esse registro é obrigatório
  • <img> suporta JPEG, BMP, PNG e GIF, embora o suporte possa variar conforme o backend
  • Estilos herdados e em cascata não são calculados duas vezes
  • Tem tipagem completa, muitos testes e busca executar com rapidez

Estado do suporte a CSS

  • Entre os itens que funcionam em inline formatting estão 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 e outros
  • Em inline formatting, font-variant, letter-spacing, tab-size, text-decoration, unicode-bidi estão em estado planejado
  • Em block formatting, clear e float funcionam
    • horizontal-tb, vertical-lr e vertical-rl de writing-mode estão parcialmente implementados
    • Já foi implementado em BFC, mas ainda não em IFC
  • Em boxes and positioning, várias propriedades de caixa e posicionamento funcionam
    • 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
  • Em boxes and positioning, display: table, min/max-height, min/max-width, position: absolute, position: fixed, transform estão em estado planejado

Fluxo básico de uso

  • O Dropflow funciona com base em um DOM com estilos herdados e calculados, da mesma forma que um navegador
  • O fluxo geral é o seguinte
    • Registrar fontes com FontFace antes do layout
    • Criar objetos de estilo com flow.style()
    • Criar o DOM com flow.h()
    • Preparar a árvore a ser renderizada com flow.dom()
    • Executar layout e pintura em todo o canvas com renderToCanvas()
  • Também existe uma API HTML, mas ela só é recomendada quando desempenho não é importante ou para aprendizado
    • O parsing acrescenta tempo extra e aumenta bastante o tamanho do bundle
    • Está escrito que o parsing de HTML é rápido graças ao @fb55
    • No momento, só a propriedade HTML style é suportada; class ainda não funciona

Características de desempenho

  • Desempenho é um objetivo principal e vem logo depois da correção
  • São apresentados números de desempenho com base em exemplos
    • Converter 8 parágrafos com vários spans inline e fontes diferentes de HTML para imagem leva 9 ms em um MacBook Pro 2019 e 13 ms em um MacBook Pro 2012
    • Converter The Little Prince com mais de 500 parágrafos de HTML para imagem leva menos de 160 ms em um MacBook Pro 2019 e menos de 250 ms em um MacBook Pro 2012
    • Gerar uma palavra de 10 letras e executar apenas o layout leva menos de 25 µs em um MacBook Pro 2019 e menos de 50 µs em um MacBook Pro 2012
  • O desempenho mais alto é obtido ao criar o DOM diretamente com a API hyperscript e pular a etapa comum de parsing de HTML/CSS
  • Reutilizar objetos de estilo traz ainda mais vantagem
  • Fazer reflow em larguras diferentes é mais rápido do que reconstruir a árvore de layout

Estrutura da API

  • Há duas etapas básicas
    • Registro de fontes
    • Criação do DOM com a API Hyperscript ou a API Parse
  • Em usos simples, o DOM pode ser renderizado diretamente em canvas
    • renderToCanvas(el, canvas) renderiza todo o layout usando width e height do canvas como tamanho da viewport
  • Usando a API de nível mais baixo, é possível preservar o layout
    • Carregar recursos dependentes
    • Criar o layout do DOM
    • Fazer reflow do layout
    • Pintar em um destino como HTML5 canvas
  • Essa abordagem pode ser usada para fazer reflow em outro tamanho, evitar pintar layouts invisíveis ou obter intrinsics

Processamento de fontes

  • O primeiro passo de um programa com Dropflow é registrar as fontes que serão selecionadas pelas propriedades CSS de fonte
  • Como o Dropflow não procura fontes do sistema, é preciso criar e adicionar pelo menos um FontFace
  • A API de registro de fontes implementa um subconjunto da CSS Font Loading API e adiciona o método não padronizado loadSync
  • No lado do servidor, URLs file:/// podem ser carregadas de forma síncrona com readFileSync
  • ArrayBuffer é carregado imediatamente no construtor, como no navegador
  • registerNotoFonts registra todas as famílias de fontes Noto Sans
    • As fontes são distribuídas pelo FontSource e hospedadas pelo jsDelivr
    • Há mais de 200 fontes Noto Sans, e a string unicodeRange das fontes CJK é grande, então é uma importação pesada
    • Para uso em produção no navegador, o texto recomenda registrar fontes individuais
    • Para Latin, fontes itálicas são registradas, e para todos os scripts são registradas normal 400 e bold 700
  • Chinês, coreano e japonês compartilham pontos de código Unicode, mas os caracteres podem ser renderizados de forma diferente; por isso, quando possível, é melhor usar fontes específicas para cada idioma

Layout, reflow e paint

  • layout(el) cria um layout que inclui box tree, fragmentation tree e glyphs
    • A box tree corresponde em grande parte à DOM tree, mas pode ter mais boxes por causa de conteúdo de texto anônimo ou menos por causa de display: none
  • reflow(layout, width = 640, height = 480) posiciona os boxes, divide o texto em linhas e o deixa pronto para paint
    • Margin collapsing de block boxes
    • Envio de texto ao HarfBuzz
    • Iteração de fallback de fonte
    • Reshaping conforme quebra de linha e break points
    • Posicionamento de floats e tratamento de clear
    • Posicionamento de spans de texto moldado e backgrounds conforme direction e direção do texto
    • Cálculo de intrinsics de conteúdo float, inline-block e absolute
    • Tratamento de position depois do fluxo normal
  • Os destinos de paint atualmente suportados são Canvas e SVG
    • paintToCanvas pinta em canvas do navegador, node-canvas e contexts semelhantes compatíveis com o padrão
    • paintToSvg cria uma string SVG e inclui regras @font-face que referenciam as URLs passadas para FontFace
    • paintToSvgElements não adiciona <svg> nem regras @font-face, para uso ao desenhar dentro de um SVG existente
    • paintToHtml gera uma lista plana de elementos posicionados absolutamente; não é recomendado para uso geral, mas pode ser útil durante o desenvolvimento

API de DOM e hooks de ambiente

  • O HTMLElement raiz obtido pelas APIs Hyperscript e Parse oferece métodos para encontrar elementos por tag name, id e class, como na família querySelector do navegador
    • query(selector) retorna um HTMLElement ou null
    • queryAll(selector) retorna HTMLElement[]
  • HTMLElement pode ter caixas de renderização conectadas
    • Em geral é uma só, mas podem ser várias quando conteúdo inline e block se misturam
    • BlockContainer é criado para elemento com posicionamento absoluto, elemento flutuante, inline-block e elemento em nível de bloco
    • ReplacedBox é criado para imagens
  • O Dropflow foi projetado para se ajustar a diferentes ambientes
    • No navegador, carrega fontes e imagens com fetch e registra buffers de fonte em document.fonts
    • No Nodejs, fontes podem ser carregadas de forma síncrona com fs.readFileSync
    • Se houver backend de canvas e node-canvas, ele chama registerFont do node-canvas
    • Como o node-canvas não suporta font buffer, é preciso usar URL file://
  • Para usar @napi-rs/canvas ou skia-canvas, são necessárias algumas linhas de código ligando flow.environment.registerFont à API de registro de fontes correspondente
  • O ambiente tem 6 hooks
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

Shaping de texto com base em HarfBuzz

  • O layout de glyphs é feito por HarfBuzz compilado para WebAssembly
  • O objetivo é alcançar uma precisão difícil de obter com uma abordagem que posiciona spans de texto usando a API measureText
  • Um exemplo: no Google Sheets, se apenas o "V" de "AV" for pintado com outra cor, o kerning desaparece e as letras ficam mais distantes do que o original
    • Isso acontece porque duas chamadas de measureText e fillText passam a ocorrer por letra, fazendo desaparecer o avanço contextual dos glyphs
  • O Dropflow usa HarfBuzz em limites de shaping mais amplos, e não no ponto em que a cor muda, para oferecer suporte mais preciso a fontes
  • O HarfBuzz compilado para WebAssembly pode apresentar métricas de desempenho parecidas com measureText de CanvasRenderingContext2D
    • Não é tão rápido quanto measureText, mas também não é muito mais lento
    • O texto diz que nenhum dos dois é o gargalo dominante da pilha de layout de texto
  • Layout de texto baseado em measureText precisa de word cache para funcionar rápido, e é dito que aplicativos do GSuite usam essa abordagem
    • Word cache não consegue suportar fontes com efeitos que atravessam espaços
    • Para suportar essas fontes, seria necessário fazer busca binária no índice de quebra do parágrafo, o que seria muito mais lento do que passar o parágrafo inteiro ao HarfBuzz
    • Diacríticos coloridos são impossíveis com measureText

Projetos dos quais depende

  • O Dropflow não tem dependências em package.json, mas aproveita o trabalho de vários projetos
  • As dependências JavaScript foram incorporadas ao projeto e modificadas em diferentes níveis para manter o foco e evitar problemas de dependency-of-dependency
  • Os principais projetos são os seguintes

3 comentários

 
winterjung 2024-03-23

O título original era "Show HN: Dropflow, a CSS layout engine for node or <canvas>". Agora está como "GN⁺: Apresentação no HN: mecanismo de layout CSS para Node ou <canvas>, Dropflow</canvas>".

 
dlehals2 2024-03-22

Como há uma tag no título, a parte do título na página de detalhes fica quebrada.. hehe. Tem que fazer escape..

 
GN⁺ 2024-03-22
Comentários no Hacker News
  • Hoje em dia, a forma padrão de gerar PDFs bonitos no backend é subir um navegador headless e usar as APIs do navegador para converter HTML/CSS em PDF, mas o custo de rodar instâncias de navegador no servidor e escalar isso para grandes volumes de trabalho é bem alto
    Isso muda o jogo, porque agora dá para projetar e gerar PDFs com HTML/CSS sem o overhead de um navegador

    • É impressionante o quanto a web chegou até aqui. Que a melhor forma de criar documentos PDF bonitos seja literalmente executar um navegador web no servidor seria algo difícil de imaginar nos anos 90 ou 2000
    • Usar um navegador tem a vantagem de que o PDF gerado usa vetores e fontes. Já uma abordagem baseada em Canvas provavelmente vai resultar em imagens na maior parte do PDF, o que talvez não seja um grande problema para a maioria dos usos
    • Já usei https://ekoopmans.github.io/html2pdf.js/ e funcionou muito bem
    • Estou um pouco confuso. Tenho usado a biblioteca Prawn há bastante tempo para geração de PDF no backend de um side project: https://github.com/prawnpdf/prawn
      Só que os PDFs que eu gero definitivamente não são tão bonitos, então talvez essa seja a diferença
    • Já construí renderizadores de PDF para alguns clientes, e a maior exigência em PDF era acessibilidade
      Todos precisavam de conformidade com a ADA, então seria difícil migrar para um renderizador em Canvas. Isso eliminaria a acessibilidade
  • Parece muito bom. No meu emprego anterior, desenvolvi uma plataforma imersiva de aprendizado online, em que usávamos Oculus Quest 2 para ensinar idiomas estrangeiros a pessoal do Departamento de Defesa, com WebXR, Three.js etc.
    Unity3D uma vez já bastou, e eu também não queria passar por revisão de app store. Como tínhamos um parque próprio de dispositivos, tudo bem
    Um dos maiores desafios foi criar um fluxo de trabalho para o conteúdo didático, e eu tive que fazer quase tudo sozinho. Instrutores de idiomas de verdade criavam PDFs a partir do PowerPoint, e eu os enviava para o banco de dados de conteúdo com um editor dedicado que construí, implantava no ambiente de treinamento e então renderizava com PDFJS em um elemento Canvas para usar como textura em retângulos 3D
    Se uma ferramenta como essa existisse, as pessoas não teriam precisado dar essa volta com PowerPoint para criar material. A abordagem com PowerPoint melhorou muito a velocidade do fluxo de trabalho em relação à tentativa anterior, na qual eles criavam imagens no Photoshop, mas teria sido ainda melhor se eu pudesse construir um editor de placas dentro do app e eliminar o ciclo de “imaginar como ficaria no ambiente → exportar para PDF → enviar para o BD → ver como realmente ficou”
    Embora, no fim das contas, isso talvez não importasse muito, já que não tínhamos uma equipe de desenvolvimento de negócios ou marketing que soubesse vender qualquer coisa além de serviço

  • Eu estava procurando algo assim para o meu projeto https://htwins.net/scale2 e outros trabalhos que usam SVG ou Canvas

    • Eu realmente gostaria de ler um relato de construção sobre como isso foi feito
  • Se você sofre com Flexbox, pode usar uma ferramenta que simplifica o processo para não precisar pensar em várias propriedades ao criar layouts responsivos: https://flexboxcss.com

    • Na prática, é o primeiro caso que vejo de design neumórfico em um site. A estética é boa, mas eu nunca tinha visto isso em uso real
  • Excelente. Ferramentas assim são muito importantes porque tornam compreensível a caixa mágica que é o motor de renderização do navegador
    Se fosse possível criar uma especificação completa e legível por máquina para renderização de HTML e CSS, seria possível gerar renderizadores. Os comportamentos peculiares de cada navegador poderiam ficar como extensões em cima disso. Seria ótimo ver algo como https://github.com/tawesoft/html5spec se tornar algo usado por motores reais

    • Isso me fez lembrar que o Ladybird é um navegador escrito do zero, e na prática ele também foi bastante útil para verificar se não havia bugs na especificação
  • Isso é bem próximo de algo em que eu vinha pensando ultimamente. Eu estava me perguntando se CSS e SVG poderiam ser usados como uma camada de abstração sobre bibliotecas gráficas e de UI
    É a primeira vez que ouço falar de node-canvas, mas parece preencher a parte de desenho. Esta ferramenta parece poder assumir a parte de layout, que é tudo o que eu preciso em uma biblioteca de UI
    Também fico curioso sobre quão difícil deve ter sido implementar CSS. Ouvi dizer que é bastante complexo

    • Existe outro projeto chamado Sciter que também usa CSS tendo bibliotecas gráficas nativas como alvo: https://sciter.com
      Implementar CSS foi difícil, mas a maior barreira era que o conhecimento não está muito exposto
      A parte mais difícil é o layout de texto. Lidar com glifos e iterar ao contrário para RTL é de enlouquecer, e a quebra de linha também fica realmente complexa. Fica ainda mais difícil porque o conhecimento necessário não está reunido em um só lugar
      Depois de terminar o layout em bloco no começo, passei anos trabalhando só algumas horas por semana enquanto aprendia os detalhes de modelagem e itemização de texto, o que fazer e o que evitar. Muito disso aprendi lendo o código-fonte do Pango [1], e o resto fui montando com pesquisas no Google
      Fora isso, as especificações do W3C cobrem quase tudo. O padrão CSS2 [2] foi um dos documentos mais bonitos que já li. É internamente consistente e conciso, resultado de anos de reflexão e tentativa e erro. CSS3 também é ótimo, mas CSS2 é a base de tudo
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • Uma grande contribuição para o mundo. Parece um caso clássico de algo que todo mundo pensa “alguém deveria fazer $X”, mas ninguém fez
    Como alguém que gosta de usar CSS para layout, hoje em dia dependo principalmente de Flexbox e Grid. Entendo perfeitamente que isso ainda não seja suportado, mas fico curioso se há planos para suportar isso algum dia. Se houver, como outras pessoas poderiam ajudar?

  • É realmente impressionante. Acho que a maioria das pessoas não faz ideia de quão difícil é converter HTML em PNG programaticamente
    Você acaba batendo em inúmeros probleminhas por causa das diferenças entre Node e navegador, ou das diferenças entre HTML e Canvas

  • Parece útil. É difícil imaginar quanto trabalho é necessário só para primeiro entender CSS e depois construir um motor de layout por cima disso