MEP14: 텍스트 처리 #
상태 번호
논의
브랜치 및 풀 리퀘스트 #
Issue #253은 텍스트의 고급 너비 대신 경계 상자를 사용하면 텍스트가 잘못 정렬되는 버그를 보여줍니다. 이것은 큰 계획에서 사소한 점이지만 이 MEP의 일부로 다루어져야 합니다.
초록 #
텍스트 처리 방식을 재구성하여 이 MEP는 다음을 목표로 합니다.
유니코드 및 non-ltr 언어에 대한 지원 개선
텍스트 레이아웃 개선(특히 여러 줄 텍스트)
더 많은 글꼴, 특히 Apple 형식이 아닌 TrueType 글꼴 및 OpenType 글꼴을 지원할 수 있습니다.
글꼴 구성을 더 쉽고 투명하게 만듭니다.
자세한 설명 #
텍스트 레이아웃
현재 matplotlib에는 텍스트를 렌더링하는 두 가지 방법이 있습니다. "내장"(FreeType 및 자체 Python 코드 기반) 및 "usetex"(TeX 설치 호출 기반)입니다. "내장" 렌더러 외에 TeX 설치 없이 TeX 언어의 하위 집합을 사용하여 수학 방정식을 렌더링하기 위한 Python 기반 "mathtext" 시스템도 있습니다. 다음과 같은 절이 있는 모든 백엔드를 포함하여 많은 소스 파일에 흩어져 있는 이 두 엔진에 대한 지원
if rcParams['text.usetex']: # do one thing else: # do another
세 번째 텍스트 렌더링 접근 방식(나중에 자세히 설명)을 추가하려면 이러한 위치도 모두 편집해야 하므로 크기가 조정되지 않습니다.
대신, 이 MEP는 사용자가 텍스트 렌더링을 위한 다양한 접근 방식 중 하나를 선택할 수 있는 "텍스트 엔진" 개념을 추가할 것을 제안합니다. 이들 각각의 구현은 자체 모듈 집합으로 지역화되며 전체 소스 트리 주위에 작은 조각이 없습니다.
더 많은 텍스트 렌더링 엔진을 추가하는 이유는 무엇입니까? "내장" 텍스트 렌더링에는 몇 가지 단점이 있습니다.
오른쪽에서 왼쪽으로 쓰는 언어만 처리하고 분음 부호 결합과 같은 유니코드의 많은 특수 기능은 처리하지 않습니다.
여러 줄 지원은 불완전하며 수동 줄 바꿈만 지원합니다. 단락을 특정 길이의 줄로 나눌 수 없습니다.
또한 Markdown, reStructuredText 또는 HTML과 같은 것을 지원하기 위해 인라인 서식 변경을 처리하지 않습니다. (이 MEP에서는 서식 있는 텍스트 서식이 고려되지만 이 설계에서 허용하는지 확인하고 싶기 때문에 서식 있는 텍스트 서식 구현의 세부 사항은 이 MEP 범위를 벗어납니다.)
이러한 것들을 지원하는 것은 어렵고 다른 여러 프로젝트의 "정규직"입니다.
위의 옵션 중에서 harfbuzz 는 처음부터 최소한의 종속성이 있는 크로스 플랫폼 옵션으로 설계되었으므로 단일 옵션을 지원하기에 좋은 후보라는 점에 유의해야 합니다.
또한 서식 있는 텍스트를 지원하기 위해 WebKit 사용을 고려할 수 있으며 단일 플랫폼 간 옵션이 좋은지 여부도 고려할 수 있습니다. 그러나 서식 있는 텍스트 서식은 이 프로젝트의 범위를 벗어납니다.
바퀴를 재발명하고 이러한 기능을 matplotlib의 "내장" 텍스트 렌더러에 추가하는 대신 이러한 프로젝트를 활용하여 더 강력한 텍스트 레이아웃을 얻을 수 있는 방법을 제공해야 합니다. "내장" 렌더러는 설치가 쉽기 때문에 여전히 존재해야 하지만 기능 세트는 다른 렌더러에 비해 더 제한적입니다. [TODO: 이 MEP는 제한된 기능이 무엇인지 명확하게 결정하고 모든 버그를 수정하여 구현이 작동하기를 원하는 모든 경우에 올바르게 작동하는 상태로 가져와야 합니다. @leejjoon님이 이것에 대해 생각이 있는 걸로 압니다.]
글꼴 선택
글꼴에 대한 추상적인 설명에서 디스크의 파일로 이동하는 것은 글꼴 선택 알고리즘의 작업입니다. 처음에 생각했던 것보다 훨씬 더 복잡한 것으로 밝혀졌습니다.
"내장" 렌더러와 "usetex" 렌더러는 서로 다른 기술로 인해 글꼴 선택을 처리하는 방식이 매우 다릅니다. TeX는 예를 들어 TeX 전용 글꼴 패키지를 설치해야 하며 TrueType 글꼴을 직접 사용할 수 없습니다. 불행하게도 글꼴 선택에 대한 다른 의미 체계에도 불구하고 동일한 글꼴 속성 집합이 각각에 사용됩니다. 이는
FontProperties
클래스와 글꼴 관련 rcParams
(기본적으로 동일한 코드를 공유함) 모두에 해당됩니다. 대신, 우리는 모든 텍스트 엔진에서 작동할 글꼴 선택 매개변수의 핵심 집합을 정의하고 사용자가 필요할 때 엔진별 작업을 수행할 수 있도록 엔진별 구성을 가져야 합니다. 예를 들어
rcParams["font.family"]
(기본값:['sans-serif']
), 그러나 "usetex"에서는 동일하지 않습니다. XeTeX를 사용하여 TrueType 글꼴을 더 쉽게 사용할 수 있지만 사용자는 여전히 TeX 글꼴 패키지를 통해 기존 메타 글꼴을 사용하기를 원할 것입니다. 따라서 서로 다른 텍스트 엔진에는 엔진별 구성이 필요하며 어떤 구성이 텍스트 엔진에서 작동하고 어떤 구성이 엔진별인지 사용자에게 더 명확해야 한다는 문제가 여전히 존재합니다.
"usetex"를 제외하고도 다양한 방법으로 글꼴을 찾을 수 있습니다. 기본값은 CSS 글꼴 일치 알고리즘font_manager
을 기반으로 자체 알고리즘을 사용하여 글꼴을 일치 시키는 글꼴 목록 캐시를 사용하는 것 입니다. Linux의 기본 글꼴 선택 알고리즘과 항상 동일한 작업을 수행하지는 않습니다( fontconfig), Mac 및 Windows, 그리고 OS가 일반적으로 선택하는 시스템의 모든 글꼴을 항상 찾지는 않습니다. 그러나 크로스 플랫폼이며 항상 matplotlib와 함께 제공되는 글꼴을 찾습니다. Cairo 및 MacOSX 백엔드(그리고 아마도 미래의 HTML5 기반 백엔드)는 현재 이 메커니즘을 우회하고 OS 네이티브 메커니즘을 사용합니다. SVG, PS 또는 PDF 파일에 글꼴을 포함하지 않고 타사 뷰어에서 열 때도 마찬가지입니다. 단점은 (적어도 Cairo에서는 MacOSX에서 확인해야 함) 우리가 matplotlib와 함께 제공하는 글꼴을 항상 찾지 못한다는 것입니다. (하지만 검색 경로에 글꼴을 추가하는 것이 가능하거나 OS에서 찾을 것으로 예상하는 위치에 글꼴을 설치하는 방법을 찾아야 할 수도 있습니다.)
PS 및 PDF에는 해당 형식에 항상 사용 가능한 핵심 글꼴만 사용하는 특수 모드도 있습니다. 여기서 글꼴 조회 메커니즘은 해당 글꼴과만 일치해야 합니다. OS 고유의 글꼴 조회 시스템이 이 경우를 처리할 수 있는지 여부는 불분명합니다.
기본적으로 꺼져 있는 matplotlib의 글꼴 선택에 fontconfig 를 사용하는 실험적 지원도 있습니다 . fontconfig는 Linux의 기본 글꼴 선택 알고리즘이지만 교차 플랫폼이기도 하며 다른 플랫폼에서도 잘 작동합니다(확실히 추가 종속성이 있음).
위에서 제안한 많은 텍스트 레이아웃 라이브러리(pango, QtTextLayout, DirectWrite 및 CoreText 등)는 자체 생태계의 글꼴 선택 라이브러리를 사용해야 한다고 주장합니다.
위의 모든 내용은 자체 작성 글꼴 선택 알고리즘에서 벗어나 가능한 경우 기본 API를 사용해야 함을 시사하는 것 같습니다. 이것이 Cairo와 MacOSX 백엔드가 이미 사용하고자 하는 것이며 복잡한 텍스트 레이아웃 라이브러리의 요구 사항이 될 것입니다. Linux에는 이미 fontconfig 구현의 뼈대가 있습니다(pango를 통해 액세스할 수도 있음). Windows 및 Mac에서는 사용자 지정 래퍼를 작성해야 할 수 있습니다. 좋은 점은 글꼴 조회를 위한 API가 상대적으로 작고 기본적으로 "글꼴 속성의 사전을 제공하면 일치하는 글꼴 파일을 제공합니다"로 구성된다는 것입니다.
글꼴 하위 설정
글꼴 하위 설정은 현재 ttconv를 사용하여 처리됩니다. ttconv는 1995년에 작성된 TrueType 글꼴을 부분 집합화된 Type 3 글꼴(다른 기능 중)으로 변환하기 위한 독립 실행형 명령줄 유틸리티였습니다. Microsoft(또는 다른 공급업체) 인코딩이 아닌 Apple 스타일 TrueType 글꼴만 처리합니다. OpenType 글꼴을 전혀 처리하지 않습니다. 즉, STIX 글꼴이 .otf 파일로 제공되더라도 matplotlib와 함께 제공하려면 .ttf 파일로 변환해야 합니다. Linux 패키저는 이것을 싫어합니다. 업스트림 STIX 글꼴에 의존하는 것이 낫습니다. ttconv에는 시간이 지남에 따라 수정하기 어려운 몇 가지 버그가 있는 것으로 나타났습니다.
대신 FreeType을 사용하여 글꼴 윤곽선을 가져오고 하위 집합 글꼴(PS 및 PDF의 Type 3 및 SVG의 경로)을 출력하기 위해 자체 코드(Python에서)를 작성할 수 있어야 합니다. 인기 있고 잘 유지 관리되는 프로젝트인 Freetype은 야생에서 다양한 글꼴을 처리합니다. 이렇게 하면 많은 사용자 지정 C 코드가 제거되고 백엔드 간의 일부 코드 중복이 제거됩니다.
이 방법으로 글꼴을 부분 집합화하는 것은 가장 쉬운 방법이지만 글꼴의 힌트를 잃게 되므로 가능한 경우 파일에 전체 글꼴을 포함하는 방법을 계속 제공해야 합니다.
대체 글꼴 하위 설정 옵션에는 Cairo에 내장된 하위 설정을 사용하거나(카이로의 나머지 부분 없이 사용할 수 있는지 확실하지 않음) fontforge (무겁고 교차 플랫폼 종속성이 아님)를 사용하는 것이 포함됩니다.
자유형 래퍼
우리의 FreeType 래퍼는 실제로 재작업을 사용할 수 있습니다. 자체 이미지 버퍼 클래스를 정의합니다(Numpy 배열이 더 쉬울 때). FreeType은 매우 다양한 글꼴 파일을 처리할 수 있지만 Apple 공급업체가 아닌 TrueType 파일 및 OpenType 파일의 특정 기능을 지원하기 훨씬 더 어렵게 만드는 래퍼에는 제한이 있습니다. (Windows 7 및 8과 함께 제공되는 글꼴을 지원하기 위한 끔찍한 결과는 #2088을 참조하십시오.) 이 래퍼를 새로 다시 작성하면 먼 길을 갈 것이라고 생각합니다.
텍스트 고정 및 정렬 및 회전
기준선 처리가 1.3.0에서 변경되어 이제 백엔드에 텍스트 하단이 아닌 텍스트 기준선 위치가 지정됩니다. 이것은 아마도 올바른 동작이며 MEP 리팩토링도 이 규칙을 따라야 합니다.
여러 줄 텍스트에서 정렬을 지원하려면 텍스트 정렬을 처리하는 것이 (제안된) 텍스트 엔진의 책임이어야 합니다. 주어진 텍스트 청크에 대해 각 엔진은 해당 텍스트의 경계 상자와 해당 상자 내 앵커 포인트의 오프셋을 계산합니다. 따라서 블록의 va가 "top"이면 앵커 포인트는 상자의 맨 위에 있게 됩니다.
텍스트 회전은 항상 기준점을 중심으로 해야 합니다. 나는 그것이 matplotlib의 현재 동작과 일치하는지 확신하지 못하지만 가장 건전하고 가장 놀라운 선택인 것 같습니다. [작업이 완료되면 다시 방문할 수 있습니다]. 텍스트 회전은 텍스트 엔진에 의해 처리되어서는 안 됩니다. 텍스트 엔진과 렌더링 백엔드 사이의 레이어에 의해 처리되어야 균일한 방식으로 처리될 수 있습니다. [텍스트 엔진이 개별적으로 처리하는 회전에 대한 이점이 없습니다...]
이 작업의 일부로 해결해야 하는 텍스트 정렬 및 고정과 관련된 다른 문제가 있습니다. [TODO: 이것들을 열거].
수정해야 할 기타 사소한 문제
mathtext 코드에는 백엔드 관련 코드가 있습니다. 대신 다른 텍스트 엔진으로 출력을 제공해야 합니다. 그러나 다른 텍스트 엔진에 의해 수행되는 더 큰 레이아웃의 일부로 수학 텍스트 레이아웃을 삽입하는 것이 여전히 바람직하므로 이렇게 하는 것이 가능해야 합니다. 임의의 텍스트 엔진의 텍스트 레이아웃을 다른 엔진에 포함하는 것이 가능해야 하는지 여부는 열린 질문입니다.
텍스트 모드는 현재 전역 rcParam("text.usetex")에 의해 설정되어 있으므로 모두 켜거나 모두 끕니다. 우리는 텍스트 엔진("text.layout_engine")을 선택하기 위해 전역 rcParam을 계속 보유해야 하지만 내부적으로는 Text
개체의 재정의 가능한 속성이어야 하므로 동일한 그림이 필요한 경우 여러 텍스트 레이아웃 엔진의 결과를 결합할 수 있습니다. .
구현 #
"텍스트 엔진"의 개념을 소개합니다. 각 텍스트 엔진은 여러 추상 클래스를 구현합니다. 인터페이스 는 TextFont
지정된 글꼴 속성 집합에 대한 텍스트를 나타냅니다. 반드시 단일 글꼴 파일로 제한되지는 않습니다. 레이아웃 엔진이 서식 있는 텍스트를 지원하는 경우 패밀리의 여러 글꼴 파일을 처리할 수 있습니다. 인스턴스가 주어지면 사용자 는 주어진 글꼴의 주어진 텍스트 문자열에 대한 레이아웃을 나타내는 인스턴스를 TextFont
얻을 수 있습니다 . TextLayout
a TextLayout
에서 s 에 대한 반복자 TextSpan
가 반환되므로 엔진은 가능한 한 적은 범위를 사용하여 편집 가능한 원시 텍스트를 출력할 수 있습니다. 엔진이 개별 문자를 가져오려는 경우 TextSpan
인스턴스에서 가져올 수 있습니다.
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
글꼴의 하위 집합을 출력하려는 그래픽 백엔드는 키가 (fontname, glyph_id)이고 값이 경로인 파일 전역 문자 사전을 구축하여 각 문자에 대한 경로의 복사본 하나만 저장되도록 할 것입니다. 파일.
특수 케이스: "usetex" 기능은 현재 Postscript 파일에 직접 삽입하기 위해 TeX에서 직접 Postscript를 가져올 수 있지만 다른 백엔드의 경우 DVI 파일을 구문 분석하고 좀 더 추상적인 것을 생성합니다. 이와 같은 경우 대부분의 백엔드에 대해 TextLayout
구현
하지만 Postscript 백엔드에 대해 추가하여 이 메서드의 존재를 찾고 사용 가능한 경우 사용하거나 . 예를 들어, 그래픽 백엔드와 텍스트 엔진이 동일한 생태계(예: Cairo 및 Pango 또는 MacOSX 및 CoreText)에 속할 때 이러한 종류의 특수 케이스도 필요할 수 있습니다.get_spans
get_ps
get_spans
구현에는 세 가지 주요 부분이 있습니다.
freetype 래퍼를 다시 작성하고 ttconv를 제거합니다.
(1)이 완료되면 개념 증명으로 업스트림 STIX .otf 글꼴로 이동할 수 있습니다.
원격 URL에서 로드된 웹 글꼴에 대한 지원을 추가합니다. (글꼴 하위 설정에 freetype을 사용하여 활성화됨).
기존의 "builtin" 및 "usetex" 코드를 별도의 텍스트 엔진으로 리팩토링하고 위에서 설명한 API를 따릅니다.
고급 텍스트 레이아웃 라이브러리에 대한 지원을 구현합니다.
(1)과 (2)는 상당히 독립적이지만 (1)을 먼저 수행하면 (2)가 더 간단해집니다. (3)은 (1)과 (2)에 의존하지만 완료되지 않거나 연기되더라도 (1)과 (2)를 완료하면 "내장"을 개선하여 더 쉽게 진행할 수 있습니다. 텍스트 엔진.
이전 버전과의 호환성 #
앵커 및 회전과 관련된 텍스트 레이아웃은 작지만 개선된 방식으로 변경될 것입니다. 여러 줄 문자의 레이아웃은 수평 정렬을 준수하므로 훨씬 더 좋습니다. 양방향 텍스트의 레이아웃 또는 기타 고급 유니코드 기능은 이제 본질적으로 작동하므로 사용자가 현재 자체 해결 방법을 사용 중인 경우 일부 문제가 발생할 수 있습니다.
글꼴은 다르게 선택됩니다. "builtin" 및 "usetex" 텍스트 렌더링 엔진 사이에서 일종의 작업에 사용되던 핵이 더 이상 작동하지 않을 수 있습니다. 이전에 matplotlib에서 찾지 못한 글꼴을 OS에서 찾은 글꼴을 선택할 수 있습니다.
대안 #
미정