Apresentando no HN: Dropflow, um mecanismo de layout CSS para Node ou `<canvas>`
(github.com/chearon)- 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,zoometc., enquantotable,absolute,fixed,transforme 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-spacinge outros - Em inline formatting,
font-variant,letter-spacing,tab-size,text-decoration,unicode-bidiestão em estado planejado - Em block formatting,
clearefloatfuncionamhorizontal-tb,vertical-lrevertical-rldewriting-modeestã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-widthtop,right,bottom,leftbox-sizingdisplay: block,inline,inline-block,flow-root,noneheight,margin,padding,overflow,width,z-index,zoomposition: relative
- Em boxes and positioning,
display: table,min/max-height,min/max-width,position: absolute,position: fixed,transformestã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
FontFaceantes 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()
- Registrar fontes com
- 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;classainda 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 usandowidtheheightdo 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 comreadFileSync ArrayBufferé carregado imediatamente no construtor, como no navegadorregisterNotoFontsregistra 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
unicodeRangedas 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
- 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
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
directione direção do texto - Cálculo de intrinsics de conteúdo
float,inline-blockeabsolute - Tratamento de
positiondepois do fluxo normal
- Os destinos de paint atualmente suportados são Canvas e SVG
paintToCanvaspinta em canvas do navegador, node-canvas e contexts semelhantes compatíveis com o padrãopaintToSvgcria uma string SVG e inclui regras@font-faceque referenciam as URLs passadas paraFontFacepaintToSvgElementsnão adiciona<svg>nem regras@font-face, para uso ao desenhar dentro de um SVG existentepaintToHtmlgera 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
HTMLElementraiz obtido pelas APIs Hyperscript e Parse oferece métodos para encontrar elementos por tag name,ideclass, como na famíliaquerySelectordo navegadorquery(selector)retorna umHTMLElementounullqueryAll(selector)retornaHTMLElement[]
HTMLElementpode 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 blocoReplacedBoxé criado para imagens
- O Dropflow foi projetado para se ajustar a diferentes ambientes
- No navegador, carrega fontes e imagens com
fetche registra buffers de fonte emdocument.fonts - No Nodejs, fontes podem ser carregadas de forma síncrona com
fs.readFileSync - Se houver backend de canvas e
node-canvas, ele chamaregisterFontdonode-canvas - Como o
node-canvasnão suporta font buffer, é preciso usar URLfile://
- No navegador, carrega fontes e imagens com
- Para usar
@napi-rs/canvasouskia-canvas, são necessárias algumas linhas de código ligandoflow.environment.registerFontà API de registro de fontes correspondente - O ambiente tem 6 hooks
wasmLocatorregisterFontresolveUrlresolveUrlSynccreateDecodedImagedestroyDecodedImage
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
measureTextefillTextpassam a ocorrer por letra, fazendo desaparecer o avanço contextual dos glyphs
- Isso acontece porque duas chamadas de
- 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
measureTextdeCanvasRenderingContext2D- 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
- Não é tão rápido quanto
- Layout de texto baseado em
measureTextprecisa 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
- harfbuzz: shaping de fontes e fornecimento das APIs de fonte essenciais
- Tehreer/SheenBidi: cálculo de limites bidi
- foliojs/linebreak: fornecimento de índices de quebra Unicode
- peggyjs/peggy: geração do parser de CSS
- fb55/htmlparser2: parsing de HTML
- google/emoji-segmenter: segmentação de emoji
- foliojs/grapheme-breaker: fornecimento de limites de grafema Unicode, fortemente modificado para Unicode 15
- foliojs/unicode-trie: usado para dados Unicode rápidos, com grandes modificações para remover partes não utilizadas
3 comentários
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>".
Como há uma tag no título, a parte do título na página de detalhes fica quebrada.. hehe. Tem que fazer escape..
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
Só que os PDFs que eu gero definitivamente não são tão bonitos, então talvez essa seja a diferença
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
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
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 é 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
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