3 Punkte von GN⁺ 2024-03-22 | 3 Kommentare | Auf WhatsApp teilen
  • Dropflow ist eine CSS-Layout-Engine, die entwickelt wurde, um grundlegende CSS-Standards wie inline, block, float, positioning und künftig table zu erforschen; flexbox und grid gehören nicht zum Zielbereich
  • Mit Node und node-canvas kann sie im Backend für die Erzeugung von PDFs und Bildern verwendet werden, und im Browser kann sie Rich-Text mit Zeilenumbrüchen auf canvas rendern
  • Das Textlayout unterstützt bidirektionalen und RTL-Text, Font-Fallback auf Graphem-Ebene, farbige diakritische Zeichen, die Registrierung von OpenType-/TrueType-Schriften sowie HarfBuzz-basiertes Shaping
  • Der Umfang der CSS-Unterstützung umfasst mehr als 30 Eigenschaften; unter anderem funktionieren float, clear, display: inline-block, position: relative, overflow, z-index und zoom, während table, absolute, fixed, transform usw. noch geplant sind
  • Die höchste Performance wird mit der hyperscript API und wiederverwendbaren Style-Objekten erreicht, die HTML/CSS-Parsing überspringen; auf einem MacBook Pro von 2019 wird Der kleine Prinz mit mehr als 500 Absätzen in weniger als 160 ms von HTML in ein Bild umgewandelt

Welchen CSS-Layout-Bereich Dropflow abdeckt

  • Dropflow ist eine CSS-Layout-Engine, die geschaffen wurde, um den Bereich grundlegender CSS-Standards zu erforschen
    • Zielbereich sind inline, block, float, positioning und künftig tables
    • flexbox und grid gehören nicht dazu
  • Es verfügt über eine hochwertige Implementierung für Textlayout und kann viele Sprachen der Welt darstellen
  • Als Einsatzgebiete werden zwei Szenarien genannt
    • Erzeugung von PDFs oder Bildern im Backend mit Node und node-canvas
    • Rendering von umbrechendem Rich-Text auf canvas im Browser

Hauptfunktionen

  • Es unterstützt mehr als 30 CSS-Eigenschaften, darunter auch komplexe Eigenschaften wie float
  • Der Funktionsumfang rund um Text ist breit
    • Bidirektionaler Text und RTL-Text
    • Font-Fallback auf Graphem-Ebene
    • Farbige diakritische Zeichen
    • Korrekte Zeilenumbruchbehandlung, etwa wenn ein führendes Padding in die nächste Zeile übernommen wird
    • Optimiertes Shaping
  • Als Eingabe werden nicht nur HTML/CSS unterstützt, sondern auch die hyperscript-h()-API, bei der Styles als Objekte übergeben werden
  • OpenType-/TrueType-Buffer können registriert werden, und die Registrierung ist erforderlich
  • <img> unterstützt JPEG, BMP, PNG und GIF, wobei die Unterstützung je nach Backend variieren kann
  • Vererbte und kaskadierte Styles werden nicht doppelt berechnet
  • Ziel sind vollständige Typisierung, umfangreiche Tests und hohe Geschwindigkeit

Status der CSS-Unterstützung

  • In der Inline-Formatierung funktionieren unter anderem 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 und word-spacing
  • In der Inline-Formatierung sind font-variant, letter-spacing, tab-size, text-decoration und unicode-bidi noch geplant
  • In der Block-Formatierung funktionieren clear und float
    • Bei writing-mode sind horizontal-tb, vertical-lr und vertical-rl teilweise implementiert
    • Im BFC ist es implementiert, im IFC jedoch noch nicht
  • Bei Boxes and positioning funktionieren verschiedene Box- und Positionierungs-Eigenschaften
    • 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
  • Bei Boxes and positioning sind display: table, min/max-height, min/max-width, position: absolute, position: fixed und transform noch geplant

Grundlegender Nutzungsablauf

  • Dropflow arbeitet wie ein Browser auf Basis eines DOM mit vererbten und berechneten Styles
  • Der typische Ablauf ist wie folgt
    • Vor dem Layout Schriften mit FontFace registrieren
    • Mit flow.style() Style-Objekte erzeugen
    • Mit flow.h() ein DOM erstellen
    • Mit flow.dom() den zu rendernden Baum vorbereiten
    • Mit renderToCanvas() Layout und Paint auf dem gesamten Canvas ausführen
  • Es gibt auch eine HTML-API, sie wird aber nur empfohlen, wenn Performance nicht entscheidend ist oder zu Lernzwecken
    • Parsing kostet zusätzliche Zeit und vergrößert das Bundle deutlich
    • Es wird erwähnt, dass das HTML-Parsing dank @fb55 schnell ist
    • Aktuell wird nur das HTML-Attribut style unterstützt; class funktioniert noch nicht

Performance-Eigenschaften

  • Performance ist ein oberstes Ziel und nach Korrektheit am wichtigsten
  • Es werden beispielhafte Leistungswerte genannt
    • Die Umwandlung von 8 Absätzen mit mehreren inline spans und unterschiedlichen Schriften von HTML in ein Bild dauert auf einem MacBook Pro von 2019 9 ms, auf einem MacBook Pro von 2012 13 ms
    • Die Umwandlung von Der kleine Prinz mit mehr als 500 Absätzen von HTML in ein Bild dauert auf einem MacBook Pro von 2019 unter 160 ms, auf einem MacBook Pro von 2012 unter 250 ms
    • Das Erzeugen eines 10-Zeichen-Worts und die reine Layout-Berechnung dauern auf einem MacBook Pro von 2019 unter 25 µs, auf einem MacBook Pro von 2012 unter 50 µs
  • Die schnellste Performance wird erreicht, wenn mit der hyperscript API das DOM direkt erstellt und der übliche HTML/CSS-Parsing-Schritt übersprungen wird
  • Noch größere Vorteile ergeben sich, wenn Style-Objekte wiederverwendet werden
  • Reflow bei anderer Breite ist schneller, als den Layout-Baum neu zu erstellen

API-Aufbau

  • Die Grundschritte sind zwei
    • Schriftregistrierung
    • DOM-Erstellung über die Hyperscript API oder die Parse API
  • Bei einfacher Nutzung kann das DOM direkt auf ein Canvas gerendert werden
    • renderToCanvas(el, canvas) rendert das vollständige Layout, wobei width und height des Canvas als Viewport-Größe verwendet werden
  • Mit einer API auf niedrigerer Ebene kann das Layout erhalten bleiben
    • Abhängige Ressourcen laden
    • Layout des DOM erzeugen
    • Layout-Reflow
    • Auf ein Ziel wie HTML5 canvas painten
  • Das kann genutzt werden, um mit anderen Größen zu reflowen, unsichtbares Layout nicht zu painten oder Intrinsics zu ermitteln

Schriftverarbeitung

  • Der erste Schritt in einem Dropflow-Programm ist die Registrierung der Schriften, die über CSS-Schrifteigenschaften ausgewählt werden sollen
  • Da Dropflow keine Systemschriften durchsucht, muss mindestens einmal FontFace erstellt und hinzugefügt werden
  • Die API zur Schriftregistrierung implementiert eine Teilmenge der CSS Font Loading API und ergänzt die nicht standardisierte Methode loadSync
  • Serverseitig können file:///-URLs synchron über readFileSync geladen werden
  • ArrayBuffer wird wie im Browser sofort im Konstruktor geladen
  • registerNotoFonts registriert alle Noto-Sans-Schriftfamilien
    • Die Schriften werden von FontSource verteilt und von jsDelivr gehostet
    • Da es mehr als 200 Noto-Sans-Schriften gibt und die unicodeRange-Strings der CJK-Schriften groß sind, ist dies ein großer Import
    • Für den produktiven Einsatz im Browser sei es besser, einzelne Schriften zu registrieren
    • Für Latin wird eine italic-Schrift registriert, für alle Schriftsysteme normal 400 und bold 700
  • Chinesisch, Koreanisch und Japanisch teilen sich gemeinsame Unicode-Codepoints, Zeichen können aber unterschiedlich gerendert werden; wenn möglich, sind sprachspezifische Schriften besser

Layout, Reflow und Paint

  • layout(el) erzeugt ein box tree, einen Fragmentierungsbaum und Glyphen umfassendes Layout
    • Der box tree entspricht grob dem DOM tree, kann wegen anonymem Textinhalt aber mehr Boxes enthalten oder wegen display: none weniger
  • reflow(layout, width = 640, height = 480) positioniert Boxes, bricht Text in Zeilen um und macht ihn paintbar
    • Margin-Collapsing von Block-Boxes
    • Übergabe von Text an HarfBuzz
    • Iteration über Font-Fallbacks
    • Reshaping je nach Zeilenumbruch und Breakpoints
    • Platzierung von Floats und Behandlung von clear
    • Platzierung von geformten Textspans und Hintergründen gemäß direction und Textrichtung
    • Berechnung der Intrinsics von float, inline-block und absolute-Inhalten
    • Verarbeitung von position nach dem normalen Flow
  • Als Paint-Ziele werden derzeit Canvas und SVG unterstützt
    • paintToCanvas paintet auf Browser-Canvas, node-canvas und ähnliche standardkonforme Contexts
    • paintToSvg erzeugt einen SVG-String und enthält @font-face-Regeln, die auf die an FontFace übergebenen URLs verweisen
    • paintToSvgElements fügt kein <svg> und keine @font-face-Regeln hinzu, damit es zum Zeichnen innerhalb eines bestehenden SVG verwendet werden kann
    • paintToHtml erzeugt eine flache Liste absolut positionierter Elemente; empfohlen wird es nicht, für die Entwicklung kann es aber nützlich sein

DOM-API und Umgebungs-Hooks

  • Das Root-HTMLElement aus der Hyperscript- und Parse-API bietet Methoden zum Finden von Elementen über Tag-Name, id und class, ähnlich wie die querySelector-Familie im Browser
    • query(selector) gibt ein HTMLElement oder null zurück
    • queryAll(selector) gibt HTMLElement[] zurück
  • HTMLElement kann verknüpfte Render-Boxes haben
    • Üblicherweise eine, bei gemischtem inline- und block-Inhalt aber mehrere
    • BlockContainer wird für absolute positioned elements, floated elements, inline-blocks und block-level elements erzeugt
    • ReplacedBox wird für Bilder erzeugt
  • Dropflow ist so entworfen, dass es an verschiedene Umgebungen angepasst werden kann
    • Im Browser werden Schriften und Bilder per fetch geladen, und Font-Buffer werden in document.fonts registriert
    • In Nodejs können Schriften synchron per fs.readFileSync geladen werden
    • Wenn ein Canvas-Backend und node-canvas vorhanden sind, wird registerFont von node-canvas aufgerufen
    • Da node-canvas keine Font-Buffer unterstützt, müssen file://-URLs verwendet werden
  • Für @napi-rs/canvas oder skia-canvas braucht man einige Zeilen Code, um flow.environment.registerFont an die jeweilige API zur Schriftregistrierung anzubinden
  • Die Umgebung besitzt 6 Hooks
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

HarfBuzz-basiertes Text-Shaping

  • Das Glyph-Layout wird von HarfBuzz ausgeführt, das zu WebAssembly kompiliert wurde
  • Ziel ist eine Genauigkeit, die mit einem Ansatz über die measureText-API für die Positionierung von Textspans schwer zu erreichen ist
  • Als Beispiel wird genannt, dass in Google Sheets beim Einfärben nur des "V" in "AV" das Kerning verschwindet und die Buchstaben weiter auseinanderstehen als ursprünglich
    • Der Grund ist, dass pro Buchstabe zwei Aufrufe von measureText und fillText erfolgen und dadurch kontextabhängige Glyph-Advances verloren gehen
  • Dropflow unterstützt Schriften präziser, indem HarfBuzz an gröberen Shaping-Grenzen statt an Farbwechselpunkten eingesetzt wird
  • Das zu WebAssembly kompilierte HarfBuzz kann Leistungswerte erreichen, die denen von CanvasRenderingContext2D mit measureText ähneln
    • Es ist laut Beschreibung nicht so schnell wie measureText, aber auch nicht deutlich langsamer
    • Beide seien nicht der dominierende Engpass im Textlayout-Stack
  • Textlayout auf Basis von measureText braucht einen Word-Cache, um schnell zu sein; es wird erwähnt, dass GSuite-Apps diesen Ansatz verwenden
    • Ein Word-Cache kann keine Schriften unterstützen, die Effekte über Leerzeichen hinweg haben
    • Um solche Schriften zu unterstützen, wäre an den Umbruchindizes eines Absatzes eine Binärsuche nötig, was viel langsamer sei, als den ganzen Absatz an HarfBuzz zu übergeben
    • Farbige diakritische Zeichen sind mit measureText nicht möglich

Abhängige Projekte

  • Dropflow hat keine package.json-Abhängigkeiten, nutzt aber die Arbeit mehrerer Projekte
  • JavaScript-Abhängigkeiten sind ins Projekt eingecheckt und wurden in unterschiedlichem Ausmaß angepasst, um den Fokus beizubehalten und dependency-of-dependency-Probleme zu vermeiden
  • Zu den wichtigsten Projekten gehören

3 Kommentare

 
winterjung 2024-03-23

Der ursprüngliche Titel war also „Show HN: Dropflow, a CSS layout engine for node or <canvas>“. Jetzt steht er als „GN⁺: HN vorgestellt: CSS-Layout-Engine für Node oder <canvas>, Dropflow</canvas>“ drin.

 
dlehals2 2024-03-22

Im Titel sind Tags enthalten, daher wird der Titelbereich auf der Detailseite beschädigt … haha. Man sollte escapen …

 
GN⁺ 2024-03-22
Hacker-News-Kommentare
  • Heutzutage besteht der Standardweg, im Backend schöne PDFs zu erzeugen, darin, einen Headless-Browser zu starten und HTML/CSS per Browser-API in PDF umzuwandeln. Die Kosten dafür, Browser-Instanzen auf dem Server laufen zu lassen und für hohe Last zu skalieren, sind jedoch ziemlich hoch.
    Das hier ist ein Tool, das die Spielregeln verändert: Man kann PDFs nun mit HTML/CSS entwerfen und erzeugen, ohne den Browser-Overhead in Kauf nehmen zu müssen.

    • Es ist erstaunlich, wie weit das Web gekommen ist. Dass die beste Methode, schöne PDF-Dokumente zu erstellen, darin besteht, auf dem Server einfach einen Webbrowser auszuführen, wäre in den 90ern oder 2000ern kaum vorstellbar gewesen.
    • Der Vorteil eines Browsers ist, dass das erzeugte PDF Vektoren und Schriftarten verwendet. Bei einem Canvas-basierten Ansatz wird im PDF dagegen vermutlich das meiste zu Bildern, was für die meisten Anwendungsfälle aber kein großes Problem sein muss.
    • Ich habe https://ekoopmans.github.io/html2pdf.js/ verwendet, und das hat ziemlich gut funktioniert.
    • Ich bin etwas verwirrt. Ich verwende seit ziemlich langer Zeit in Side-Project-Backends zur PDF-Erzeugung die Bibliothek Prawn: https://github.com/prawnpdf/prawn
      Die PDFs, die ich erzeuge, sind allerdings definitiv nicht besonders hübsch, also könnte genau das der Unterschied sein.
    • Ich habe für einige Kunden PDF-Renderer gebaut, und die wichtigste Anforderung bei PDFs war Barrierefreiheit.
      Alle mussten ADA-konform sein, daher ist ein Wechsel zu einem Canvas-Renderer schwierig, weil dabei die Barrierefreiheit verloren geht.
  • Sieht wirklich gut aus. In meinem vorherigen Job habe ich eine immersive Online-Lernplattform entwickelt, mit der Personal des Verteidigungsministeriums über die Oculus Quest 2 in Fremdsprachen geschult wurde, unter Einsatz von WebXR, Three.js und mehr.
    Unity3D reichte mir einmal, und ich wollte auch nicht durch App-Store-Prüfungen gehen. Da wir einen eigenen Gerätepark hatten, war das in Ordnung.
    Eine der größten Herausforderungen war der Aufbau eines Workflows für Unterrichtsinhalte, und ich musste das fast alleine stemmen. Echte Sprachlehrkräfte erstellten PDFs in PowerPoint, und mit einem von mir gebauten Spezialeditor wurden die PDFs in die Content-Datenbank hochgeladen, in die Trainingsumgebung ausgerollt und dann mit PDFJS auf ein Canvas-Element gerendert, das ich als Textur für 3D-Rechtecke verwendet habe.
    Mit einem solchen Tool hätten die Leute ihre Materialien nicht über PowerPoint erstellen müssen. Der PowerPoint-Ansatz war gegenüber einem früheren Versuch, bei dem Bilder in Photoshop erstellt wurden, bereits eine deutliche Verbesserung der Workflow-Geschwindigkeit. Noch besser wäre es aber gewesen, einen Schild-Editor direkt in der App zu haben, um die Schleife „raten, wie es in der Umgebung aussehen wird → als PDF exportieren → in die DB hochladen → tatsächliches Aussehen prüfen“ zu vermeiden.
    Letztlich hätte es aber wohl keinen großen Unterschied gemacht, weil wir ohnehin kein Business-Development- oder Marketing-Team hatten, das etwas anderes als den Verkauf von Services verstand.

  • Ich habe nach so etwas für mein Projekt https://htwins.net/scale2 und andere Arbeiten gesucht, die SVG oder Canvas verwenden.

    • Ich würde unbedingt gern einen Making-of-Bericht darüber lesen, wie das gebaut wurde.
  • Wenn man mit Flexbox kämpft, kann man ein Tool verwenden, das den Prozess vereinfacht, damit man beim Erstellen responsiver Layouts nicht auf so viele Eigenschaften achten muss: https://flexboxcss.com

    • Das ist tatsächlich das erste Mal, dass ich auf einer echten Website neumorphes Design sehe. Es wirkt ästhetisch ansprechend, aber in freier Wildbahn hatte ich es bisher nicht gesehen.
  • Hervorragend. Solche Tools sind enorm wichtig, weil sie die magische Blackbox einer Browser-Rendering-Engine nachvollziehbar machen.
    Wenn man eine vollständige maschinenlesbare Spezifikation für HTML- und CSS-Rendering erstellen könnte, ließe sich daraus ein Renderer erzeugen. Browser-spezifisches Verhalten könnte dann als Erweiterung darüberliegen. Es wäre großartig, wenn so etwas wie https://github.com/tawesoft/html5spec in eine Form käme, die in echten Engines verwendet werden kann.

    • Das erinnert mich daran, dass Ladybird ein von Grund auf neu geschriebener Browser ist, und das war tatsächlich auch ziemlich nützlich, um zu überprüfen, ob die Spezifikation keine Fehler enthält.
  • Das kommt dem ziemlich nahe, worüber ich in letzter Zeit nachgedacht habe. Ich habe mich gefragt, ob man CSS und SVG als Abstraktionsschicht über Grafik- und UI-Bibliotheken verwenden könnte.
    Von node-canvas höre ich zum ersten Mal, aber es scheint den Zeichnungsteil davon abzudecken. Dieses Tool könnte den Layout-Teil übernehmen, also genau den Teil, den ich aus einer UI-Bibliothek brauche.
    Mich würde auch interessieren, wie schwierig die CSS-Implementierung war. Ich habe gehört, dass sie ziemlich komplex ist.

    • Es gibt auch ein anderes Projekt namens Sciter, das native Grafikbibliotheken mit CSS als Ziel anspricht: https://sciter.com
      Die Implementierung von CSS war schwierig, aber die größte Hürde war, dass das nötige Wissen nicht gut sichtbar dokumentiert ist.
      Der schwierigste Teil ist Text-Layout. Sich mit Glyphen zu befassen und für RTL rückwärts zu iterieren, ist schon kompliziert genug, und Zeilenumbrüche machen alles noch viel komplexer. Noch schwerer wird es dadurch, dass das erforderliche Wissen nicht an einer Stelle gesammelt ist.
      Nachdem das Block-Layout anfangs fertig war, habe ich über mehrere Jahre hinweg nur ein paar Stunden pro Woche daran gearbeitet und musste die Details von Text Shaping und Itemization lernen, also was man tun und was man vermeiden sollte. Vieles habe ich durch das Lesen des Pango-[1]-Quellcodes gelernt, den Rest durch zusammenstückelte Google-Suchen.
      Davon abgesehen decken die W3C-Spezifikationen fast alles ab. Der CSS2-Standard [2] war eines der schönsten Dokumente, die ich je gelesen habe. Er ist intern konsistent und prägnant und spiegelt jahrelanges Nachdenken sowie Versuch und Irrtum wider. CSS3 ist ebenfalls großartig, aber CSS2 ist die Grundlage von allem.
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • Das ist ein großer Beitrag für die Welt. Es wirkt wie ein klassischer Fall von „Jemand sollte $X tun“, den alle denken, aber niemand umsetzt.
    Als jemand, der CSS für Layouts gern verwendet, verlasse ich mich heute meist auf Flexbox und Grid. Dass das noch nicht unterstützt wird, ist völlig verständlich, aber ich frage mich, ob geplant ist, das irgendwann zu unterstützen. Falls ja: Wie könnten andere dabei helfen?

  • Wirklich beeindruckend. Die meisten Leute wissen vermutlich nicht, wie schwierig es ist, HTML programmatisch in PNG umzuwandeln.
    Wegen der Unterschiede zwischen Node und Browser oder zwischen HTML und Canvas stößt man auf unzählige kleine Probleme.

  • Sieht nützlich aus. Es ist schwer vorstellbar, wie viel Arbeit nötig ist, um zuerst CSS zu verstehen und darauf dann eine Layout-Engine aufzubauen.