Silnik substytucji OpenType GSUB

Powierzchnia możliwości substytucji glifów (v2.119.43 - v2.119.50)

 

Kształtowanie arabskie  Podzbiory CFF / OpenType

Silnik OpenType GSUB (Glyph SUBstitution) w HotPDF pozwala kodowi wywołującemu odpytywać, sterować i osadzać każdy rodzaj substytucji glifów deklarowany przez font OpenType - ligatury, alternatywy stylistyczne, warianty kontekstowe, arabskie / indyjskie formy kształtowania, alternatywne formy CJK itd. Wszystkie OpenType GSUB LookupType od 1 do 8 są zaimplementowane i udostępnione jako powierzchnia zapytań o możliwości; kod wywołujący steruje emisją tekstu i decyduje, który glif zastępczy zapisać do strumienia zawartości strony.

 

Publiczne API

type

  TGSUBStringArray = array of AnsiString;

 

// LookupType 1 - Single Substitution (jeden glif -> jeden glif)

function GetSingleSubstituteGlyph(InputGID: Word; const FeatureTag: AnsiString): Word;

 

// LookupType 2 - Multiple Substitution (jeden glif -> sekwencja glifów)

function GetMultipleSubstituteGlyphs(InputGID: Word; const FeatureTag: AnsiString;

  var OutGIDs: array of Word): Boolean;

 

// LookupType 3 - Alternate Substitution (jeden glif -> jedna z N alternatyw)

function GetAlternateGlyphCount(InputGID: Word; const FeatureTag: AnsiString): Integer;

function GetAlternateGlyph(InputGID: Word; const FeatureTag: AnsiString;

  AlternateIndex: Integer): Word;

 

// LookupType 4 - Ligature Substitution (N glifów -> jedna ligatura)

function ApplyLigatureSubstitution(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  out OutGID: Word; out ConsumedCount: Integer): Boolean;

 

// LookupType 5 + 6 - Contextual / Chained Contextual Substitution

function ApplyContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  var OutGIDs: array of Word;

  out ConsumedLen: Integer): Boolean;

 

// LookupType 8 - Reverse Chained Contextual Single Substitution

function ApplyReverseChainedContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  out OutGID: Word): Boolean;

 

// Wybór Script / LangSys (faza 7)

procedure SetGSUBScript(const ScriptTag: AnsiString);

procedure SetGSUBLanguage(const LangTag: AnsiString);

function GetGSUBScripts: TGSUBStringArray;

function GetGSUBLanguages(const ScriptTag: AnsiString): TGSUBStringArray;

function GetGSUBFeatures(const ScriptTag, LangTag: AnsiString): TGSUBStringArray;

 

// Domknięcie subsettera TTF (faza 9)

procedure MarkUnicodeGlyphUsed(GID: Word);

 

Opis

Silnik aktywuje się po tym, jak RegisterUnicodeTTF przeanalizuje font i zbuforuje jego tabele GSUB / GDEF / cmap. Każde zapytanie o substytucję przechodzi przez łańcuch ScriptList / LangSysList / FeatureList / LookupList fontu i przekazuje pracę do odpowiedniego handlera LookupType. Powyższe 12 metod to kompletna powierzchnia publiczna; wszystko inne (przejście cmap, parsowanie ScriptList, wyszukiwanie w tabeli Coverage, rozwiązywanie ClassDef, respektowanie LookupFlag, rozpakowanie opakowania Extension, zagnieżdżona dyspozycja SequenceLookupRecord) znajduje się za tą powierzchnią.

 

Kontrakt defensywny obowiązuje wszędzie: fonty bez tabeli GSUB, tagi feature inne niż 4-bajtowe, funkcje nieogłaszane przez wybrany script / language, GID-y nieobjęte żadną podtabelą oraz glify wejściowe ignorowane przez LookupFlag zwracają bezpieczne no-op (False / OutGID = InputGID / puste OutGIDs / ConsumedCount = 1), więc kod wywołujący nie dostaje wyjątków dla rutynowych przypadków „brak pasującej substytucji”.

 

Macierz LookupType

LookupType 1 (Single Substitution) - jeden glif mapuje się na jeden zamiennik. Kanoniczne funkcje: salt, ss01-ss20, smcp, onum, liga, gdy LookupType 1 jest podłączony, oraz arabskie formy pozycyjne init / medi / fina / isol w fontach, które prowadzą je przez GSUB. Użyj GetSingleSubstituteGlyph.

LookupType 2 (Multiple Substitution) - jeden glif dzieli się na sekwencję glifów zastępczych. Kanoniczny użytkownik: ccmp Glyph Composition / Decomposition (prekomponowane akcentowane litery łacińskie dzielą się na bazę + znaki łączące dla późniejszego pozycjonowania znaków). Użyj GetMultipleSubstituteGlyphs.

LookupType 3 (Alternate Substitution) - jeden glif mapuje się na jedną z N alternatyw. Kanoniczne funkcje: aalt (Access All Alternates), salt, gdy jest podłączone jako Type 3, titl (Titling Alternates), zestawy stylistyczne ss01-ss20, gdy projektant fontu oferuje więcej niż jedną alternatywę w slocie. Użyj GetAlternateGlyphCount + GetAlternateGlyph.

LookupType 4 (Ligature Substitution) - N glifów wejściowych składa się w jedną ligaturę. Kanoniczne funkcje: liga (Standard Ligatures: fi / fl / ffi / ffl), clig (Contextual Ligatures), dlig (Discretionary Ligatures), hlig (Historical Ligatures), rlig (Required Ligatures - arabskie LAM-ALEF i podobne), ligatury pism indyjskich (akhn, pres, blws, psts). Użyj ApplyLigatureSubstitution.

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - dopasowuje sekwencję glifów wejściowych i uruchamia zagnieżdżone lookupy w konkretnych pozycjach dopasowania. Zaimplementowane są wszystkie trzy warianty Format (1 sekwencja literalna, 2 sekwencja ClassDef, 3 sekwencja Coverage); dyspozytor SequenceLookupRecord ponownie wchodzi do LookupList i obsługuje zagnieżdżone lookupy Single / Multiple / Alternate (pierwszy) / Ligature z bieżącym śledzeniem MatchPositions. Kanoniczne funkcje: rclt (Required Contextual Alternates - arabskie init/medi/fina/isol sterowane przez GSUB), clig, calt, kształtowanie indyjskie pres / blws / psts / half / pstf / cjct. Użyj ApplyContextualSubst (jeden punkt wejścia obejmuje LookupType 5 i 6).

LookupType 7 (Extension Substitution) - czysta warstwa pośrednia, którą specyfikacja OpenType definiuje dla fontów, których podtabela substytucji leży poza 16-bitowym zasięgiem LookupList. Każde publiczne API przezroczysto podąża przez 32-bitową pośrednią Offset32 do rzeczywistej podtabeli LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Odblokowuje ciężkie fonty CJK / indyjskie (Noto Sans CJK, Noto Sans Devanagari), których GSUB przekracza 64 KB. Nie ma osobnego API - rozpakowanie jest automatyczne.

LookupType 8 (Reverse Chained Contextual Single Substitution) - kontekstowa substytucja 1:1, której cechą wyróżniającą jest to, że kod wywołujący musi stosować ją w ODWROTNEJ kolejności skanowania po wieloglifowym ciągu (koniec -> początek), ponieważ każdy zamiennik może zależeć od PRZYSZŁEGO kontekstu lookahead, który nie mógł zostać jeszcze zastąpiony. Kanoniczne użycie: arabskie / syriackie / N'Ko / indyjskie alternatywy kontekstowe, których forma końcowa zależy od kolejnego glifu. Użyj ApplyReverseChainedContextualSubst; kod wywołujący steruje pętlą skanowania wstecznego.

 

Wybór Script / LangSys

Domyślnie silnik preferuje script DFLT (albo pierwszy script deklarowany przez font) i domyślny LangSys. Wywołaj SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) oraz SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., z końcowymi spacjami do 4 bajtów), aby przypiąć zapytania do konkretnej pary script / language. Pusty ciąg przywraca bazową ścieżkę domyślną. Wybory utrzymują się między zapytaniami i są czyszczone przez RegisterUnicodeTTF('', nil).

 

Semantyka strict-vs-fallback: nieznany ScriptTag sprawia, że kolejne zapytania zwracają puste wyniki no-op (dzięki czemu kod wywołujący może wykryć niedostępność wybranego script); nieznany LangTag wraca do domyślnego LangSys skryptu zgodnie z konwencją OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures wyliczają to, co załadowany font faktycznie ogłasza.

 

Respektowanie LookupFlag i GDEF

Każde zapytanie odczytuje LookupFlag każdej tabeli Lookup (oraz opcjonalny końcowy uint16 markFilteringSet, gdy ustawione jest useMarkFilteringSet) i pomija glify wejściowe oznaczone do ignorowania. Respektowane są bity zdefiniowane w specyfikacji: ignoreBaseGlyphs (0x0002, pomiń klasę GDEF 1), ignoreLigatures (0x0004, pomiń klasę 2), ignoreMarks (0x0008, pomiń klasę 3), useMarkFilteringSet (0x0010) oraz wysoki bajt markAttachmentType. Parsowane są ClassDef Format 1 i 2; akceptowane są nagłówki GDEF v1.0 / v1.1 / v1.2. Fonty bez tabeli GDEF wracają do zasady „żaden glif nie jest ignorowany”, więc wyjście pozostaje identyczne bajtowo dla kodu używającego fontów bez GDEF.

 

Domknięcie subsettera TTF (MarkUnicodeGlyphUsed)

Subsetter TTF HotPDF v2.84.0 wyprowadza zestaw użytych glifów z FUnicodeUsedCps przez cmap. Glify zastępcze GSUB (alternatywy stylistyczne, ligatury, warianty kontekstowe - wszystko, co zwraca 7 powyższych API zapytań) zwykle nie mają codepointu prowadzącego do nich przez cmap, więc wcześniej były niewidoczne dla subsettera, a czytnik odbiorcy renderował w ich miejscu .notdef.

 

Po wyemitowaniu dowolnego GID zwróconego przez GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst do strumienia tekstu PDF wywołaj MarkUnicodeGlyphUsed(GID) raz na każdy emitowany GID, aby wciągnąć go do osadzonego podzbioru. Pomocnik jest idempotentny, defensywny (GID-y spoza zakresu są cicho odrzucane) i integruje się z przebiegiem domknięcia glifów złożonych w v2.84.0: kod wywołujący musi oznaczyć tylko najwyższy GID zastępczy - komponenty złożone są wciągane automatycznie.

 

Typowy przepływ (łacińskie kapitaliki)

 

PDF.RegisterUnicodeTTF('myFont', 'C:\\Windows\\Fonts\\arial.ttf');

PDF.SetGSUBScript('latn');

PDF.SetGSUBLanguage('');  // domyślny LangSys

SmallCapGID := PDF.GetSingleSubstituteGlyph(InputGID, 'smcp');

if SmallCapGID <> InputGID then

begin

  // emituj SmallCapGID do strumienia zawartości strony...

  PDF.MarkUnicodeGlyphUsed(SmallCapGID);  // wciągnij do podzbioru

end;

 

Typowy przepływ (arabska ligatura LAM-ALEF)

 

PDF.SetGSUBScript('arab');

Run := [LamGID, FathaGID, AlefGID];  // GID-y po cmap

if PDF.ApplyLigatureSubstitution(Run, 0, 'rlig', LigGID, ConsumedCount) then

begin

  // emituj LigGID + przesuń o ConsumedCount

  PDF.MarkUnicodeGlyphUsed(LigGID);

end;

 

Zakres i ograniczenia

Silnik jest powierzchnią zapytań wyłącznie o możliwości: odpowiada na pytanie „co GSUB zrobiłby tutaj”, ale nie uruchamia automatycznego potoku kształtowania (układ klasy Harfbuzz, zmiana kolejności z uwzględnieniem klastrów dla pism indyjskich, rozwiązywanie BiDi, pozycjonowanie GPOS, przyłączanie znaków). Kod wywołujący odpowiada za sterowanie pętlą skanowania, wybór emitowanego zamiennika / alternatywy, wywołanie MarkUnicodeGlyphUsed dla każdego emitowanego zastępczego GID oraz zastosowanie dowolnego pozycjonowania GPOS / znaków wymaganego przez font.

 

Kształtowanie arabskie / perskie / urdu po stronie producenta (obowiązkowa ligatura LAM-ALEF + Arabic Presentation Forms-A) jest zaimplementowane jako osobny wbudowany potok, który uruchamia się automatycznie podczas emisji tekstu - zobacz kształtowanie arabskie / perskie / urdu.

 

Ślad wersji

v2.119.43 Single Substitution + faza 1. v2.119.44 Multiple + Alternate (faza 2). v2.119.45 Ligature (faza 3). v2.119.46 Extension + GDEF + respektowanie LookupFlag (faza 4). v2.119.47 Contextual + Chained Contextual + dyspozytor SequenceLookupRecord (faza 5). v2.119.48 Reverse Chained Contextual - macierz LookupType 1-8 zamknięta (faza 6). v2.119.49 API wyboru Script / LangSys (faza 7). v2.119.50 domknięcie subsettera TTF przez MarkUnicodeGlyphUsed (faza 9; faza 8 była próbą integracji kształtowania po stronie producenta i została podzielona na 8a-8f dla przyszłych rewizji).

 

Zobacz też: Kształtowanie arabskie / perskie / urdu, Funkcje tworzenia podzbioru fontów CFF / OpenType, THotPDF.EnableFontSubsetting