Condividi tramite


Selezione del tipo di carattere

L'interfaccia IDWriteFontSet4 espone i metodi per la selezione dei tipi di carattere da un set di tipi di carattere. Questi metodi consentono di passare al modello di famiglia di caratteri tipografico mantenendo la compatibilità con applicazioni, documenti e tipi di carattere esistenti.

La selezione dei tipi di carattere (talvolta denominata corrispondenza dei tipi di carattere o mapping dei tipi di carattere) è il processo di selezione dei tipi di carattere disponibili che corrispondono meglio ai parametri di input passati dall'applicazione. I parametri di input vengono talvolta definiti collettivamente come carattere logico . Un tipo di carattere logico include un nome di famiglia di caratteri e altri attributi che indicano un tipo di carattere specifico all'interno della famiglia. Un algoritmo di selezione dei tipi di carattere corrisponde al tipo di carattere logico ("il tipo di carattere desiderato") a un tipo di carattere fisico disponibile ("un tipo di carattere disponibile").

Una famiglia di tipi di carattere è un gruppo denominato di tipi di carattere che condividono una progettazione comune, ma potrebbe differire in attributi come il peso. Un modello di famiglia di caratteri definisce gli attributi che possono essere usati per distinguere i tipi di carattere all'interno di una famiglia. Il nuovo modello di famiglia di caratteri tipografico offre molti vantaggi rispetto ai due modelli precedenti della famiglia di caratteri usati in Windows. Tuttavia, la modifica dei modelli di famiglia di caratteri crea opportunità di confusione e problemi di compatibilità. I metodi esposti dall'interfaccia IDWriteFontSet4 implementano un approccio ibrido che offre i vantaggi del modello di famiglia di caratteri tipografici, riducendo al contempo i problemi di compatibilità.

In questo argomento vengono confrontati i modelli della famiglia di caratteri meno recenti con il modello di famiglia di caratteri tipografico; spiega le sfide di compatibilità poste dalla modifica dei modelli della famiglia di caratteri; e infine spiega come queste sfide possono essere superate usando i metodi [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).

Modello di famiglia di caratteri RBIZ

Il modello di famiglia di tipi di carattere de facto usato nell'ecosistema di applicazioni GDI è talvolta denominato modello "modello a quattro caratteri" o "RBIZ". Ogni famiglia di caratteri in questo modello ha in genere al massimo quattro tipi di carattere. L'etichetta "RBIZ" deriva dalla convenzione di denominazione usata per alcuni file di tipo di carattere, ad esempio:

Nome file Stile carattere
verdana.ttf Regolare
verdanab.ttf Audace
verdanai.ttf Corsivo
verdanaz.ttf Grassetto corsivo

Con GDI, i parametri di input usati per selezionare un tipo di carattere sono definiti dalla strutturaLOGFONT, che include il nome della famiglia (lfFaceName), il peso (lfWeight) e i campi corsivo (lfItalic). Il campo lfItalic è TRUE o FALSE. GDI consente al campo lfWeight di essere qualsiasi valore nell'intervallo FW_THIN (100) di FW_BLACK (900), ma per motivi cronologici i tipi di carattere sono stati progettati da molto tempo in modo che non vi siano più di due pesi nella stessa famiglia di caratteri GDI.

Le interfacce utente dell'applicazione più diffuse fin dall'inizio includevano un pulsante corsivo (per attivare e disattivare il corsivo) e un pulsante in grassetto (per alternare i pesi normali e grassetti). L'uso di questi due pulsanti per selezionare i tipi di carattere all'interno di una famiglia presuppone il modello RBIZ. Pertanto, anche se GDI supporta più di due pesi, gli sviluppatori di tipi di carattere guidati dalla compatibilità delle applicazioni per impostare il nome della famiglia GDI (ID nome OpenType 1) in modo coerente con il modello RBIZ.

Si supponga, ad esempio, di voler aggiungere un peso "Nero" più pesante alla famiglia di caratteri Arial. Logicamente, questo tipo di carattere fa parte della famiglia Arial, quindi potrebbe essere necessario selezionarlo impostando lfFaceName su "Arial" e lfWeight su FW_BLACK. Tuttavia, non esiste un modo per consentire a un utente dell'applicazione di scegliere tra tre pesi usando un pulsante in grassetto a due stati. La soluzione consiste nel assegnare al nuovo tipo di carattere un nome di famiglia diverso, in modo che l'utente possa selezionarlo scegliendo "Arial Black" dall'elenco delle famiglie di caratteri. Analogamente, non è possibile scegliere tra larghezze diverse nella stessa famiglia di caratteri usando solo pulsanti grassetto e corsivo, quindi le versioni strette di Arial hanno nomi di famiglia diversi nel modello RBIZ. Così abbiamo "Arial", "Arial Black" e "Arial Narrow" nel modello RBIZ, anche se digitati tutti appartengono a una famiglia.

Da questi esempi, si può vedere come le limitazioni di un modello di famiglia di caratteri possono influire sul modo in cui i tipi di carattere vengono raggruppati in famiglie. Poiché le famiglie di caratteri sono identificate in base al nome, questo significa che lo stesso tipo di carattere può avere nomi di famiglia diversi a seconda del modello di famiglia di caratteri in uso.

DirectWrite non supporta direttamente il modello della famiglia di caratteri RBIZ, ma fornisce metodi di conversione da e verso il modello RBIZ, ad esempio IDWriteGdiInterop::CreateFontFromLOGFONT e IDWriteGdiInterop::ConvertFontToLOGFONT. Puoi anche ottenere il nome della famiglia RBIZ di un tipo di carattere chiamando il relativo metodo IDWriteFont::GetInformationalStrings e specificando DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.

Modello di famiglia di caratteri di tipo spessore esteso

Il modello di famiglia di caratteri di tipo spessore è il modello originale della famiglia di caratteri usato da DirectWrite prima dell'introduzione del modello di famiglia di caratteri tipografico. È noto anche come pendenza di spessore (WWS). Nel modello WWS i tipi di carattere all'interno della stessa famiglia possono essere diversi in base a tre proprietà: peso (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH) e stile (DWRITE_FONT_STYLE).

Il modello WWS è più flessibile rispetto al modello RBIZ in due modi. Prima di tutto, i tipi di carattere nella stessa famiglia possono essere differenziati in base all'estensione (o larghezza) e allo stile (regolare, corsivo o obliquo). In secondo luogo, ci possono essere più di due pesi nella stessa famiglia. Questa flessibilità è sufficiente per consentire l'inserimento di tutte le varianti di Arial nella stessa famiglia WWS. La tabella seguente confronta le proprietà dei tipi di carattere RBIZ e WWS per una selezione di tipi di carattere Arial:

Nome completo Nome famiglia RBIZ lfWeight lfItalic WWS FamilyName Peso Stendere Stile
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

Come si può notare, "Arial Narrow" ha gli stessi valori lfWeight e lfItalic come "Arial", quindi ha un nome di famiglia RBIZ diverso per evitare ambiguità. "Arial Black" ha un nome di famiglia RBIZ diverso per evitare di avere più di due pesi nella famiglia "Arial". Al contrario, tutti questi tipi di carattere si trovano nella stessa famiglia di stili di spessore.

Tuttavia, il modello di tipo weight-stretch non è aperto. Se due tipi di carattere hanno lo stesso peso, l'estensione e lo stile, ma differiscono in altro modo (ad esempio, dimensioni ottiche), non possono essere inclusi nella stessa famiglia di caratteri WWS. Questo ci porta al modello di famiglia di caratteri tipografici.

Modello di famiglia di caratteri tipografico

A differenza dei predecessori, il modello di famiglia di caratteri tipografico è aperto. Supporta un numero qualsiasi di assi di variazione all'interno di una famiglia di caratteri.

Se si pensa ai parametri di selezione dei tipi di carattere come coordinate in uno spazio di progettazione, il modello di stile spessore definisce un sistema di coordinate tridimensionali con peso, estensione e stile come assi. Ogni tipo di carattere di una famiglia WWS deve avere una posizione univoca definita dalle coordinate lungo questi tre assi. Per selezionare un tipo di carattere, specificare un nome di famiglia WWS e parametri di spessore, estensione e stile.

Al contrario, il modello della famiglia di caratteri tipografici ha uno spazio di progettazione N-dimensionale. Una finestra di progettazione dei tipi di carattere può definire un numero qualsiasi di assi di progettazione, ognuno identificato da un tag dell'asse a quattro caratteri. La posizione di un determinato tipo di carattere nello spazio di progettazione N-dimensionale è definita da una matrice di valori dell'asse , dove ogni valore dell'asse comprende un tag dell'asse e un valore a virgola mobile. Per selezionare un tipo di carattere, specificare un nome di famiglia tipografico e una matrice di valori dell'asse (DWRITE_FONT_AXIS_VALUE strutture).

Anche se il numero di assi del tipo di carattere è aperto, esistono alcuni assi registrati con significati standard e i valori di spessore, estensione e stile possono essere mappati ai valori dell'asse registrati. DWRITE_FONT_WEIGHT può essere mappato a un valore dell'asse "wght" (DWRITE_FONT_AXIS_TAG_WEIGHT). DWRITE_FONT_STRETCH possibile eseguire il mapping a un valore dell'asse "wdth" (DWRITE_FONT_AXIS_TAG_WIDTH). DWRITE_FONT_STYLE possono essere mappati a una combinazione di valori dell'asse "DWRITE_FONT_AXIS_TAG_ITALIC e DWRITE_FONT_AXIS_TAG_SLANT" (slnt).

Un altro asse registrato è "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Una famiglia di caratteri ottici come Sitka include tipi di carattere che differiscono lungo l'asse "opsz", ovvero sono progettati per essere usati in diverse dimensioni punto. Il modello di famiglia di caratteri WWS non ha un asse delle dimensioni ottiche, quindi la famiglia di caratteri Sitka deve essere suddivisa in più famiglie di caratteri WWS: "Sitka Small", "Sitka Text", "Sitka Subheading" e così via. Ogni famiglia di caratteri WWS corrisponde a una dimensione ottica diversa e viene lasciata all'utente per specificare il nome della famiglia WWS corretto per una determinata dimensione del carattere. Con il modello di famiglia di caratteri tipografici, l'utente può semplicemente scegliere "Sitka" e l'applicazione può impostare automaticamente il valore dell'asse "opsz" in base alle dimensioni del carattere.

Tipi di carattere tipografici e tipi di carattere variabili

Il concetto di assi di variazione è spesso associato ai tipi di carattere variabili, ma si applica anche ai tipi di carattere statici. La tabella OpenType STAT (attributi di stile) dichiara gli assi di progettazione di un tipo di carattere e i valori di tali assi. Questa tabella è necessaria per i tipi di carattere variabili, ma è rilevante anche per i tipi di carattere statici.

L'API DirectWrite espone i valori dell'asse "wght", "wdth", "axis" e "slnt" per ogni tipo di carattere, anche se non sono presenti nella tabella STAT o se non è presente alcuna tabella STAT. Questi valori sono derivati dalla tabella STAT, se possibile. In caso contrario, derivano dallo spessore del carattere, dall'estensione del carattere e dallo stile del carattere.

Gli assi dei tipi di carattere possono essere variabili o non variabili. Un tipo di carattere statico ha solo assi non variabili, mentre un tipo di carattere variabile può avere entrambi. Per utilizzare un tipo di carattere variabile, è necessario creare un tipo di carattere variabile 'istanza in cui tutti gli assi delle variabili sono stati associati a valori specifici. L'interfacciaIDWriteFontFacerappresenta un tipo di carattere statico o un'istanza particolare di un tipo di carattere variabile. È possibile creare un'istanza arbitraria di un tipo di carattere variabile con valori dell'asse specificati. Inoltre, un tipo di carattere variabile può dichiarare istanze denominate nella tabella STAT con combinazioni predefinite di valori dell'asse. Le istanze denominate consentono a un tipo di carattere variabile di comportarsi in modo analogo a una raccolta di tipi di carattere statici. Quando enumeri gli elementi di un IDWriteFontFamily o IDWriteFontSet, è presente un elemento per ogni tipo di carattere statico e per ogni istanza del tipo di carattere variabile denominata.

L'algoritmo di corrispondenza dei caratteri tipografici seleziona innanzitutto i potenziali candidati corrispondenti in base al nome della famiglia. Se i candidati corrispondenti includono tipi di carattere variabili, tutti i candidati di corrispondenza per lo stesso tipo di carattere variabile vengono compressi in un candidato di corrispondenza in cui a ogni asse delle variabili viene assegnato un valore specifico il più vicino possibile al valore richiesto per tale asse. Se non è presente alcun valore richiesto per un asse delle variabili, viene assegnato il valore predefinito per tale asse. L'ordine dei candidati di corrispondenza viene quindi determinato confrontando i valori degli assi con i valori dell'asse richiesti.

Si consideri ad esempio la famiglia tipografica Sitka in Windows. Sitka è una famiglia di caratteri ottici, ovvero ha un asse "opsz". In Windows 11 Sitka viene implementato come due tipi di carattere variabili con i valori dell'asse seguenti. Si noti che gli assi opsz e wght sono variabili, mentre gli altri assi non sono variabili.

Nome file "opsz" "wght" "wdth" "italico" "slnt"
SitkaVF.ttf 6-27.5 400-700 100 0 0
SitkaVF-Italic.ttf 6-27.5 400-700 100 1 -12

Si supponga che i valori dell'asse richiesti siano opsz:12 wght:475 wdth:100 ital:0 slnt:0. Per ogni tipo di carattere di variabile viene creato un riferimento a un tipo di carattere variabile 'istanza in cui a ogni asse delle variabili viene assegnato un valore specifico. In genere, gli assi opsz e wght vengono impostati rispettivamente su 12 e 475. In questo modo vengono restituiti i tipi di carattere corrispondenti seguenti, con il tipo di carattere non corsivo classificato per primo perché è una corrispondenza migliore per gli assi ital e slnt:

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

Nell'esempio precedente i tipi di carattere corrispondenti sono istanze arbitrarie di tipi di carattere variabili. Non esiste alcuna istanza denominata di Sitka con peso 475. Al contrario, l'algoritmo di corrispondenza weight-stretch-style restituisce solo istanze denominate.

Ordine di corrispondenza dei tipi di carattere

Esistono diversi metodi di overload GetMatchingFonts per il modello di famiglia di tipi di carattere di tipo spessore (IDWriteFontFamily::GetMatchingFonts) e il modello di famiglia di caratteri tipografico (IDWriteFontCollection2::GetMatchingFonts). In entrambi i casi, l'output è un elenco di tipi di carattere corrispondenti in ordine decrescente in base al livello di corrispondenza tra i tipi di carattere candidati e le proprietà di input. In questa sezione viene descritto come viene determinata la priorità.

Nel modello di stile spessore, i parametri di input sono il peso del carattere (DWRITE_FONT_WEIGHT), l'estensione del tipo di carattere (DWRITE_FONT_STRETCH) e lo stile del carattere (DWRITE_FONT_STYLE). L'algoritmo per trovare la corrispondenza migliore è stato documentato in un white paper del 2006 intitolato "WPF Font Selection Model" di Michael Leonov e David Brown. Vedere la sezione "Corrispondenza di un viso dall'elenco dei visi candidati". Questo documento riguardava Windows Presentation Foundation (WPF), ma DirectWrite in seguito usava lo stesso approccio.

L'algoritmo usa il concetto di vettore di attributo carattere, che per una determinata combinazione di peso, estensione e stile viene calcolato come segue:

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

Si noti che ogni coordinata vettoriale viene normalizzata sottraendo il valore "normale" per l'attributo corrispondente e moltiplicando per una costante. I moltiplicatori compensano il fatto che gli intervalli di valori di input per peso, estensione e stile sono molto diversi. In caso contrario, il peso (100..999) dominava lo stile (0,.2).

Per ogni candidato di corrispondenza, viene calcolata una distanza vettoriale e un prodotto punto tra il vettore di attributo carattere del candidato di corrispondenza e il vettore dell'attributo del tipo di carattere di input. Quando si confrontano due candidati di corrispondenza, il candidato con la distanza del vettore minore è la corrispondenza migliore. Se le distanze sono uguali, il candidato con il prodotto più piccolo è una corrispondenza migliore. Se il prodotto punto è uguale, le distanze lungo gli assi X, Y e Z vengono confrontate in tale ordine.

Il confronto delle distanze è sufficientemente intuitivo, ma l'uso del prodotto punto come misura secondaria potrebbe richiedere una spiegazione. Si supponga che il peso di input sia semibold (600) e due pesi candidati siano neri (900) e semilight (300). La distanza di ogni peso candidato dal peso di input è la stessa, ma il peso nero si trova nella stessa direzione dall'origine (ovvero 400 o normale), quindi avrà un prodotto punto più piccolo.

L'algoritmo di corrispondenza tipografica è una generalizzazione di quella per lo stile di spessore. Ogni valore dell'asse viene considerato come una coordinata in un vettore di attributo carattere N-dimensionale. Per ogni candidato di corrispondenza, viene calcolata una distanza vettoriale e un prodotto punto tra il vettore di attributo carattere del candidato di corrispondenza e il vettore dell'attributo del tipo di carattere di input. Il candidato con la distanza del vettore più piccola è la corrispondenza migliore. Se le distanze sono uguali, il candidato con il prodotto più piccolo è una corrispondenza migliore. Se il prodotto punto è uguale, la presenza in una famiglia di stili di spessore può essere utilizzata come tie-breaker.

Per calcolare la distanza vettoriale e il prodotto punto, il vettore dell'attributo carattere di un candidato di corrispondenza e il vettore dell'attributo del tipo di carattere di input devono avere gli stessi assi. Pertanto, qualsiasi valore dell'asse mancante in entrambi i vettori viene compilato sostituendo il valore standard per tale asse. Le coordinate vettoriali vengono normalizzate sottraendo il valore standard (o "normale") per l'asse corrispondente e moltiplicando il risultato per un moltiplicatore specifico dell'asse. Di seguito sono riportati i moltiplicatori e i valori standard per ogni asse:

Asse Moltiplicatore Valore standard
"wght" 5 400
"wdth" 55 100
"italico" 1400 0
"slnt" 35 0
"opsz" 1 12
Altro 1 0

I moltiplicatori sono coerenti con quelli usati dall'algoritmo di tipo weight-stretch, ma ridimensionati in base alle esigenze. Ad esempio, la larghezza normale è 100, che equivale all'estensione 5. Questo produce un moltiplicatore di 55 rispetto a 1100. L'attributo di stile legacy (0..2) entangles corsivo e obliquazione, che nel modello tipografico viene scomposto in un asse "axis" (0..1) e un asse "slnt" (-90..90). I moltiplicatori scelti per questi due assi danno risultati equivalenti all'algoritmo legacy se si presuppone un'inclinazione di 20 gradi predefinita per i tipi di carattere obliquo.

Selezione caratteri tipografici e dimensioni ottiche

Un'applicazione che usa il modello di famiglia di caratteri tipografici può implementare il ridimensionamento ottico specificando un valore dell'asse opsz come parametro di selezione del tipo di carattere. Ad esempio, un'applicazione di elaborazione delle parole potrebbe specificare un valore dell'asse opsz uguale alla dimensione del carattere espressa in punti. In questo caso, un utente potrebbe selezionare "Sitka" come famiglia di caratteri e l'applicazione seleziona automaticamente un'istanza di Sitka con il valore dell'asse opsz corretto. Nel modello WWS ogni dimensione ottica viene esposta come nome di famiglia diverso e spetta all'utente selezionare quella giusta.

In teoria, si potrebbe implementare il ridimensionamento ottico automatico nel modello di stile spessore sovrascrivendo il valore dell'asse opsz come passaggio separato dopo selezione dei tipi di carattere. Tuttavia, questa operazione funziona solo se il primo tipo di carattere corrispondente è un tipo di carattere variabile con una variabile opsz asse. Specificare opsz come parametro di selezione dei tipi di carattere funziona altrettanto bene per i tipi di carattere statici. Ad esempio, la famiglia di caratteri Sitka viene implementata come tipi di carattere variabili in Windows 11, ma come raccolta di tipi di carattere statici in Windows 10. I tipi di carattere statici hanno intervalli di asse diversi e non opsz sovrapposti (questi sono dichiarati come intervalli per la selezione dei tipi di carattere, ma non sono assi variabili). Se si specifica opsz come parametro di selezione del tipo di carattere, è possibile selezionare il tipo di carattere statico corretto per le dimensioni ottiche.

Vantaggi della selezione dei tipi di carattere tipografico e problemi di compatibilità

Il modello di selezione dei tipi di carattere tipografico presenta diversi vantaggi rispetto ai modelli precedenti, ma nella sua forma pura presenta alcuni potenziali problemi di compatibilità. Questa sezione descrive i vantaggi e i problemi di compatibilità. La sezione successiva descrive un modello di selezione dei tipi di carattere ibrido che mantiene i vantaggi riducendo al contempo i problemi di compatibilità.

I vantaggi del modello di famiglia di caratteri tipografici sono:

  • I tipi di carattere possono essere raggruppati in famiglie come previsto dalla finestra di progettazione, invece di essere suddivisi in sottofamili a causa delle limitazioni del modello di famiglia di caratteri.

  • Un'applicazione può selezionare automaticamente il valore corretto dell'asse opsz in base alle dimensioni del carattere, invece di esporre diverse dimensioni ottiche all'utente come famiglie di caratteri diverse.

  • È possibile selezionare istanze arbitrarie di tipi di carattere variabili. Ad esempio, se un tipo di carattere variabile supporta pesi nell'intervallo continuo da 100 a 900, il modello tipografico può selezionare qualsiasi peso in questo intervallo. I modelli della famiglia di caratteri meno recenti possono scegliere solo il peso più vicino tra le istanze denominate definite dal tipo di carattere.

I problemi di compatibilità con il modello di selezione dei tipi di carattere tipografico sono:

  • Alcuni tipi di carattere meno recenti non possono essere selezionati in modo univoco usando solo i valori del nome e dell'asse della famiglia tipografica.

  • I documenti esistenti possono fare riferimento ai tipi di carattere in base al nome della famiglia WWS o al nome della famiglia RBIZ. Gli utenti potrebbero anche aspettarsi di usare nomi di famiglia WWS e RBIZ. Ad esempio, un documento potrebbe specificare "Sitka Subheading" (nome della famiglia WWS) anziché "Sitka" (un nome di famiglia tipografico).

  • Una libreria o un framework potrebbe adottare il modello di famiglia di caratteri tipografico per sfruttare il ridimensionamento ottico automatico, ma non fornire un'API per specificare valori arbitrari dell'asse. Anche se viene fornita una nuova API, il framework potrebbe dover usare le applicazioni esistenti che specificano solo parametri di spessore, estensione e stile.

Il problema di compatibilità con i tipi di carattere meno recenti si verifica perché il concetto di nome della famiglia tipografica precede il concetto di valori dell'asse dei caratteri, introdotti insieme ai tipi di carattere variabili in OpenType 1.8. Prima di OpenType 1.8, il nome della famiglia tipografica esprimeva semplicemente l'intento della finestra di progettazione che un set di tipi di carattere era correlato, ma senza alcuna garanzia che tali tipi di carattere potessero essere differenziati a livello di codice in base alle relative proprietà. Come esempio ipotetico, si supponga che tutti i tipi di carattere seguenti abbiano il nome di famiglia tipografico "Legacy":

Nome completo Famiglia WWS Peso Stendere Stile Famiglia di errori di digitatura wght wdth Ital slnt
Eredità Eredità 400 5 0 Eredità 400 100 0 0
Grassetto legacy Eredità 700 5 0 Eredità 700 100 0 0
Nero legacy Eredità 900 5 0 Eredità 900 100 0 0
Legacy Soft Legacy Soft 400 5 0 Eredità 400 100 0 0
Legacy Soft Bold Legacy Soft 700 5 0 Eredità 700 100 0 0
Legacy Soft Black Legacy Soft 900 5 0 Eredità 900 100 0 0

La famiglia tipografica "Legacy" ha tre pesi e ogni peso ha varianti regolari e "Soft". Se fossero nuovi tipi di carattere, potrebbero essere implementati come dichiarazione di un asse di progettazione SOFT. Tuttavia, questi tipi di carattere precedono OpenType 1.8, quindi gli unici assi di progettazione sono quelli derivati da peso, estensione e stile. Per ogni peso, questa famiglia di caratteri ha due tipi di carattere con valori di asse identici, quindi non è possibile selezionare in modo univoco un tipo di carattere in questa famiglia usando solo i valori dell'asse.

Algoritmo di selezione dei tipi di carattere ibrido

Le API di selezione dei tipi di carattere descritte nella sezione successiva usano un algoritmo di selezione dei tipi di carattere ibrido che mantiene i vantaggi della selezione dei caratteri tipografici riducendo al contempo i problemi di compatibilità.

La selezione dei tipi di carattere ibrida fornisce un bridge da modelli di famiglia di caratteri meno recenti, consentendo il mapping dei valori di spessore, estensione del carattere e stile del carattere ai valori dell'asse dei caratteri corrispondenti. Ciò consente di risolvere i problemi di compatibilità dei documenti e delle applicazioni.

Inoltre, l'algoritmo di selezione del tipo di carattere ibrido consente al nome della famiglia specificato di essere un nome di famiglia tipografico, un nome di famiglia di stile spessore, un nome di famiglia GDI/RBIZ o un nome completo del tipo di carattere. La corrispondenza si verifica in uno dei modi seguenti, in ordine decrescente di priorità:

  1. Il nome corrisponde a una famiglia tipografica, ad esempio Sitka. La corrispondenza si verifica all'interno della famiglia tipografica e vengono usati tutti i valori dell'asse richiesti. Se il nome corrisponde anche a una sottofamiglia WWS (ovvero una più piccola della famiglia tipografica), l'appartenenza alla sottofamiglia WWS viene usata come tie-breaker.

  2. Il nome corrisponde a una famiglia WWS (ad esempio, Sitka Text). La corrispondenza si verifica all'interno della famiglia WWS e i valori dell'asse richiesti diversi da "wght", "wdth", "axis" e "slnt" vengono ignorati.

  3. Il nome corrisponde a una famiglia GDI (ad esempio, Bahn inserita nel condensato). La corrispondenza si verifica all'interno della famiglia RBIZ e i valori dell'asse richiesti diversi da "wght", "axis" e "slnt" vengono ignorati.

  4. Il nome corrisponde a un nome completo(ad esempio, Bahn windows Bold Condensed). Viene restituito il tipo di carattere corrispondente al nome completo. I valori dell'asse richiesti vengono ignorati. La corrispondenza in base al nome completo del tipo di carattere è consentita perché GDI la supporta.

La sezione precedente descriveva una famiglia tipografica ambigua denominata "Legacy". L'algoritmo ibrido consente di evitare l'ambiguità specificando "Legacy" o "Legacy Soft" come nome della famiglia. Se si specifica "Legacy Soft", non c'è ambiguità perché la corrispondenza si verifica solo all'interno della famiglia WWS. Se "Legacy" è specificato, tutti i tipi di carattere della famiglia tipografica vengono considerati candidati corrispondenti, ma l'ambiguità viene evitata usando l'appartenenza alla famiglia WWS "Legacy" come tie-breaker.

Si supponga che un documento specifichi un nome di famiglia e un peso, un tratto e parametri di stile, ma non valori dell'asse. L'applicazione può prima convertire il peso, l'estensione, lo stile e le dimensioni del carattere in valori dell'asse chiamando IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. L'applicazione può quindi passare sia il nome della famiglia che i valori dell'asse a IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts restituisce un elenco di tipi di carattere corrispondenti in ordine di priorità e il risultato è appropriato se il nome della famiglia specificato è un nome di famiglia tipografico, un nome di famiglia di tipo weight-stretch, un nome di famiglia RBIZ o un nome completo. Se la famiglia specificata ha un asse "opsz", la dimensione ottica appropriata viene selezionata automaticamente in base alle dimensioni del carattere.

Si supponga che un documento specifichi il peso, l'estensione e lo stile e anche specifica i valori dell'asse. In tal caso, i valori espliciti dell'asse possono anche essere passati a IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValuese i valori dell'asse derivati restituiti dal metodo includeranno solo gli assi del tipo di carattere non specificati in modo esplicito. Pertanto, i valori dell'asse specificati in modo esplicito dal documento (o dall'applicazione) hanno la precedenza sui valori dell'asse derivati dal peso, dall'estensione, dallo stile e dalle dimensioni del carattere.

API di selezione dei tipi di carattere ibrido

Il modello di selezione dei tipi di carattere ibrido viene implementato dai metodi di IDWriteFontSet4 seguenti:

  • Il metodo IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues converte i parametri di dimensioni, spessore, estensione e stile dei tipi di carattere nei valori dell'asse corrispondenti. Tutti i valori espliciti dell'asse passati dal client vengono esclusi dai valori dell'asse derivati.

  • Il metodo IDWriteFontSet4::GetMatchingFonts restituisce un elenco con priorità dei tipi di carattere corrispondenti in base al nome della famiglia e alla matrice di valori dell'asse. Come descritto in precedenza, il parametro family name può essere un nome di famiglia tipografico, il nome della famiglia WWS, il nome della famiglia RBIZ o il nome completo. Il nome completo identifica uno stile di carattere specifico, ad esempio "Arial Bold Italic". GetMatchingFonts supporta la corrispondenza con il nome completo per maggiore comaptibiltiy con GDI, che lo consente anche.

Le altre API DirectWrite seguenti usano anche l'algoritmo di selezione dei tipi di carattere ibrido:

Esempi di codice delle API di selezione dei tipi di carattere in uso

Questa sezione illustra un'applicazione console completa che illustra il IDWriteFontSet4::GetMatchingFonts e i metodi IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Prima di tutto sono incluse alcune intestazioni:

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

Il metodoIDWriteFontSet4::GetMatchingFontsrestituisce un elenco di tipi di carattere in ordine di priorità che corrispondono ai valori del nome e dell'asse della famiglia specificati. Di seguito La funzione MatchAxisValues restituisce i parametri in IDWriteFontSet4::GetMatchingFonts e l'elenco dei tipi di carattere corrispondenti nel set di tipi di carattere restituito.

// 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;
}

Un'applicazione può avere parametri di spessore, estensione e stile anziché valori dell'asse (o oltre ai valori dell'asse). Ad esempio, l'applicazione potrebbe dover usare documenti che fanno riferimento ai tipi di carattere usando parametri RBIZ o di tipo spessore. Anche se l'applicazione aggiunge il supporto per specificare valori arbitrari dell'asse, potrebbe essere necessario supportare anche i parametri meno recenti. A tale scopo, l'applicazione può chiamare IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues prima di chiamare IDWriteFontSet4::GetMatchingFonts.

Il funzione MatchFont seguente accetta parametri di spessore, estensione, stile e dimensioni del carattere oltre ai valori dell'asse. Inoltra questi parametri al metodo IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues per calcolare i valori dell'asse derivati, accodati ai valori dell'asse di input. Passa i valori dell'asse combinati alla funzione MatchAxisValues precedente.

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);
}

La funzione seguente illustra i risultati della chiamata alla funzione MatchFont precedente con alcuni input di esempio:

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);
}

Di seguito è riportato l'output della funzione 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

Di seguito sono riportate le implementazioni degli operatori di overload dichiarati in precedenza. Questi valori vengono usati da MatchAxisValues per scrivere i valori dell'asse di input e i riferimenti ai visi del carattere risultanti:

// 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;
}

Per completare l'esempio, di seguito sono riportate le funzioni di analisi della riga di comando e la funzione principale:

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());
    }
}