Sdílet prostřednictvím


Výběr písma

Rozhraní IDWriteFontSet4 zveřejňuje metody pro výběr písem ze sady písem. Tyto metody umožňují přechod na typografického modelu rodiny písem při zachování kompatibility s existujícími aplikacemi, dokumenty a písmy.

Výběr písma (někdy označovaný jako porovnávání písem nebo mapování písem) je proces výběru dostupných písem, která nejlépe odpovídají vstupním parametrům předanými vaší aplikací. Vstupní parametry se někdy označují souhrnně jako logické písmo. Logické písmo obsahuje název rodiny písem a další atributy označující konkrétní písmo v rámci rodiny. Algoritmus výběru písma odpovídá logickému písmu ("požadované písmo") dostupnému fyzickému písmu ("písmo, které máte").

rodina písem je pojmenovaná skupina písem, která sdílejí společný návrh, ale můžou se lišit v atributech, jako je váha. Model rodiny písem definuje, jaké atributy lze použít k rozlišení písem v rámci rodiny. Nový model typografické rodiny písem má mnoho výhod oproti dvěma předchozím modelům rodiny písem používaným ve Windows. Změna modelů rodiny písem ale vytváří příležitosti k nejasnostem a problémům s kompatibilitou. Metody zveřejněné rozhraním IDWriteFontSet4 implementují hybridní přístup, který nabízí výhody typografického modelu rodiny písem a zároveň zmírňují problémy s kompatibilitou.

Toto téma porovnává starší modely rodiny písem s typografickým modelem rodiny písem; vysvětluje problémy kompatibility způsobené změnou modelů rodiny písem; a nakonec vysvětluje, jak lze tyto výzvy překonat pomocí metod [IDWriteFontSet4 ](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Model rodiny písem RBIZ

Model rodiny písem, který se používá v ekosystému aplikací GDI, se někdy označuje jako model "čtyř písem" nebo "RBIZ". Každá rodina písem v tomto modelu má obvykle maximálně čtyři písma. Popisek "RBIZ" pochází z konvence vytváření názvů používaných pro některé soubory písem, například:

Název souboru Styl písma
verdana.ttf Pravidelný
verdanab.ttf Tučný
verdanai.ttf Kurzíva
verdanaz.ttf Tučné kurzíva

U GDI jsou vstupní parametry použité k výběru písma definovány logfont struktury, která zahrnuje pole rodinného jména (lfFaceName), váhy (lfWeight) a kurzívy (lfItalic). Pole lfItalic má hodnotu PRAVDA nebo NEPRAVDA. GDI umožňuje lfWeight pole být libovolnou hodnotou v rozsahu FW_THIN (100) na FW_BLACK (900), ale z historických důvodů byla písma navržena tak, aby ve stejné skupině písem GDI nebyly více než dvě váhy.

Oblíbená uživatelská rozhraní aplikací z raného počátku obsahovala kurzívu (pro zapnutí a vypnutí kurzívy) a tučné tlačítko (pro přepínání mezi normálními a tučnými váhami). Použití těchto dvou tlačítek k výběru písem v rámci rodiny předpokládá model RBIZ. I když vlastní rozhraní GDI podporuje více než dvě váhy, vedly vývojáře kompatibility aplikací k nastavení názvu rodiny GDI (OpenType name ID 1) způsobem, který byl konzistentní s modelem RBIZ.

Předpokládejme například, že chcete do rodiny písem Arial přidat těžší váhu Black. Logicky je toto písmo součástí rodiny Arial, takže můžete očekávat, že ho vyberete nastavením lfFaceName na "Arial" a lfWeight na FW_BLACK. Neexistuje však způsob, jak si uživatel aplikace vybrat mezi třemi váhami pomocí tlačítka s tučným písmem se dvěma stavy. Řešením bylo dát novému písmu jiné jméno rodiny, takže ho uživatel mohl vybrat tak, že v seznamu rodin písem zvolí "Arial Black". Stejně tak není možné vybírat z různých šířek ve stejné rodině písem pouze tučným písmem a kurzívou, takže úzké verze Arialu mají v modelu RBIZ různé názvy rodin. Proto máme v modelu RBIZ rodiny písem "Arial", "Arial Black" a "Arial Narrow", i když tyto typy patří do jedné rodiny.

V těchto příkladech si můžete prohlédnout, jak mohou omezení modelu rodiny písem ovlivnit, jak se písma seskupují do rodin. Vzhledem k tomu, že rodiny písem jsou identifikovány jménem, znamená to, že stejné písmo může mít různá rodinná jména v závislosti na tom, jaký model rodiny písem používáte.

DirectWrite nepodporuje přímo model rodiny písem RBIZ, ale poskytuje metody převodu na a z modelu RBIZ, například IDWriteGdiInterop::CreateFontFromLOGFONT a IDWriteGdiInterop::ConvertFontToLOGFONT. Název rodiny RBIZ můžete získat také voláním jeho IDWriteFont::GetInformationalStrings metoda a určením DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Model rodiny písem ve stylu hmotnosti

Model rodiny písem s tloušťkou roztaženého stylu je původní model rodiny písem používaný rozhraním DirectWrite před zavedením typografického modelu rodiny písem. Označuje se také jako sklon šířky hmotnosti (WWS). V modelu WWS můžou být písma ve stejné rodině deaferentována třemi vlastnostmi: váha (DWRITE_FONT_WEIGHT), roztažení (DWRITE_FONT_STRETCH) a styl (DWRITE_FONT_STYLE).

Model WWS je flexibilnější než model RBIZ dvěma způsoby. Za prvé, písma ve stejné rodině lze odlišit roztažením (nebo šířkou) a také tloušťkou a stylem (normální, kurzívou nebo oblikováním). Za druhé může existovat více než dvě váhy ve stejné rodině. Tato flexibilita je dostatečná, aby bylo možné zahrnout všechny varianty Arialu do stejné rodiny WWS. Následující tabulka porovnává vlastnosti písma RBIZ a WWS pro výběr písem Arial:

Celé jméno Jméno rodiny RBIZ lfWeight lfItalic WWS FamilyName Hmotnost Natáhnout Styl
Arial Arial 400 0 Arial 400 5 0
Arial Bold Arial 700 0 Arial 700 5 0
Arial Black Arial Black 900 0 Arial 900 5 0
Arial Narrow Arial Narrow 400 0 Arial 400 3 0
Arial Narrow Bold Arial Narrow 700 0 Arial 700 3 0

Jak vidíte, "Arial Narrow" má stejné lfWeight a lfItalic hodnoty jako "Arial", takže má jiný název rodiny RBIZ, aby se zabránilo nejednoznačnosti. "Arial Black" má jiný název rodiny RBIZ, aby se zabránilo více než dvěma hmotnostem v rodině "Arial". Naproti tomu všechna tato písma jsou ve stejné rodině s váhou a roztažením.

Model s roztaženým stylem ale není otevřený. Pokud mají dvě písma stejnou váhu, roztažení a styl, ale liší se jiným způsobem (například optické velikosti), nemůžou být zahrnuté ve stejné rodině písem WWS. Tím se dostaneme k typografickému modelu rodiny písem.

Typografický model rodiny písem

Na rozdíl od předchůdců je typografický model rodiny písem otevřený. Podporuje libovolný počet os variant v rámci rodiny písem.

Pokud si parametry výběru písma myslíte jako souřadnice v návrhovém prostoru, model s roztaženým stylem definuje trojrozměrný souřadnicový systém s hmotností, roztažením a stylem jako osami. Každé písmo v rodině WWS musí mít jedinečné umístění definované jeho souřadnicemi podél těchto tří os. Pokud chcete vybrat písmo, zadáte název rodiny WWS a tloušťku, roztažení a parametry stylu.

Naproti tomu typografický model rodiny písem má prostor návrhu N-dimenzionální. Návrhář písma může definovat libovolný počet os návrhu, z nichž každý je označen značkou osy čtyřmi znaky. Dané umístění písma v prostoru návrhu N-dimenzionální je definováno polem hodnot osy, kde každá hodnota osy obsahuje značku osy a hodnotu s plovoucí desetinou čárkou. Pokud chcete vybrat písmo, zadáte typografický název rodiny a pole hodnot osy (DWRITE_FONT_AXIS_VALUE struktury).

I když je počet os písma otevřený, existuje několik registrovaných os se standardním významem a hodnoty váhy, roztažení a stylu lze mapovat na registrované hodnoty osy. DWRITE_FONT_WEIGHT lze mapovat na hodnotu osy wght (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH lze mapovat na hodnotu osy wdth (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE lze mapovat na kombinaci hodnot osy "ital" a "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC a DWRITE_FONT_AXIS_TAG_SLANT).

Další registrovaná osa je opsz (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Řada optických písem, jako je Sitka, obsahuje písma, která se liší podél osy "opsz", což znamená, že jsou navrženy tak, aby se používaly v různých velikostech bodů. Model rodiny písem WWS nemá osu optické velikosti, takže rodina písem Sitka musí být rozdělena do více rodin písem WWS: "Sitka Small", "Sitka Text", "Sitka Podnadpis" atd. Každá rodina písem WWS odpovídá jiné optické velikosti a uživatel ponechá správný název rodiny WWS pro danou velikost písma. S typografickým modelem rodiny písem může uživatel jednoduše zvolit "Sitka" a aplikace může automaticky nastavit hodnotu osy opsz na základě velikosti písma.

Typografický výběr písma a proměnná písma

Koncept os varianty je často přidružený k proměnným písmům, ale platí také pro statická písma. OpenType STAT (atributy stylu) tabulka deklaruje, jaké osy návrhu písmo má a hodnoty těchto os. Tato tabulka se vyžaduje pro různá písma, ale je relevantní i pro statická písma.

Rozhraní API DirectWrite zveřejňuje hodnoty osy wght, wdth, "ital" a "slnt" pro každé písmo, i když nejsou přítomné v tabulce STAT nebo pokud neexistuje žádná tabulka STAT. Pokud je to možné, tyto hodnoty jsou odvozeny z tabulky STAT. V opačném případě se odvozují od tloušťky písma, roztažení písma a stylu písma.

Osy písma můžou být proměnné nebo ne proměnné. Statické písmo má pouze ne proměnných os, zatímco proměnné písmo může mít obojí. Pokud chcete použít písmo proměnné, musíte vytvořit písmo proměnné instanci, ve které byly všechny osy proměnných svázané s konkrétními hodnotami. Rozhraní IDWriteFontFace představuje statické písmo nebo konkrétní instanci proměnné písma. Je možné vytvořit libovolnou instanci proměnné písma se zadanými hodnotami osy. Kromě toho může písmo proměnné deklarovat pojmenované instance v tabulce STAT s předdefinovanými kombinacemi hodnot osy. Pojmenované instance umožňují, aby se proměnné písmo chovalo podobně jako kolekce statických písem. Při vytváření výčtu prvků IDWriteFontFamily nebo IDWriteFontSetexistuje jeden prvek pro každé statické písmo a pro každou pojmenovanou instanci písma proměnné.

Typografický algoritmus porovnávání písem nejprve vybere potenciální kandidáty na základě jména rodiny. Pokud kandidáti shody obsahují různá písma, všechny kandidáty pro stejnou proměnnou jsou sbalené do jednoho kandidáta, ve kterém je každá osa proměnných přiřazena konkrétní hodnotě co nejblíže požadované hodnotě pro tuto osu. Pokud pro osu proměnných neexistuje žádná požadovaná hodnota, přiřadí se jí výchozí hodnota pro tuto osu. Pořadí kandidátů shody se pak určí porovnáním hodnot osy s požadovanými hodnotami osy.

Představte si například typografickou řadu Sitka ve Windows. Sitka je optická rodina písem, což znamená, že má osu "opsz". Ve Windows 11 se Sitka implementuje jako dvě proměnná písma s následujícími hodnotami osy. Všimněte si, že opsz a wght osy jsou proměnné, zatímco ostatní osy nejsou proměnné.

Název souboru "opsz" "wght" "wdth" "ital" "slnt"
SitkaVF.ttf 6-27.5 400-700 100 0 0
SitkaVF-Italic.ttf 6-27.5 400-700 100 1 -12

Předpokládejme, že požadované hodnoty osy jsou opsz:12 wght:475 wdth:100 ital:0 slnt:0. Pro každé písmo proměnné vytvoříme odkaz na písmo proměnné instanci, ve které má každá osa proměnných přiřazenou určitou hodnotu. Konkrétně jsou osy opsz a wght nastaveny na 12 a 475. Tím se zobrazí následující odpovídající písma s ne kurzívou, protože je vhodnější pro ital a slnt osy:

SitkaVF.ttf opsz:12 wght:475 wdth:100 ital:0 slnt0
SitkaVF-Italic.ttf opsz:12 wght:475 wdth:100 ital:1 slnt:-12

V předchozím příkladu jsou odpovídající písma libovolně proměnné instance písma. Neexistuje žádná pojmenovaná instance Sitka s hmotností 475. Naproti tomu algoritmus porovnávání se stylem váhy vrací pouze pojmenované instance.

Pořadí porovnávání písem

Existují různé přetížené GetMatchingFonts metody pro model rodiny písem s tloušťkou roztaženého stylu (IDWriteFontFamily::GetMatchingFonts) a typografický model rodiny písem (IDWriteFontCollection2::GetMatchingFonts). V obou případech je výstup seznam odpovídajících písem v sestupném pořadí podle priority podle toho, jak dobře odpovídá vstupním vlastnostem každé kandidátské písmo. Tato část popisuje, jak se určuje priorita.

V modelu s tloušťkou roztaženého stylu jsou vstupními parametry váha písma (DWRITE_FONT_WEIGHT), roztažení písma (DWRITE_FONT_STRETCH) a styl písma (DWRITE_FONT_STYLE). Algoritmus pro nalezení nejlepší shody byl zdokumentován v dokumentu white paper 2006 s názvem "WPF Font Selection Model" od Michail Leonov a David Brown. Podívejte se na část "Párování tváře ze seznamu kandidátských tváří". Tento dokument se týká Windows Presentation Foundation (WPF), ale DirectWrite později použil stejný přístup.

Algoritmus používá pojem vektoru atributu písma, který pro danou kombinaci hmotnosti, roztažení a stylu se vypočítá takto:

FontAttributeVector.X = (stretch - 5) * 1100;
FontAttributeVector.Y = style * 700;
FontAttributeVector.Z = (weight - 400) * 5;

Všimněte si, že každá souřadnice vektoru je normalizována odečtením hodnoty "normální" pro odpovídající atribut a vynásobením konstantou. Násobitelé kompenzují skutečnost, že rozsahy vstupních hodnot pro hmotnost, roztažení a styl jsou velmi odlišné. V opačném případě by váha (100.,999) dominovala nad stylem (0..2).

Pro každou shodu kandidáta se vypočítá vektorová vzdálenost a tečkovaný součin mezi vektorem atributu písma kandidáta a vektorem atributu vstupního písma. Při porovnávánídvouch Pokud jsou vzdálenosti stejné, kandidát s menším tečkovým produktem je lepší shoda. Pokud je tečkovaný produkt také stejný, vzdálenosti podél os X, Y a Z se porovnávají v daném pořadí.

Porovnání vzdáleností je dostatečně intuitivní, ale použití tečky jako sekundární míry může vyžadovat určité vysvětlení. Předpokládejme, že vstupní hmotnost je polobold (600) a dvě kandidátské váhy jsou černé (900) a středníky (300). Vzdálenost každé kandidátské hmotnosti od vstupní hmotnosti je stejná, ale černá hmotnost je ve stejném směru od původu (tj. 400 nebo normální), takže bude mít menší tečku.

Typografický shodný algoritmus je zobecněním algoritmu pro tloušťku a roztažení. Každá hodnota osy se považuje za souřadnici ve vektoru atributu N-dimenzionálního písma. Pro každou shodu kandidáta se vypočítá vektorová vzdálenost a tečkovaný součin mezi vektorem atributu písma kandidáta a vektorem atributu vstupního písma. Kandidát s menší vektorovou vzdáleností je lepší shoda. Pokud jsou vzdálenosti stejné, kandidát s menším tečkovým produktem je lepší shoda. Pokud je tečkovaný produkt také stejný, může být přítomnost v zadané rodině s roztažením ve stylu hmotnosti použita jako tie-breaker.

Pokud chcete vypočítat vektorovou vzdálenost a tečkovaný součin, musí mít vektor atributu písma kandidáta a vektor atributu vstupního písma stejné osy. Proto každá chybějící hodnota osy v obou vektorech je vyplněna nahrazením standardní hodnoty pro tuto osu. Vektorové souřadnice jsou normalizovány odečtením standardní (neboli "normální") hodnoty pro odpovídající osu a vynásobením výsledku násobením násobitele specifického pro osu. Následují násobitele a standardní hodnoty pro každou osu:

Osa Multiplikátor Standardní hodnota
"wght" 5 400
"wdth" 55 100
"ital" 1400 0
"slnt" 35 0
"opsz" 1 12
jiný 1 0

Násobitele jsou konzistentní s těmi, které používá algoritmus pro roztažení a roztažení, ale podle potřeby se škáluje. Například normální šířka je 100, což odpovídá roztažení 5. Výsledkem je násobitel 55 vs. 1100. Atribut staršího stylu (0...2) proplete kurzívu a šikmo, který je v typografickém modelu rozdělený na osu "ital" (0..1) a osu "slnt" (-90..90). Zvolené násobitele pro tyto dvě osy poskytují ekvivalentní výsledky staršímu algoritmu, pokud předpokládáme výchozí 20stupňový sklon pro šikmá písma.

Typografický výběr písma a optická velikost

Aplikace používající typografický model rodiny písem může implementovat optické určení velikosti zadáním hodnoty osy opsz jako parametr výběru písma. Aplikace pro zpracování textu může například zadat hodnotu osy opsz rovna velikosti písma v bodech. V takovém případě může uživatel jako rodinu písem vybrat "Sitka" a aplikace automaticky vybere instanci Sitka se správnou hodnotou osy opsz. Pod modelem WWS je každá optická velikost vystavena jako jiné jméno rodiny a je na uživateli, aby vybral ten správný.

Teoreticky by bylo možné implementovat automatickou optické změnu velikosti v modelu ve stylu protažení hmotnosti přepsáním hodnoty osy opsz jako samostatný krok po výběru písma. To ale funguje jenom v případě, že první odpovídající písmo je proměnlivé písmo s proměnnou opsz osou. Zadání opsz jako parametr výběru písma funguje stejně dobře pro statická písma. Například rodina písem Sitka se ve Windows 11 implementuje jako proměnná písma, ale jako kolekce statických písem ve Windows 10. Statická písma mají různé nepřekrývající se opsz rozsahy os (jsou deklarovány jako oblasti pro účely výběru písma, ale nejedná se o proměnné osy). Zadání opsz jako parametru výběru písma umožňuje výběr správného statického písma pro výběr optické velikosti.

Výhody výběru typografického písma a problémy s kompatibilitou

Typografický model výběru písma má oproti dřívějším modelům několik výhod, ale ve své čisté podobě má některé potenciální problémy s kompatibilitou. Tato část popisuje výhody a problémy s kompatibilitou. Další část popisuje model hybridního výběru písem, který zachovává výhody a zároveň snižuje problémy s kompatibilitou.

Výhody typografického modelu rodiny písem jsou:

  • Písma se dají seskupit do rodin podle záměru návrháře, nikoli rozdělit do podřízených rodin kvůli omezením modelu rodiny písem.

  • Aplikace může automaticky vybrat správnou hodnotu osy opsz na základě velikosti písma, a ne vystavit uživatelům různé optické velikosti jako různé rodiny písem.

  • Je možné vybrat libovolné instance proměnných písem. Pokud například písmo proměnné podporuje váhy v souvislém rozsahu 100–900, typografický model může vybrat libovolné hmotnosti v této oblasti. Starší modely rodiny písem mohou zvolit pouze nejbližší váhu z pojmenovaných instancí definovaných písmem.

Problémy s kompatibilitou s modelem výběru typografického písma jsou:

  • Některá starší písma nelze jednoznačně vybrat pouze pomocí typografického názvu rodiny a hodnot osy.

  • Existující dokumenty můžou odkazovat na písma podle názvu rodiny WWS nebo názvu rodiny RBIZ. Uživatelé také mohou očekávat, že budou používat názvy rodin WWS a RBIZ. Dokument může například místo "Sitka" (typografický název rodiny) zadat "Sitka Podnadpis" (název rodiny WWS).

  • Knihovna nebo architektura můžou použít typografický model rodiny písem, aby využívaly automatické optické nastavení velikosti, ale neposkytují rozhraní API pro určení libovolných hodnot osy. I když je k dispozici nové rozhraní API, může být nutné, aby architektura fungovala s existujícími aplikacemi, které určují pouze parametry váhy, roztažení a stylu.

K problému s kompatibilitou se staršími písmy dochází, protože koncept typografického názvu rodiny předchází konceptu hodnot osy písem, které byly zavedeny spolu s proměnnými písmy v OpenType 1.8. Před verzí OpenType 1.8 se název typografické rodiny pouze vyjádřil záměrem návrháře, že sada písem souvisela, ale bez záruky, že by tato písma mohla být programově diferencována na základě jejich vlastností. Předpokládejme jako hypotetický příklad, že všechna následující písma mají typografický název rodiny "Legacy":

Celé jméno Rodina WWS Hmotnost Natáhnout Styl Typo Rodina vytáhový wdth ital slnt
Dědictví Dědictví 400 5 0 Dědictví 400 100 0 0
Starší tučné písmo Dědictví 700 5 0 Dědictví 700 100 0 0
Starší černá verze Dědictví 900 5 0 Dědictví 900 100 0 0
Starší soft Starší soft 400 5 0 Dědictví 400 100 0 0
Starší soft bold Starší soft 700 5 0 Dědictví 700 100 0 0
Starší měkká černá Starší soft 900 5 0 Dědictví 900 100 0 0

Typografická řada Starší verze má tři váhy a každá hmotnost má pravidelné a měkké varianty. Pokud se jedná o nová písma, je možné je implementovat jako deklarování osy návrhu SOFT. Tato písma však předepisují OpenType 1.8, takže jejich jediné návrhové osy jsou odvozené od váhy, roztažení a stylu. Pro každou váhu má tato rodina písem dvě písma se stejnými hodnotami osy, takže není možné jednoznačně vybrat písmo v této rodině pomocí hodnot os samostatně.

Algoritmus hybridního výběru písma

Rozhraní API pro výběr písma popsaná v další části používají algoritmus hybridního výběru písma, který zachovává výhody typografického výběru písma a zároveň zmírňuje problémy s kompatibilitou.

Hybridní výběr písma poskytuje most ze starších modelů rodiny písem tím, že umožňuje namapovat hodnoty tloušťky písma, roztažení písma a stylu písma na odpovídající hodnoty osy písma. To pomáhá řešit problémy s kompatibilitou dokumentů a aplikací.

Kromě toho algoritmus hybridního výběru písma umožňuje zadanému rodinnému jménu zadat typografický název rodiny, název rodiny s tloušťkou roztaženého stylu, název rodiny GDI/RBIZ nebo úplný název písma. Porovnávání probíhá jedním z následujících způsobů v sestupném pořadí priority:

  1. Jméno odpovídá typografické rodině (například Sitka). Porovnávání probíhá v typografické rodině a používají se všechny požadované hodnoty osy. Pokud název odpovídá podfamii WWS (tj. jedna menší než typografická rodina), členství v subfamii WWS se použije jako tie-breaker.

  2. Název odpovídá rodině WWS (například Sitka Text). K porovnávání dochází v rámci rodiny WWS a hodnoty požadované osy jiné než "wght", "wdth", "ital" a "slnt" jsou ignorovány.

  3. Název odpovídá rodině GDI (například Bahnschrift Condensed). K porovnávání dochází v rámci řady RBIZ a požadované hodnoty osy jiné než "wght", "ital" a "slnt" jsou ignorovány.

  4. Název odpovídá úplnému názvu (například Bahnschrift Bold Condensed). Vrátí se písmo odpovídající celému jménu. Požadované hodnoty osy jsou ignorovány. Shoda podle úplného názvu písma je povolená, protože ji GDI podporuje.

Předchozí část popsala nejednoznačný typografický typografický název "Legacy". Hybridní algoritmus umožňuje nejednoznačnosti zabránit zadáním "Starší verze" nebo "Starší soft" jako názvu rodiny. Pokud je zadána možnost Starší verze soft, neexistuje nejednoznačnost, protože shoda nastane pouze v rámci řady WWS. Pokud je zadána možnost Starší verze, považují se všechna písma v typografické rodině za kandidáty, ale nejednoznačnost se vyhnete použitím členství v rodině WWS starší verze jako tie-breaker.

Předpokládejme, že dokument určuje název rodiny a tloušťku, roztažení a parametry stylu, ale žádné hodnoty osy. Aplikace může nejprve převést hmotnost, roztažení, styl a velikost písma na hodnoty osy voláním IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Aplikace pak může předat hodnoty názvu rodiny i osy IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts vrátí seznam odpovídajících písem v pořadí priority a výsledek je vhodný, jestli je zadaným názvem rodiny typografický název rodiny, jméno rodiny ve stylu hmotnosti, název rodiny RBIZ nebo celé jméno. Pokud má zadaná řada osu opsz, je na základě velikosti písma automaticky vybrána příslušná optická velikost.

Předpokládejme, že dokument určuje tloušťku, roztažení a styl a také určuje hodnoty osy. V takovém případě je možné explicitní hodnoty osy předat také IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValuesa odvozené hodnoty osy vrácené metodou budou obsahovat pouze osy písma, které nebyly explicitně zadány. Hodnoty osy zadané v dokumentu (nebo aplikaci) proto mají přednost před hodnotami osy odvozenými z váhy, roztažení, stylu a velikosti písma.

Rozhraní API pro hybridní výběr písma

Model hybridního výběru písma je implementovaný následujícími metodami IDWriteFontSet4:

  • IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues metoda převede velikost písma, tloušťku, roztažení a parametry stylu na odpovídající hodnoty osy. Všechny explicitní hodnoty osy předávané klientem jsou vyloučeny z odvozených hodnot osy.

  • Metoda IDWriteFontSet4::GetMatchingFonts vrátí seznam shodných písem s názvem rodiny a polem hodnot osy. Jak je popsáno výše, parametr rodinného jména může být typografický název rodiny, název rodiny WWS, název rodiny RBIZ nebo celé jméno. (Úplný název identifikuje určitý styl písma, například "Arial Bold Kurzíva". GetMatchingFonts podporuje porovnávání úplným názvem pro zajištění větší kompatibility s GDI, což také umožňuje.)

Následující další rozhraní API DirectWrite také používají algoritmus hybridního výběru písma:

Příklady kódu rozhraní API pro výběr písma, která se používají

Tato část ukazuje kompletní konzolovou aplikaci, která ukazuje IDWriteFontSet4::GetMatchingFonts a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues metody. Nejprve zahrňme několik hlaviček:

#include <dwrite_core.h>
#include <wil/com.h>
#include <iostream>
#include <string>
#include <vector>

Metoda IDWriteFontSet4::GetMatchingFonts vrátí seznam písem v pořadí priority, které odpovídají zadaným hodnotám rodiny a osy. Následující MatchAxisValues funkce vypíše parametry do IDWriteFontSet4::GetMatchingFonts a seznam odpovídajících písem ve vrácené sadě písem.

// Forward declarations of overloaded output operators used by MatchAxisValues.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue);
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference);
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference);
//
// MatchAxisValues calls IDWriteFontSet4::GetMatchingFonts with the
// specified parameters and outputs the matching fonts.
//
void MatchAxisValues(
    IDWriteFontSet4* fontSet,
    _In_z_ WCHAR const* familyName,
    std::vector<DWRITE_FONT_AXIS_VALUE> const& axisValues,
    DWRITE_FONT_SIMULATIONS allowedSimulations
    )
{
    // Write the input parameters.
    std::wcout << L"GetMatchingFonts(\"" << familyName << L"\", {";
    for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
    {
        std::wcout << L' ' << axisValue;
    }
    std::wcout << L" }, " << allowedSimulations << L"):\n";
    // Get the matching fonts for the specified family name and axis values.
    wil::com_ptr<IDWriteFontSet4> matchingFonts;
    THROW_IF_FAILED(fontSet->GetMatchingFonts(
        familyName,
        axisValues.data(),
        static_cast<UINT32>(axisValues.size()),
        allowedSimulations,
        &matchingFonts
    ));
    // Output the matching font face references.
    UINT32 const fontCount = matchingFonts->GetFontCount();
    for (UINT32 fontIndex = 0; fontIndex < fontCount; fontIndex++)
    {
        wil::com_ptr<IDWriteFontFaceReference1> faceReference;
        THROW_IF_FAILED(matchingFonts->GetFontFaceReference(fontIndex, &faceReference));
        std::wcout << L"    " << faceReference.get() << L'\n';
    }
    std::wcout << std::endl;
}

Aplikace může mít parametry váhy, roztažení a stylu místo hodnot osy (nebo kromě) os. Aplikace může například potřebovat pracovat s dokumenty, které odkazují na písma pomocí parametrů RBIZ nebo weight-stretch-style. I když aplikace přidá podporu pro zadávání libovolných hodnot osy, může být potřeba podporovat i starší parametry. Aplikace tak může volat IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValue s před voláním IDWriteFontSet4::GetMatchingFonts.

Následující funkce MatchFont kromě hodnot os přebírá kromě hodnot osy také váhu, roztažení, styl a velikost písma. Tyto parametry předá IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues metodu pro výpočet odvozených hodnot osy, které jsou připojeny ke vstupním hodnotám osy. Předá kombinované hodnoty osy výše MatchAxisValues funkce.

struct FontStyleParams
{
    FontStyleParams() {}
    FontStyleParams(DWRITE_FONT_WEIGHT fontWeight) : fontWeight{ fontWeight } {}
    FontStyleParams(float fontSize) : fontSize{ fontSize } {}
    DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL;
    DWRITE_FONT_STRETCH fontStretch = DWRITE_FONT_STRETCH_NORMAL;
    DWRITE_FONT_STYLE fontStyle = DWRITE_FONT_STYLE_NORMAL;
    float fontSize = 0.0f;
};
//
// MatchFont calls IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues to convert
// the input parameters to axis values and then calls MatchAxisValues.
//
void MatchFont(
    IDWriteFactory7* factory,
    _In_z_ WCHAR const* familyName,
    FontStyleParams styleParams = {},
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues = {},
    DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE
    )
{
    wil::com_ptr<IDWriteFontSet2> fontSet2;
    THROW_IF_FAILED(factory->GetSystemFontSet(/*includeDownloadableFonts*/ false, &fontSet2));
    wil::com_ptr<IDWriteFontSet4> fontSet;
    THROW_IF_FAILED(fontSet2->QueryInterface(&fontSet));
    // Ensure the total number of axis values can't overflow a UINT32.
    size_t const inputAxisCount = axisValues.size();
    if (inputAxisCount > UINT32_MAX - DWRITE_STANDARD_FONT_AXIS_COUNT)
    {
        THROW_HR(E_INVALIDARG);
    }
    // Reserve space at the end of axisValues vector for the derived axis values.
    // The maximum number of derived axis values is DWRITE_STANDARD_FONT_AXIS_COUNT.
    axisValues.resize(inputAxisCount + DWRITE_STANDARD_FONT_AXIS_COUNT);
    // Convert the weight, stretch, style, and font size to derived axis values.
    UINT32 derivedAxisCount = fontSet->ConvertWeightStretchStyleToFontAxisValues(
        /*inputAxisValues*/ axisValues.data(),
        static_cast<UINT32>(inputAxisCount),
        styleParams.fontWeight,
        styleParams.fontStretch,
        styleParams.fontStyle,
        styleParams.fontSize,
        /*out*/ axisValues.data() + inputAxisCount
        );
    // Resize the vector based on the actual number of derived axis values returned.
    axisValues.resize(inputAxisCount + derivedAxisCount);
    // Pass the combined axis values (explicit and derived) to MatchAxisValues.
    MatchAxisValues(fontSet.get(), familyName, axisValues, allowedSimulations);
}

Následující funkce ukazuje výsledky volání výše uvedené MatchFont funkce s několika ukázkovými vstupy:

void TestFontSelection(IDWriteFactory7* factory)
{
    // Request "Cambria" with bold weight, with and without allowing simulations.
    //  - Matches a typographic/WWS family.
    //  - Result includes all fonts in the family ranked by priority.
    //  - Useful because Cambria Bold might have fewer glyphs than Cambria Regular.
    //  - Simulations may be applied if allowed.
    MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD);
    MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD, {}, DWRITE_FONT_SIMULATIONS_NONE);
    // Request "Cambria Bold".
    //  - Matches the full name of one static font.
    MatchFont(factory, L"Cambria Bold");
    // Request "Bahnschrift" with bold weight.
    //  - Matches a typographic/WWS family.
    //  - Returns an arbitrary instance of the variable font.
    MatchFont(factory, L"Bahnschrift", DWRITE_FONT_WEIGHT_BOLD);
    // Request "Segoe UI Variable" with two different font sizes.
    //  - Matches a typographic family name only (not a WWS family name).
    //  - Font size maps to "opsz" axis value (Note conversion from DIPs to points.)
    //  - Returns an arbitrary instance of the variable font.
    MatchFont(factory, L"Segoe UI Variable", 16.0f);
    MatchFont(factory, L"Segoe UI Variable", 32.0f);
    // Same as above with an explicit opsz axis value.
    // The explicit value is used instead of an "opsz" value derived from the font size.
    MatchFont(factory, L"Segoe UI Variable", 16.0f, { { DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE, 32.0f } });
    // Request "Segoe UI Variable Display".
    //  - Matches a WWS family (NOT a typographic family).
    //  - The input "opsz" value is ignored; the optical size of the family is used.
    MatchFont(factory, L"Segoe UI Variable Display", 16.0f);
    // Request "Segoe UI Variable Display Bold".
    //  - Matches the full name of a variable font instance.
    //  - All input axes are ignored; the axis values of the matching font are used.
    MatchFont(factory, L"Segoe UI Variable Display Bold", 16.0f);
}

Následuje výstup výše uvedené funkce TestFontSelection:

GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
    cambria.ttc wght:400 wdth:100 ital:0 slnt:0 BOLDSIM
    cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
    cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4 BOLDSIM
GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 0):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
    cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
    cambria.ttc wght:400 wdth:100 ital:0 slnt:0
    cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4
GetMatchingFonts("Cambria Bold", { wght:400 wdth:100 ital:0 slnt:0 }, 3):
    cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Bahnschrift", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
    bahnschrift.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:12 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:24 }, 3):
    SegUIVar.ttf opsz:24 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { opsz:32 wght:400 wdth:100 ital:0 slnt:0 }, 3):
    SegUIVar.ttf opsz:32 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:36 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display Bold", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
    SegUIVar.ttf opsz:36 wght:700 wdth:100 ital:0 slnt:0

Následuje implementace přetížených operátorů deklarovaných výše. Tyto hodnoty používají MatchAxisValues k zápisu hodnot vstupní osy a výsledných odkazů na rozpoznávání tváře písma:

// Output a font axis value.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue)
{
    UINT32 tagValue = axisValue.axisTag;
    WCHAR tagChars[5] =
    {
        static_cast<WCHAR>(tagValue & 0xFF),
        static_cast<WCHAR>((tagValue >> 8) & 0xFF),
        static_cast<WCHAR>((tagValue >> 16) & 0xFF),
        static_cast<WCHAR>((tagValue >> 24) & 0xFF),
        '\0'
    };
    return out << tagChars << L':' << axisValue.value;
}
// Output a file name given a font file reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference)
{
    wil::com_ptr<IDWriteFontFileLoader> loader;
    THROW_IF_FAILED(fileReference->GetLoader(&loader));
    wil::com_ptr<IDWriteLocalFontFileLoader> localLoader;
    if (SUCCEEDED(loader->QueryInterface(&localLoader)))
    {
        const void* fileKey;
        UINT32 fileKeySize;
        THROW_IF_FAILED(fileReference->GetReferenceKey(&fileKey, &fileKeySize));
        WCHAR filePath[MAX_PATH];
        THROW_IF_FAILED(localLoader->GetFilePathFromKey(fileKey, fileKeySize, filePath, MAX_PATH));
        WCHAR* fileName = wcsrchr(filePath, L'\\');
        fileName = (fileName != nullptr) ? fileName + 1 : filePath;
        out << fileName;
    }
    return out;
}
// Output a font face reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference)
{
    // Output the font file name.
    wil::com_ptr<IDWriteFontFile> fileReference;
    THROW_IF_FAILED(faceReference->GetFontFile(&fileReference));
    std::wcout << fileReference.get();
    // Output the face index if nonzero.
    UINT32 const faceIndex = faceReference->GetFontFaceIndex();
    if (faceIndex != 0)
    {
        out << L'#' << faceIndex;
    }
    // Output the axis values.
    UINT32 const axisCount = faceReference->GetFontAxisValueCount();
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues(axisCount);
    THROW_IF_FAILED(faceReference->GetFontAxisValues(axisValues.data(), axisCount));
    for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
    {
        out << L' ' << axisValue;
    }
    // Output the simulations.
    DWRITE_FONT_SIMULATIONS simulations = faceReference->GetSimulations();
    if (simulations & DWRITE_FONT_SIMULATIONS_BOLD)
    {
        out << L" BOLDSIM";
    }
    if (simulations & DWRITE_FONT_SIMULATIONS_OBLIQUE)
    {
        out << L" OBLIQUESIM";
    }
    return out;
}

Pokud chcete ukázku zaokrouhlit, jedná se o funkce analýzy příkazového řádku a hlavní funkci:

char const g_usage[] =
"ParseCmdLine <args>\n"
"\n"
" -test             Test sample font selection parameters.\n"
" <familyname>      Sets the font family name.\n"
" -size <value>     Sets the font size in DIPs.\n"
" -bold             Sets weight to bold (700).\n"
" -weight <value>   Sets a weight in the range 100-900.\n"
" -italic           Sets style to DWRITE_FONT_STYLE_ITALIC.\n"
" -oblique          Sets style to DWRITE_FONT_STYLE_OBLIQUE.\n"
" -stretch <value>  Sets a stretch in the range 1-9.\n"
" -<axis>:<value>   Sets an axis value (for example, -opsz:24).\n"
" -nosim            Disallow font simulations.\n"
"\n";
struct CmdArgs
{
    std::wstring familyName;
    FontStyleParams styleParams;
    std::vector<DWRITE_FONT_AXIS_VALUE> axisValues;
    DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE;
    bool test = false;
};
template<typename T>
_Success_(return)
bool ParseEnum(_In_z_ WCHAR const* arg, long minValue, long maxValue, _Out_ T* result)
{
    WCHAR* endPtr;
    long value = wcstol(arg, &endPtr, 10);
    *result = static_cast<T>(value);
    return value >= minValue && value <= maxValue && *endPtr == L'\0';
}
_Success_(return)
bool ParseFloat(_In_z_ WCHAR const* arg, _Out_ float* value)
{
    WCHAR* endPtr;
    *value = wcstof(arg, &endPtr);
    return *arg != L'\0' && *endPtr == L'\0';
}
bool ParseCommandLine(int argc, WCHAR** argv, CmdArgs& cmd)
{
    for (int argIndex = 1; argIndex < argc; argIndex++)
    {
        WCHAR const* arg = argv[argIndex];
        if (*arg != L'-')
        {
            if (!cmd.familyName.empty())
                return false;
            cmd.familyName = argv[argIndex];
        }
        else if (!wcscmp(arg, L"-test"))
        {
            cmd.test = true;
        }
        else if (!wcscmp(arg, L"-size"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseFloat(argv[argIndex], &cmd.styleParams.fontSize))
                return false;
        }
        else if (!wcscmp(arg, L"-bold"))
        {
            cmd.styleParams.fontWeight = DWRITE_FONT_WEIGHT_BOLD;
        }
        else if (!wcscmp(arg, L"-weight"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 100, 900, &cmd.styleParams.fontWeight))
                return false;
        }
        else if (!wcscmp(arg, L"-italic"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_ITALIC;
        }
        else if (!wcscmp(arg, L"-oblique"))
        {
            cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_OBLIQUE;
        }
        else if (!wcscmp(arg, L"-stretch"))
        {
            if (++argIndex == argc)
                return false;
            if (!ParseEnum(argv[argIndex], 1, 9, &cmd.styleParams.fontStretch))
                return false;
        }
        else if (wcslen(arg) > 5 && arg[5] == L':')
        {
            // Example: -opsz:12
            DWRITE_FONT_AXIS_VALUE axisValue;
            axisValue.axisTag = DWRITE_MAKE_FONT_AXIS_TAG(arg[1], arg[2], arg[3], arg[4]);
            if (!ParseFloat(arg + 6, &axisValue.value))
                return false;
            cmd.axisValues.push_back(axisValue);
        }
        else if (!wcscmp(arg, L"-nosim"))
        {
            cmd.allowedSimulations = DWRITE_FONT_SIMULATIONS_NONE;
        }
        else
        {
            return false;
        }
    }
    return true;
}
int __cdecl wmain(int argc, WCHAR** argv)
{
    CmdArgs cmd;
    if (!ParseCommandLine(argc, argv, cmd))
    {
        std::cerr << "Invalid command. Type TestFontSelection with no arguments for usage.\n";
        return 1;
    }
    if (cmd.familyName.empty() && !cmd.test)
    {
        std::cout << g_usage;
        return 0;
    }
    wil::com_ptr<IDWriteFactory7> factory;
    THROW_IF_FAILED(DWriteCoreCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory7),
        (IUnknown**)&factory
    ));
    if (!cmd.familyName.empty())
    {
        MatchFont(
            factory.get(),
            cmd.familyName.c_str(),
            cmd.styleParams,
            std::move(cmd.axisValues),
            cmd.allowedSimulations
        );
    }
    if (cmd.test)
    {
        TestFontSelection(factory.get());
    }
}