Bei der Blockfragmentierung wird ein CSS-Feld auf Blockebene (z. B. ein Abschnitt oder Absatz) in mehrere Fragmente aufgeteilt, wenn es nicht in einen Fragmentcontainer passt. Dieser wird als Fragmentierer bezeichnet. Ein Fragmentierer ist kein Element, sondern eine Spalte in einem mehrspaltigen Layout oder eine Seite in Seitenmedien.
Für eine Fragmentierung muss sich der Inhalt in einem Fragmentierungskontext befinden. Ein Fragmentierungskontext wird meist durch einen mehrspaltigen Container (Inhalt wird in Spalten aufgeteilt) oder beim Drucken (Inhalt wird in Seiten aufgeteilt) erzeugt. Ein langer Absatz mit vielen Zeilen muss möglicherweise in mehrere Fragmente aufgeteilt werden, sodass die ersten Zeilen im ersten Fragment und die verbleibenden Zeilen in die nachfolgenden Fragmente platziert werden.
Die Blockfragmentierung entspricht einem anderen bekannten Typ der Fragmentierung: der Linienfragmentierung, auch bekannt als „Zeilenumbruch“. Jedes Inline-Element, das aus mehr als einem Wort besteht (beliebige Textknoten, <a>
-Elemente usw.) und Zeilenumbrüche zulässt, kann in mehrere Fragmente aufgeteilt werden. Jedes Fragment wird in einem anderen Linienfeld platziert. Ein Linienfeld ist die Inline-Fragmentierung, die einem Fragmentierer für Spalten und Seiten entspricht.
LayoutNG-Blockfragmentierung
LayoutNGBlockFragmentation ist eine Umformulierung der Fragmentierungs-Engine für LayoutNG, die ursprünglich in Chrome 102 verfügbar war. In Bezug auf die Datenstrukturen hat es mehrere Vor-NG-Datenstrukturen durch NG-Fragmente ersetzt, die direkt in der Fragmentstruktur dargestellt sind.
Beispielsweise unterstützen wir jetzt den Wert "avoid" für die CSS-Eigenschaften "break-before" und "break-after", mit denen Autoren Unterbrechungen direkt nach einem Header vermeiden können. Es sieht oft unangenehm aus, wenn das letzte Element auf einer Seite eine Kopfzeile ist, während der Inhalt eines Bereichs auf der nächsten Seite beginnt. Es ist besser, vor dem Header zu brechen.
Chrome unterstützt auch den Fragmentüberlauf, damit monolithische Inhalte, die angeblich nicht zerstörbar sein sollen, nicht in mehrere Spalten aufgeteilt werden und Farbeffekte wie Schatten und Transformationen korrekt angewendet werden.
Die Blockfragmentierung in LayoutNG ist jetzt abgeschlossen.
Kernfragmentierung (Blockcontainer, einschließlich Linienlayout, Gleitkommazahlen und Out-of-Flow-Positionierung) in Chrome 102. Die Flex- und Rasterfragmentierung wurde in Chrome 103 eingeführt und die Tabellenfragmentierung in Chrome 106. Die Funktion zum Drucken wurde in Chrome 108 eingeführt. Die Blockfragmentierung war die letzte Funktion, die zur Ausführung des Layouts auf die Legacy-Engine angewiesen war.
Ab Chrome 108 wird die Legacy-Engine nicht mehr zum Ausführen von Layouts verwendet.
Darüber hinaus unterstützen LayoutNG-Datenstrukturen Painting und Treffertests. Wir nutzen jedoch einige ältere Datenstrukturen für JavaScript APIs, die Layoutinformationen lesen, z. B. offsetLeft
und offsetTop
.
Durch die Bereitstellung aller Elemente mit NG können neue Funktionen implementiert und ausgeliefert werden, die nur LayoutNG-Implementierungen (und kein Gegenstück in der Legacy-Engine) haben, z. B. CSS-Containerabfragen, Ankerpositionierung, MathML und benutzerdefiniertes Layout (Houdini). Wir haben Containerabfragen etwas im Voraus versendet und Entwickler darauf hingewiesen, dass die Druckfunktion noch nicht unterstützt wird.
Wir haben den ersten Teil von LayoutNG 2019 veröffentlicht. Er bestand aus regulärem Blockcontainer-Layout, Inline-Layout, Floats und Out-of-Flow-Positionierung. Flex, Raster und Tabellen wurden jedoch nicht unterstützt. Auch die Blockfragmentierung wurde nicht unterstützt. Wir würden auf die alte Layout-Engine für Flex, Raster, Tabellen und alles, was Blockfragmentierung einschließt, zurückgreifen. Dies galt auch für Block-, Inline-, Floating- und Out-of-Flow-Elemente innerhalb von fragmentierten Inhalten. Wie Sie sehen können, ist das Upgrade einer so komplexen Layout-Engine ein sehr heikler Tanz.
Darüber hinaus war Mitte 2019 der Großteil der Kernfunktionen des LayoutNG-Layouts zur Blockfragmentierung bereits implementiert (hinter einem Flag). Warum hat der Versand so lange gedauert? Die kurze Antwort lautet: Fragmentierung muss korrekt mit verschiedenen Legacy-Systemen koexistieren, die erst entfernt oder aktualisiert werden können, wenn alle Abhängigkeiten aktualisiert wurden.
Interaktion mit Legacy-Engine
Ältere Datenstrukturen sind immer noch für JavaScript APIs verantwortlich, die Layoutinformationen lesen. Daher müssen wir Daten so in die Legacy-Engine zurückschreiben, dass sie sie verstehen. Dazu gehört die korrekte Aktualisierung der älteren mehrspaltigen Datenstrukturen wie LayoutMultiColumnFlowThread.
Fallback-Erkennung und -handhabung der Legacy-Engine
Wir mussten auf die alte Layout-Engine zurückgreifen, als sich darin Inhalte befanden, die von der LayoutNG-Blockfragmentierung noch nicht verarbeitet werden konnten. Zum Zeitpunkt des Versands der Kernfragmentierung von LayoutNG umfasste die Blockfragmentierung Flex, Raster, Tabellen und alles, was gedruckt wird. Dies war besonders schwierig, da wir ermitteln mussten, ob ein Legacy-Fallback erforderlich war, bevor Objekte im Layoutbaum erstellt wurden. Beispielsweise mussten wir feststellen, ob es einen mehrspaltigen Container-Ancestor gibt, und noch bevor wir wussten, welche DOM-Knoten ein Formatierungskontext werden würden oder nicht. Es ist ein „Huhn-und-Ei-Problem“, für das es keine perfekte Lösung gibt. Solange es aber nur ein falsch positives Verhalten gibt (ein Fallback auf „alt“, wenn es tatsächlich nicht erforderlich ist), ist es in Ordnung, denn alle Fehler in diesem Layoutverhalten sind keine neuen, sondern bereits von Chromium.
Baumspaziergang vor Anstrich
Das Voranstrichen erfolgt nach dem Layout, aber vor dem Malen. Die größte Herausforderung besteht darin, dass wir noch den Layoutobjektbaum durchlaufen müssen, aber wir haben jetzt NG-Fragmente. Wie gehen wir damit um? Wir gehen sowohl das Layout-Objekt als auch die NG-Fragmentstrukturen gleichzeitig durch! Das ist ziemlich kompliziert, da die Kartierung zwischen den beiden Bäumen nicht so einfach ist.
Während die Struktur des Layoutobjekts der DOM-Baumstruktur ähnelt, ist die Fragmentstruktur eine Ausgabe des Layouts und keine Eingabe dafür. Die Fragmentstruktur spiegelt nicht nur den Effekt einer Fragmentierung wider, einschließlich Inline-Fragmentierung (Linienfragmente) und Blockfragmentierung (Spalten- oder Seitenfragmente), sondern hat auch eine direkte hierarchische Beziehung zwischen einem enthaltenden Block und den DOM-Nachfolgern, die dieses Fragment als enthaltenden Block haben. In der Fragmentstruktur ist beispielsweise ein Fragment, das von einem absolut positionierten Element generiert wird, ein direktes untergeordnetes Element des enthaltenden Blockfragments, selbst wenn sich andere Knoten in der Abstammungskette zwischen dem Out-of-Flow-Positionierten Nachfolger und dem enthaltenden Block befinden.
Dies kann noch komplizierter sein, wenn sich ein Out-of-Flow-Element innerhalb der Fragmentierung befindet, da die Out-of-Flow-Fragmente dann direkte untergeordnete Elemente des Fragmentierers werden (und nicht ein untergeordnetes Element des von CSS annehmenden Blocks). Dies war ein Problem, das gelöst werden musste, damit es mit der Legacy-Engine koexistieren konnte. In Zukunft sollten wir diesen Code vereinfachen können, da LayoutNG so konzipiert ist, dass es alle modernen Layoutmodi flexibel unterstützt.
Die Probleme mit der Legacy-Fragmentierungs-Engine
Die Legacy-Engine, die in einer früheren Ära des Webs entwickelt wurde, verfolgt kein wirklich ein Konzept der Fragmentierung, selbst wenn es damals auch Fragmentierung gab (um die Druckfunktion zu unterstützen). Die Unterstützung der Fragmentierung wurde lediglich oben aufgeschraubt (Drucken) oder nachgerüstet (mehrspaltig).
Beim Anlegen fragmentierter Inhalte legt die Legacy-Engine alles in einem hohen Streifen an, dessen Breite der Inline-Größe einer Spalte oder Seite entspricht und deren Höhe so hoch ist, wie sie für den Inhalt erforderlich ist. Dieser hohe Streifen wird nicht auf der Seite gerendert. Stellen Sie sich das als eine virtuelle Seite vor, die anschließend zur endgültigen Anzeige neu angeordnet wird. Es ähnelt konzeptionell dem Ausdrucken eines ganzen Zeitungsartikels aus Papier in einer Spalte, den er dann in einem zweiten Schritt mit einer Schere in mehrere Teile zerlegt. (Früher haben einige Zeitungen tatsächlich ähnliche Techniken verwendet!)
Die Legacy-Engine verfolgt eine imaginäre Seiten- oder Spaltengrenze im Datenstreifen. So kann er Inhalte, die nicht über die Begrenzung hinausgehen, auf die nächste Seite oder Spalte verschieben. Wenn beispielsweise nur die obere Hälfte einer Linie auf das passt, was die Suchmaschine für die aktuelle Seite einhält, wird eine "Paginierungsstrebe" eingefügt, um sie an die Position zu verschieben, an der die Suchmaschine davon ausgeht, dass es sich bei der nächsten Seite um den oberen Teil der Seite handelt. Dann findet der größte Teil der Fragmentierung (das "Schneiden mit Schere und Platzierung") nach dem Layout statt, indem die Seiten durch das Abschneiden der Inhalte in Teile abgeschnitten werden. Dies machte einige Dinge im Wesentlichen unmöglich, wie die Anwendung von Transformationen und die relative Positionierung nach der Fragmentierung (was die Spezifikation erfordert). Darüber hinaus gibt es zwar eine gewisse Unterstützung für die Tabellenfragmentierung in der Legacy-Engine, aber keine flexible oder Rasterfragmentierung.
Hier ist eine Abbildung, wie ein dreispaltiges Layout intern in der Legacy-Engine dargestellt wird, bevor wir eine Schere, die Platzierung und den Klebstoff verwenden (wir haben eine bestimmte Höhe, sodass nur vier Zeilen passen, aber unten ist etwas überschüssiger Platz):
Da die Legacy-Layout-Engine den Inhalt während des Layouts nicht fragmentiert, kommen viele seltsame Artefakte vor, wie z. B. die relative Positionierung und fehlerhafte Transformationen sowie das Abschneiden von Feldschatten an den Spaltenkanten.
Hier ein Beispiel mit text-shadow:
Die Legacy-Engine verarbeitet dies nicht gut:
Sehen Sie, wie der Textschatten der Zeile in der ersten Spalte abgeschnitten und stattdessen oben in der zweiten Spalte platziert wird? Das liegt daran, dass die alte Layout-Engine Fragmentierung nicht erkennt.
Sie sollte so aussehen:
Machen wir es nun mit Transformationen und box-shadow ein wenig komplizierter. Beachten Sie, dass in der Legacy-Engine die Begrenzung und das Spaltenrandung fehlerhaft sind. Das liegt daran, dass Transformationen gemäß Spezifikation als Post-Layout- und Post-Fragmentierungseffekt angewendet werden sollen. Bei LayoutNG-Fragmentierung funktionieren beide ordnungsgemäß. Dadurch wird die Interoperabilität mit Firefox erhöht, das seit einiger Zeit gute Fragmentierung unterstützt und die meisten Tests in diesem Bereich auch dort bestanden werden.
Die Legacy-Engine hat auch Probleme mit hohen monolithischen Inhalten. Inhalte sind monolithisch, wenn sie nicht in mehrere Fragmente aufgeteilt werden können. Elemente mit Überlauf-Scrollen sind monolithisch, da es für Nutzer nicht sinnvoll ist, in einem nicht rechteckigen Bereich zu scrollen. Linienfelder und Bilder sind weitere Beispiele für monolithische Inhalte. Beispiel:
Wenn der monolithische Inhalt zu hoch ist, um in eine Spalte zu passen, wird er von der Legacy-Engine brutal segmentiert, was zu einem sehr "interessanten" Verhalten beim Scrollen des scrollbaren Containers führt:
Anstatt die erste Spalte überlaufen zu lassen (wie es bei der LayoutNG-Blockfragmentierung der Fall ist):
Die Legacy-Engine unterstützt erzwungene Unterbrechungen. Mit <div style="break-before:page;">
wird z. B. ein Seitenumbruch vor dem DIV-Element eingefügt. Die Methode zur Ermittlung optimaler nicht erzwungener Umbrüche wird jedoch nur eingeschränkt unterstützt. Es werden break-inside:avoid
sowie Verwaisen und Witwen unterstützt. Es gibt jedoch keine Möglichkeit, Unterbrechungen zwischen Blöcken zu vermeiden, z. B. über break-before:avoid
. Betrachten Sie dieses Beispiel:
Hier bietet das #multicol
-Element Platz für fünf Zeilen in jeder Spalte (da es 100 Pixel hoch ist und die Zeilenhöhe 20 Pixel beträgt), sodass alle #firstchild
-Elemente in die erste Spalte passen könnten. Auf der Ebene des verwandten #secondchild
-Objekts wird jedoch „break-before:avoid“ verwendet, d. h., der Inhalt wünscht, dass zwischen ihnen keine Pause entsteht. Da der Wert von widows
2 ist, müssen zwei Zeilen von #firstchild
in die zweite Spalte verschoben werden, um alle Anfragen zur Vermeidung von Unterbrechungen zu berücksichtigen. Chromium ist die erste Browser-Engine, die diese Kombination von Funktionen vollständig unterstützt.
Funktionsweise der NG-Fragmentierung
Die Layout-Engine von NG durchläuft das Dokument in der Regel zuerst tief in der CSS-Boxstruktur. Wenn alle Nachfolgerelemente eines Knotens angeordnet sind, kann das Layout dieses Knotens abgeschlossen werden, indem ein NGPhysicalFragment erzeugt wird und zum übergeordneten Layoutalgorithmus zurückkehrt. Dieser Algorithmus fügt das Fragment seiner Liste untergeordneter Fragmente hinzu und generiert, sobald alle untergeordneten Fragmente abgeschlossen sind, ein Fragment für sich selbst mit allen untergeordneten Fragmenten. Mit dieser Methode wird eine Fragmentstruktur für das gesamte Dokument erstellt. Dies stellt jedoch eine zu starke Vereinfachung dar: Zum Beispiel müssen Out-of-Flow-Positionierte Elemente von der Stelle im DOM-Baum bis zu ihrem enthaltenden Block aufsteigen, bevor sie angeordnet werden können. Der Einfachheit halber ignoriere ich diese erweiterten Details hier.
Zusammen mit dem CSS-Feld stellt LayoutNG einen Einschränkungsraum für einen Layout-Algorithmus bereit. Dadurch erhält der Algorithmus Informationen wie z. B. den verfügbaren Platz für das Layout, ob ein neuer Formatierungskontext erstellt wird, und die Ergebnisse zum Minimieren des Zwischenrands aus dem vorhergehenden Inhalt. Der Beschränkungsbereich kennt auch die angelegte Blockgröße des Fragmentierers und den aktuellen Blockversatz. Dies gibt an, wo die Unterbrechung erfolgen soll.
Wenn Blockfragmentierung eine Rolle spielt, muss das Layout der untergeordneten Elemente bei einer Pause anhalten. Gründe für den Absturz sind beispielsweise nicht genügend Speicherplatz auf der Seite oder Spalte oder ein erzwungener Umbruch. Anschließend erstellen wir Fragmente für die besuchten Knoten und kehren bis zum Stamm der Fragmentierungskontexte zurück (den multicol-Container oder, falls es gedruckt wird, zum Dokumentstamm). Am Stamm des Fragmentierungskontexts bereiten wir uns auf einen neuen Fragmentierer vor und steigen wieder in den Baum ab. Die Sitzung wird an der Stelle fortgesetzt, an der wir vor der Pause aufgehört haben.
Die entscheidende Datenstruktur zur Bereitstellung der Möglichkeit, das Layout nach einer Unterbrechung fortzusetzen, heißt NGBlockBreakToken. Sie enthält alle Informationen, die erforderlich sind, um das Layout im nächsten Fragmentierer wieder zu aktivieren. Ein NGBlockBreakToken ist einem Knoten zugeordnet und bildet einen NGBlockBreakToken-Baum, so dass jeder Knoten, der fortgesetzt werden muss, dargestellt wird. Ein NGBlockBreakToken wird an das NGPhysicalBoxFragment angehängt, das für Knoten generiert wurde, die eingreifen. Die Pausentokens werden an die übergeordneten Elemente weitergegeben, wodurch eine Baumstruktur von Pausentokens gebildet wird. Wenn vor einem Knoten (anstatt innerhalb dieses Knotens) aufgebrochen werden muss, wird kein Fragment erzeugt, aber der übergeordnete Knoten muss trotzdem ein "Break-before"-Break-Token für den Knoten erstellen, damit wir mit der Anordnung beginnen können, wenn wir im nächsten Fragmentierer an derselben Position im Knotenbaum angekommen sind.
Unterbrechungen werden eingefügt, wenn entweder der Fragmentierungsbereich aufgebraucht ist (eine nicht erzwungene Pause) oder eine erzwungene Pause angefordert wird.
Die Spezifikation enthält Regeln für optimale nicht erzwungene Pausen. Es ist nicht immer sinnvoll, eine Pause genau dort einzufügen, wo uns der Platz ausgeht. Beispielsweise gibt es verschiedene CSS-Eigenschaften wie break-before
, die die Auswahl der Position für die Unterbrechung beeinflussen.
Damit der Spezifikationsabschnitt für nicht erzwungene Unterbrechungen während des Layouts korrekt implementiert werden kann, müssen wir mögliche Haltepunkte im Auge behalten. Dieser Eintrag bedeutet, dass wir zurückgehen und den letzten gefundenen Haltepunkt verwenden können, wenn der Speicherplatz an einem Punkt zur Verfügung steht, an dem wir gegen Anfragen zur Pausenvermeidung verstoßen würden (z. B. break-before:avoid
oder orphans:7
). Jedem möglichen Haltepunkt wird eine Punktzahl zugewiesen. Diese reicht von "Machen Sie dies nur als letztes Mittel" bis "Perfekter Ort zum Umschalten", wobei einige Werte dazwischen liegen. Wenn die Platzierung einer Pause als „perfekt“ eingestuft wird, bedeutet dies, dass bei einem Verstoß gegen keine Regeln verstoßen wird (und wenn wir diesen Wert genau an dem Punkt erzielen, an dem uns der Speicherplatz ausgeht, gibt es keinen Grund, nach etwas Besserem zu suchen). Lautet der Wert "Letztes Resort", ist der Breakpoint gar kein gültiger Breakpoint. Falls wir nichts Besseres finden, funktioniert der Breakpoint aber dennoch, um einen Überlauf von Fragmenten zu vermeiden.
Gültige Haltepunkte treten in der Regel nur zwischen gleichgeordneten Elementen (Zeilenfeldern oder Blöcken) auf, nicht z. B. zwischen einem übergeordneten und seinem ersten untergeordneten Element. Haltepunkte der Klasse C sind eine Ausnahme, auf diese werden wir hier jedoch nicht eingehen. Es gibt einen gültigen Haltepunkt, z. B. vor einem gleichgeordneten Block mit „break-before:avoid“, aber er befindet sich irgendwo zwischen „perfect“ und „last-resort“.
Während des Layouts verfolgen wir den besten Haltepunkt, den wir bisher in einer Struktur namens NGEarlyBreak gefunden haben. Ein früher Umbruch ist ein möglicher Haltepunkt vor oder innerhalb eines Blockknotens oder vor einer Zeile (entweder einer Blockcontainer- oder einer Flex-Zeile). Wir können eine Kette oder einen Pfad von NGEarlyBreak-Objekten bilden, für den Fall, dass sich der beste Haltepunkt tief im Inneren eines Objekts befindet, an dem wir schon einmal vorbeigegangen sind, als uns der Platz ausging. Beispiel:
In diesem Fall ist der Speicherplatz kurz vor #second
vollständig belegt, aber mit „break-before:avoid“ wird „break-before:avoid“ verwendet, wodurch die Position der Unterbrechung „Verstoß gegen die Werbeunterbrechung verhindert“ lautet. An dieser Stelle haben wir eine NGEarlyBreak-Kette von „in #outer
> in #middle
> innerhalb #inner
> vor „line 3“, mit „perfect“, also würden wir hier eher aufbrechen. Also müssen wir das Layout am Anfang von #outer zurückgeben (und diesmal die NGEarlyBreak übergeben, die wir gefunden haben), damit wir vor "Zeile 3" in #inner einen Zeilenumbruch einfügen können. (Wir unterbrechen vor Zeile 3, sodass die verbleibenden vier Zeilen im nächsten Fragmentierer landen, und um widows:4
zu berücksichtigen.)
Der Algorithmus ist so konzipiert, dass die Funktion immer beim bestmöglichen Haltepunkt – wie in der Spezifikation definiert – funktioniert, indem Regeln in die richtige Reihenfolge gesetzt werden, wenn nicht alle Regeln erfüllt werden können. Das Layout muss maximal einmal pro Fragmentierungsablauf angepasst werden. Als wir im zweiten Layout-Pass sind, wurde die beste Unterbrechungsposition bereits an die Layout-Algorithmen übergeben. Dies ist die Stelle der Unterbrechung, die im ersten Layout-Pass gefunden und in dieser Runde als Teil der Layoutausgabe bereitgestellt wurde. Beim zweiten Layout-Pass geht es erst dann los, wenn der Platz knapp wird. Es ist sogar nicht davon auszugehen, dass uns der Platz ausgeht (das wäre ein Fehler), weil uns ein supersüßer Ort zur Verfügung gestellt wurde, um eine frühe Pause einzulegen, um unnötige Verstöße gegen Regeln zu vermeiden. Also legen wir uns auf diesen Punkt und machen eine Pause.
In diesem Fall müssen wir mitunter gegen einige Anforderungen zur Vermeidung von Unterbrechungen verstoßen, wenn dadurch ein Überlauf von Fragmentierungsmöglichkeiten verhindert wird. Beispiel:
Hier ist der Speicherplatz kurz vor #second
knapp, aber "break-before:avoid" wurde verwendet. In diesem Beispiel wurde dies wie im letzten Beispiel mit „Verstoß gegen die Pausen verhindern“ übersetzt. Wir haben auch eine NGEarlyBreak mit „verstoßende Waisen und Witwen“ (in #first
> vor „Zeile 2“), die zwar nicht perfekt ist, aber besser als „Verstoß gegen die Werbeunterbrechung vermeiden“. Wir unterbrechen also vor Zeile 2 und verstoßen gegen den Antrag für Waisen / Witwen. In der Spezifikation wird dies in 4.4. Unerzwungene Unterbrechungen: Hier wird definiert, welche Regeln zuerst ignoriert werden, wenn nicht genügend Haltepunkte vorhanden sind, um einen Überlauf durch Fragmentierung zu vermeiden.
Fazit
Das funktionale Ziel des Projekts zur Blockfragmentierung von LayoutNG bestand darin, eine LayoutNG-Architekturunterstützende Implementierung von allem, was von der Legacy-Engine unterstützt wird, und so wenig wie möglich bereitzustellen, abgesehen von Fehlerkorrekturen. Die wichtigste Ausnahme ist die bessere Unterstützung zur Vermeidung von Unterbrechungen (z. B. break-before:avoid
), da dies ein zentraler Bestandteil der Fragmentierungs-Engine ist und sie von Anfang an darin enthalten sein musste, da ein späteres Hinzufügen eine weitere Umformulierung bedeuten würde.
Da die Blockfragmentierung von LayoutNG nun abgeschlossen ist, können wir neue Funktionen hinzufügen, zum Beispiel die Unterstützung unterschiedlicher Seitengrößen beim Drucken, @page
-Randfelder beim Drucken, box-decoration-break:clone
und vieles mehr. Und wie bei LayoutNG gehen wir davon aus, dass die Fehlerrate und der Wartungsaufwand für das neue System im Laufe der Zeit deutlich geringer sein werden.
Danksagungen
- Una Kravets für den schönen "handgefertigten Screenshot".
- Chris Harrelson für Korrekturlesen, Feedback und Vorschläge.
- Philip Jägenstedt für Feedback und Vorschläge.
- Rachel Andrew zur Bearbeitung und das erste mehrspaltige Beispielbild.