MEP14: Textverarbeitung #

Status #

  • Diskussion

Branches und Pull-Requests #

Ausgabe Nr. 253 zeigt einen Fehler, bei dem die Verwendung des Begrenzungsrahmens anstelle der erweiterten Textbreite zu falsch ausgerichtetem Text führt. Dies ist ein kleiner Punkt im Großen und Ganzen, aber er sollte als Teil dieses MdEP angesprochen werden.

Zusammenfassung #

Durch die Neuordnung des Umgangs mit Texten will dieser Abgeordnete:

  • Verbesserung der Unterstützung für Unicode und Nicht-ltr-Sprachen

  • Textlayout verbessern (insbesondere mehrzeiliger Text)

  • Ermöglichen Sie die Unterstützung weiterer Schriftarten, insbesondere TrueType-Schriftarten im Nicht-Apple-Format und OpenType-Schriftarten.

  • machen die Schriftkonfiguration einfacher und transparenter

Detaillierte Beschreibung #

Textlayout

Derzeit hat matplotlib zwei verschiedene Möglichkeiten, Text zu rendern: "eingebaut" (basierend auf FreeType und unserem eigenen Python-Code) und "usetex" (basierend auf dem Aufrufen einer TeX-Installation). Neben dem "eingebauten" Renderer gibt es auch das Python-basierte "mathtext"-System zum Rendern mathematischer Gleichungen mit einer Teilmenge der TeX-Sprache, ohne dass eine TeX-Installation verfügbar ist. Die Unterstützung für diese beiden Engines ist über viele Quelldateien verstreut, einschließlich aller Backends, wo man Klauseln wie findet

if rcParams['text.usetex']: # do one thing else: # do another

Das Hinzufügen eines dritten Textrendering-Ansatzes (dazu später mehr) würde auch die Bearbeitung all dieser Stellen erfordern und daher nicht skalieren.

Stattdessen schlägt dieser Europaabgeordnete vor, ein Konzept von „Textmaschinen“ hinzuzufügen, bei denen der Benutzer einen von vielen verschiedenen Ansätzen zum Rendern von Text auswählen könnte. Die Implementierungen von jedem davon würden in ihren eigenen Satz von Modulen lokalisiert und hätten keine kleinen Teile um den gesamten Quellbaum herum.

Warum weitere Textwiedergabe-Engines hinzufügen? Die "eingebaute" Textwiedergabe hat eine Reihe von Mängeln.

  • Es verarbeitet nur Sprachen, die von rechts nach links geschrieben werden, und nicht viele Sonderfunktionen von Unicode, wie z. B. das Kombinieren von diakritischen Zeichen.

  • Die mehrzeilige Unterstützung ist unvollkommen und unterstützt nur manuelle Zeilenumbrüche – sie kann einen Absatz nicht in Zeilen einer bestimmten Länge aufteilen.

  • Es verarbeitet auch keine Inline-Formatierungsänderungen, um so etwas wie Markdown, reStructuredText oder HTML zu unterstützen. (Obwohl die Rich-Text-Formatierung in diesem MEP in Betracht gezogen wird, da wir sicherstellen möchten, dass dieses Design dies zulässt, liegen die Einzelheiten einer Implementierung der Rich-Text-Formatierung außerhalb des Rahmens dieses MEP.)

Diese Dinge zu unterstützen ist schwierig und der "Vollzeitjob" einer Reihe anderer Projekte:

Zu den oben genannten Optionen ist anzumerken, dass Harfbuzz von Anfang an als plattformübergreifende Option mit minimalen Abhängigkeiten konzipiert ist und daher ein guter Kandidat für die Unterstützung einer einzelnen Option ist.

Darüber hinaus könnten wir zur Unterstützung von Rich Text die Verwendung von WebKit in Betracht ziehen und möglicherweise, ob dies eine gute einzelne plattformübergreifende Option darstellt. Aber auch hier liegt die Rich-Text-Formatierung außerhalb des Rahmens dieses Projekts.

Anstatt zu versuchen, das Rad neu zu erfinden und diese Funktionen zum „eingebauten“ Textrenderer von matplotlib hinzuzufügen, sollten wir eine Möglichkeit bieten, diese Projekte zu nutzen, um ein leistungsfähigeres Textlayout zu erhalten. Der "eingebaute" Renderer muss aus Gründen der einfachen Installation weiterhin vorhanden sein, aber sein Funktionsumfang wird im Vergleich zu den anderen eingeschränkter sein. [TODO: Dieser MdEP sollte klar entscheiden, was diese eingeschränkten Funktionen sind, und alle Fehler beheben, um die Implementierung in einen Zustand zu bringen, in dem sie in allen Fällen, in denen wir möchten, dass sie funktioniert, korrekt funktioniert. Ich weiß, dass @leejjoon einige Gedanken dazu hat.]

Schriftartauswahl

Von einer abstrakten Beschreibung einer Schriftart zu einer Datei auf der Festplatte zu gelangen, ist die Aufgabe des Schriftartauswahlalgorithmus -- es stellt sich als viel komplizierter heraus, als es zunächst scheint.

Die "eingebauten" und "usetex"-Renderer haben aufgrund ihrer unterschiedlichen Technologien sehr unterschiedliche Möglichkeiten, die Schriftauswahl zu handhaben. TeX erfordert beispielsweise die Installation von TeX-spezifischen Schriftpaketen und kann TrueType-Schriftarten nicht direkt verwenden. Unglücklicherweise wird trotz der unterschiedlichen Semantik für die Schriftauswahl für jede die gleiche Gruppe von Schrifteigenschaften verwendet. Dies gilt sowohl für die FontPropertiesKlasse als auch für die Schriftart rcParams(die im Grunde den gleichen Code darunter teilen). Stattdessen sollten wir einen Kernsatz von Schriftartauswahlparametern definieren, die für alle Textmaschinen funktionieren, und eine maschinenspezifische Konfiguration haben, damit der Benutzer bei Bedarf maschinenspezifische Dinge tun kann. Zum Beispiel ist es möglich, eine Schriftart direkt nach Namen in der "integrierten" Verwendung auszuwählen rcParams["font.family"](Standard:['sans-serif']), aber dasselbe ist mit "usetex" nicht möglich. Es kann möglich sein, die Verwendung von TrueType-Schriftarten durch die Verwendung von XeTeX zu vereinfachen, aber Benutzer werden immer noch die traditionellen Metaschriften über TeX-Schriftartenpakete verwenden wollen. Das Problem besteht also immer noch darin, dass verschiedene Textmaschinen eine maschinenspezifische Konfiguration benötigen, und es sollte für den Benutzer offensichtlicher sein, welche Konfiguration für alle Textmaschinen funktioniert und welche maschinenspezifisch sind.

Beachten Sie, dass es auch ohne "Usetex" verschiedene Möglichkeiten gibt, Schriftarten zu finden. Standardmäßig wird der Schriftartenlisten-Cache verwendet, in font_manager dem Schriftarten mithilfe unseres eigenen Algorithmus auf der Grundlage des CSS-Schriftartvergleichsalgorithmus abgeglichen werden . Es macht nicht immer dasselbe wie die nativen Schriftartauswahlalgorithmen unter Linux ( fontconfig), Mac und Windows, und es findet nicht immer alle Schriftarten auf dem System, die das Betriebssystem normalerweise aufnehmen würde. Es ist jedoch plattformübergreifend und findet immer die Schriftarten, die mit Matplotlib geliefert werden. Die Backends von Cairo und MacOSX (und vermutlich ein zukünftiges HTML5-basiertes Backend) umgehen derzeit diesen Mechanismus und verwenden die nativen des Betriebssystems. Gleiches gilt, wenn Sie keine Schriftarten in SVG-, PS- oder PDF-Dateien einbetten und diese in einem Drittanbieter-Viewer öffnen. Ein Nachteil ist, dass sie (zumindest bei Cairo, muss bei MacOSX bestätigt werden) nicht immer die Schriftarten finden, die wir mit matplotlib liefern. (Möglicherweise ist es jedoch möglich, die Schriftarten zu ihrem Suchpfad hinzuzufügen, oder wir müssen möglicherweise einen Weg finden, unsere Schriftarten an einem Ort zu installieren, an dem das Betriebssystem sie erwartet).

Es gibt auch spezielle Modi in PS und PDF, um nur die Kernschriften zu verwenden, die diesen Formaten immer zur Verfügung stehen. Dort muss der Schriftartsuchmechanismus nur mit diesen Schriftarten übereinstimmen. Es ist unklar, ob die betriebssystemeigenen Font-Lookup-Systeme diesen Fall verarbeiten können.

Es gibt auch experimentelle Unterstützung für die Verwendung von fontconfig für die Schriftartauswahl in Matplotlib, die standardmäßig deaktiviert ist. fontconfig ist der native Schriftartauswahlalgorithmus unter Linux, ist aber auch plattformübergreifend und funktioniert gut auf den anderen Plattformen (obwohl es dort offensichtlich eine zusätzliche Abhängigkeit gibt).

Viele der oben vorgeschlagenen Textlayout-Bibliotheken (pango, QtTextLayout, DirectWrite und CoreText usw.) bestehen darauf, die Schriftartauswahlbibliothek aus ihrem eigenen Ökosystem zu verwenden.

All dies scheint darauf hinzudeuten, dass wir uns von unserem selbstgeschriebenen Algorithmus zur Schriftauswahl entfernen und nach Möglichkeit die nativen APIs verwenden sollten. Das ist es, was die Backends von Cairo und MacOSX bereits verwenden wollen, und es wird eine Voraussetzung für jede komplexe Textlayout-Bibliothek sein. Unter Linux haben wir bereits die Knochen einer Fontconfig- Implementierung (auf die auch über Pango zugegriffen werden könnte). Unter Windows und Mac müssen wir möglicherweise benutzerdefinierte Wrapper schreiben. Das Schöne daran ist, dass die API für die Schriftsuche relativ klein ist und im Wesentlichen aus "Wenn Sie ein Wörterbuch mit Schrifteigenschaften haben, geben Sie mir eine passende Schriftdatei" besteht.

Font-Untereinstellung

Font-Subsetting wird derzeit mit ttconv gehandhabt. ttconv war ein eigenständiges Befehlszeilendienstprogramm zum Konvertieren von TrueType-Schriftarten in untergeordnete Typ-3-Schriftarten (neben anderen Funktionen), das 1995 geschrieben wurde und von matplotlib (na ja, ich) gegabelt wurde, damit es als Bibliothek funktioniert. Es verarbeitet nur TrueType-Schriftarten im Apple-Stil, keine mit den Kodierungen von Microsoft (oder anderen Anbietern). Es verarbeitet überhaupt keine OpenType-Schriftarten. Das bedeutet, dass wir die STIX-Schriftarten, obwohl sie als .otf-Dateien vorliegen, in .ttf-Dateien konvertieren müssen, um sie mit matplotlib zu versenden. Die Linux-Packager hassen das – sie verlassen sich lieber nur auf die Upstream-STIX-Fonts. Es hat sich auch gezeigt, dass ttconv einige Fehler aufweist, die im Laufe der Zeit schwer zu beheben waren.

Stattdessen sollten wir in der Lage sein, FreeType zu verwenden, um die Schriftumrisse zu erhalten, und unseren eigenen Code (wahrscheinlich in Python) schreiben können, um Untergruppen von Schriften auszugeben (Typ 3 auf PS und PDF und Pfade auf SVG). Freetype, als beliebtes und gut gepflegtes Projekt, verarbeitet eine Vielzahl von Schriftarten in freier Wildbahn. Dies würde viel benutzerdefinierten C-Code entfernen und einige Codeduplizierungen zwischen Backends beseitigen.

Beachten Sie, dass das Unterteilen von Schriftarten auf diese Weise zwar der einfachste Weg ist, jedoch die Hinweise in der Schriftart verlieren. Daher müssen wir wie jetzt fortfahren und eine Möglichkeit bieten, die gesamte Schriftart nach Möglichkeit in die Datei einzubetten.

Alternative Font-Subsetting-Optionen umfassen die Verwendung der in Cairo integrierten Subsettings (nicht klar, ob sie ohne den Rest von Cairo verwendet werden können) oder die Verwendung von Fontforge (was eine starke und nicht allzu plattformübergreifende Abhängigkeit darstellt).

Freetype-Wrapper

Unser FreeType-Wrapper könnte wirklich eine Überarbeitung gebrauchen. Es definiert seine eigene Bildpufferklasse (wenn ein Numpy-Array einfacher wäre). Obwohl FreeType eine große Vielfalt von Schriftartdateien verarbeiten kann, gibt es Einschränkungen für unseren Wrapper, die es viel schwieriger machen, TrueType-Dateien von Drittanbietern und bestimmte Funktionen von OpenType-Dateien zu unterstützen. (Siehe #2088 für ein schreckliches Ergebnis davon, nur um die Schriftarten zu unterstützen, die mit Windows 7 und 8 geliefert werden). Ich denke, eine Neufassung dieses Wrappers würde einen langen Weg zurücklegen.

Textverankerung und -ausrichtung und -drehung

Die Behandlung von Grundlinien wurde in 1.3.0 so geändert, dass die Backends jetzt die Position der Grundlinie des Textes erhalten, nicht das Ende des Textes. Dies ist wahrscheinlich das richtige Verhalten, und auch das MEP-Refactoring sollte dieser Konvention folgen.

Um die Ausrichtung bei mehrzeiligem Text zu unterstützen, sollte es in der Verantwortung der (vorgeschlagenen) Text-Engine liegen, die Textausrichtung zu handhaben. Für einen bestimmten Textabschnitt berechnet jede Engine einen Begrenzungsrahmen für diesen Text und den Versatz des Ankerpunkts innerhalb dieses Rahmens. Wenn also das va eines Blocks "oben" wäre, wäre der Ankerpunkt oben in der Box.

Das Rotieren von Text sollte immer um den Ankerpunkt erfolgen. Ich bin mir nicht sicher, ob das mit dem aktuellen Verhalten in Matplotlib übereinstimmt, aber es scheint die vernünftigste/am wenigsten überraschende Wahl zu sein. [Dies könnte erneut aufgegriffen werden, sobald wir etwas haben, das funktioniert]. Die Drehung von Text sollte nicht von der Text-Engine gehandhabt werden – dies sollte von einer Ebene zwischen der Text-Engine und dem Rendering-Backend gehandhabt werden, damit es auf einheitliche Weise gehandhabt werden kann. [Ich sehe keinen Vorteil darin, dass die Rotation von den Text-Engines einzeln gehandhabt wird ...]

Es gibt noch weitere Probleme bei der Textausrichtung und -verankerung, die im Rahmen dieser Arbeit gelöst werden sollten. [TODO: diese aufzählen].

Andere kleinere Probleme zu beheben

Der mathtext-Code hat Backend-spezifischen Code – er sollte seine Ausgabe stattdessen einfach als eine weitere Text-Engine bereitstellen. Es ist jedoch immer noch wünschenswert, das Mathtext-Layout als Teil eines größeren Layouts einzufügen, das von einer anderen Text-Engine ausgeführt wird, daher sollte dies möglich sein. Ob das Einbetten des Textlayouts einer beliebigen Text-Engine in eine andere möglich sein soll, ist offen.

Der Textmodus wird derzeit von einem globalen rcParam ("text.usetex") gesetzt, also ist entweder alles an oder alles aus. Wir sollten weiterhin einen globalen rcParam haben, um die Text-Engine ("text.layout_engine") auszuwählen, aber unter der Haube sollte es eine überschreibbare Eigenschaft des TextObjekts sein, sodass dieselbe Figur bei Bedarf die Ergebnisse mehrerer Text-Layout-Engines kombinieren kann .

Implementierung #

Ein Konzept einer "Textmaschine" wird eingeführt. Jede Textmaschine implementiert eine Reihe von abstrakten Klassen. Die TextFontSchnittstelle stellt Text für einen gegebenen Satz von Schriftarteigenschaften dar. Es ist nicht notwendigerweise auf eine einzige Schriftdatei beschränkt – wenn die Layout-Engine Rich Text unterstützt, kann sie eine Reihe von Schriftdateien in einer Familie handhaben. Wenn eine TextFontInstanz gegeben ist, kann der Benutzer eine TextLayoutInstanz erhalten, die das Layout für eine bestimmte Textzeichenfolge in einer bestimmten Schriftart darstellt. Von a TextLayoutwird ein Iterator über TextSpans zurückgegeben, damit die Engine unbearbeiteten bearbeitbaren Text mit so wenigen Spannen wie möglich ausgeben kann. Möchte die Engine lieber einzelne Charaktere erhalten, können diese aus der TextSpanInstanz bezogen werden:

class TextFont(TextFontBase):
    def __init__(self, font_properties):
        """
        Create a new object for rendering text using the given font properties.
        """
        pass

    def get_layout(self, s, ha, va):
        """
        Get the TextLayout for the given string in the given font and
        the horizontal (left, center, right) and verticalalignment (top,
        center, baseline, bottom)
        """
        pass

class TextLayout(TextLayoutBase):
    def get_metrics(self):
        """
        Return the bounding box of the layout, anchored at (0, 0).
        """
        pass

    def get_spans(self):
        """
        Returns an iterator over the spans of different in the layout.
        This is useful for backends that want to editable raw text as
        individual lines.  For rich text where the font may change,
        each span of different font type will have its own span.
        """
        pass

    def get_image(self):
        """
        Returns a rasterized image of the text.  Useful for raster backends,
        like Agg.

        In all likelihood, this will be overridden in the backend, as it can
        be created from get_layout(), but certain backends may want to
        override it if their library provides it (as freetype does).
        """
        pass

    def get_rectangles(self):
        """
        Returns an iterator over the filled black rectangles in the layout.
        Used by TeX and mathtext for drawing, for example, fraction lines.
        """
        pass

    def get_path(self):
        """
        Returns a single Path object of the entire laid out text.

        [Not strictly necessary, but might be useful for textpath
        functionality]
        """
        pass

class TextSpan(TextSpanBase):
    x, y      # Position of the span -- relative to the text layout as a whole
              # where (0, 0) is the anchor.  y is the baseline of the span.
    fontfile  # The font file to use for the span
    text      # The text content of the span

    def get_path(self):
        pass  # See TextLayout.get_path

    def get_chars(self):
        """
        Returns an iterator over the characters in the span.
        """
        pass

class TextChar(TextCharBase):
    x, y      # Position of the character -- relative to the text layout as
              # a whole, where (0, 0) is the anchor.  y is in the baseline
              # of the character.
    codepoint # The unicode code point of the character -- only for informational
              # purposes, since the mapping of codepoint to glyph_id may have been
              # handled in a complex way by the layout engine.  This is an int
              # to avoid problems on narrow Unicode builds.
    glyph_id  # The index of the glyph within the font
    fontfile  # The font file to use for the char

    def get_path(self):
        """
        Get the path for the character.
        """
pass

Grafik-Backends, die eine Teilmenge von Schriftarten ausgeben möchten, würden wahrscheinlich ein dateiglobales Wörterbuch von Zeichen aufbauen, in dem die Schlüssel (fontname, glyph_id) und die Werte die Pfade sind, sodass nur eine Kopie des Pfads für jedes Zeichen gespeichert wird die Datei.

Spezielle Groß- und Kleinschreibung: Die "usetex"-Funktionalität ist derzeit in der Lage, Postscript direkt von TeX zu erhalten, um es direkt in eine Postscript-Datei einzufügen, aber für andere Backends analysiert es eine DVI-Datei und generiert etwas Abstrakteres. In einem solchen Fall TextLayoutwürde get_spansfür die meisten Backends implementiert, aber get_psfür das Postscript-Backend hinzugefügt, das nach dem Vorhandensein dieser Methode suchen und sie verwenden würde, falls verfügbar, oder auf get_spans. Ein solches spezielles Gehäuse kann beispielsweise auch dann notwendig sein, wenn Grafik-Backend und Text-Engine zum selben Ökosystem gehören, zB Cairo und Pango oder MacOSX und CoreText.

Die Implementierung besteht aus drei Hauptelementen:

  1. Umschreiben des Freetype-Wrappers und Entfernen von ttconv.

  1. Sobald (1) fertig ist, können wir als Machbarkeitsnachweis zu den vorgelagerten STIX .otf-Schriftarten wechseln

  2. Fügen Sie Unterstützung für Webfonts hinzu, die von einer Remote-URL geladen werden. (Aktiviert durch die Verwendung von Freetype für die Schriftartuntereinstellung).

  1. Refactoring des vorhandenen „Builtin“- und „Usetex“-Codes in separate Text-Engines und Befolgen der oben beschriebenen API.

  2. Implementieren der Unterstützung für erweiterte Textlayoutbibliotheken.

(1) und (2) sind ziemlich unabhängig, obwohl (2) einfacher wird, wenn (1) zuerst ausgeführt wird. (3) hängt von (1) und (2) ab, aber selbst wenn es nicht fertig wird (oder verschoben wird), macht es das Vervollständigen von (1) und (2) einfacher, mit der Verbesserung des "eingebauten" fortzufahren Text-Engine.

Abwärtskompatibilität #

Das Layout des Textes in Bezug auf seinen Anker und seine Drehung wird sich auf hoffentlich kleine, aber verbesserte Weise ändern. Das Layout von mehrzeiligem Text wird viel besser, da die horizontale Ausrichtung berücksichtigt wird. Das Layout von bidirektionalem Text oder anderen erweiterten Unicode-Funktionen funktioniert jetzt von Natur aus, was einige Dinge beschädigen kann, wenn Benutzer derzeit ihre eigenen Problemumgehungen verwenden.

Schriftarten werden unterschiedlich ausgewählt. Hacks, die früher zwischen den Textwiedergabe-Engines "BuiltIn" und "UseTex" funktionierten, funktionieren möglicherweise nicht mehr. Vom Betriebssystem gefundene Schriftarten, die zuvor nicht von matplotlib gefunden wurden, können ausgewählt werden.

Alternativen #

offen