[Tex/LaTex] How to tell fontspec to load a font using a specific glyph variant not available as a CharacterVariant

fontsfontspecopentypexetex

I would like to use OpenType Vollkorn with xetex but its "1" causes me problems. It has an alternate glyph (whose glyph name is "one.ss01"), which I can trigger via StylisticSet={1}. But this also changes other characters:

otfinfo -g Vollkorn-Regular.otf | grep "ss01"

J.ss01
Jcircumflex.ss01
Q.ss01
uni1E9E.ss01
a.ss01
aacute.ss01
abreve.ss01
acircumflex.ss01
adieresis.ss01
agrave.ss01
amacron.ss01
aogonek.ss01
aring.ss01
atilde.ss01
g.ss01
gbreve.ss01
gcircumflex.ss01
gcommaaccent.ss01
gdotaccent.ss01
one.ss01
three.ss01
zero.lf.ss01
three.lf.ss01
seven.lf.ss01
zero.tf.ss01
three.tf.ss01
seven.tf.ss01
slash.ss01

I don't want the alternate "a". I could use locally \XeTeXglyph as in this example:

\documentclass{article}

\usepackage{fontspec}

% http://vollkorn-typeface.com/

\setmainfont{Vollkorn}[%StylisticSet={1},
ItalicFont={* Italic}, BoldFont={* Bold}, BoldItalicFont={* Bold Italic}]

% \def\VollkornFamilyName{Vollkorn(0)}
% utiliser \XeTeXglyph 381 en testant \f@family ?
% non je ne vais pas rendre le 1 actif !
% et c'est \XeTeXglyph 309 en italic

\begin{document}

1234567890

\bfseries 1234567890

\itshape 1234567890

\mdseries 1234567890

\normalfont

\XeTeXglyph 381 234567890

\bfseries \XeTeXglyph 381 234567890

% need to change glyph number

\itshape \XeTeXglyph 309 234567890

\mdseries \XeTeXglyph 309 234567890

\end{document}

% Local Variables:
% TeX-engine: xetex
% End:

vollkorn digits

But I need to apply this on a document which is generated from other sources not manually typed and it seems somewhat dangerous to make the 1 active, with the idea that it would test for the font family and font shapes,etc… and insert the suitable glyph via \XeTeXglyph or just \string1.

as expected making the 1 active had many issues, among them that \XeTeXglyph can not be used in math mode, or in a \csname — just not an option

Is there a solution for using one.ss01 glyph without activating the full StylisticSet ?

I checked the fontspec manual and discovered the notion of CharacterVariant but there is no such features in that OpenType font:

Fonts$ otfinfo -f Vollkorn-Regular.otf 
aalt    Access All Alternates
calt    Contextual Alternates
case    Case-Sensitive Forms
cpsp    Capital Spacing
dlig    Discretionary Ligatures
dnom    Denominators
frac    Fractions
kern    Kerning
liga    Standard Ligatures
lnum    Lining Figures
mark    Mark Positioning
numr    Numerators
onum    Oldstyle Figures
ordn    Ordinals
pnum    Proportional Figures
salt    Stylistic Alternates
ss01    Stylistic Set 1
sups    Superscript
tnum    Tabular Figures

Best Answer

The following Python script for FontForge creates

#!/usr/bin/env python2
import fontforge
import os.path

font_files =  [
    'Vollkorn-Bold.otf',
    'Vollkorn-BoldItalic.otf',
    'Vollkorn-Italic.otf',
    'Vollkorn-Regular.otf',
    ]

def main():
    for font_file_name in font_files:        
        make_new_font(font_file_name)

def get_file_name_stem(font_file_name):
    name_without_directory = os.path.basename(font_file_name)
    name_stem = os.path.splitext(name_without_directory)[0]
    return name_stem

def make_new_font(font_file_name):
    name_stem = get_file_name_stem(font_file_name)
    feature_file_name = name_stem + '.fea'
    new_font_file_name = name_stem + '-ss02.otf'

    print('Font file: ' + font_file_name)
    font = fontforge.open(font_file_name)
    print('Font name: ' + font.fontname)
    print('=> ' + feature_file_name)
    font.generateFeatureFile(feature_file_name)

    print('Lookup names:')
    for lookup in font.gsub_lookups:
        print('  ' + lookup)

    lookup_ss01_name = "'ss01' Style Set 1 lookup 24"
    lookup_ss02_name = "'ss02' Style Set 2 lookup 25"
    subtable_ss02_name = "'ss02' Style Set 2 lookup 25 subtable"

    (type_name,
     flags,
     feature_script_lang_tuples) = font.getLookupInfo(lookup_ss01_name)

    print('Lookup(' + lookup_ss01_name + '): ')
    print('  Type: ' + type_name)
    print('  Flags: ' + ', '.join(flags))
    print('  Feature/Script/Languages: ')
    script_lang_ss01 = None
    for (feature, script_lang) in feature_script_lang_tuples:
        print('    ' + feature + ':')
        for (script, languages) in script_lang:
            language_list = ', '.join([l.rstrip() for l in languages])
            print('      ' + script + ': ' + language_list)
        if feature == 'ss01':
            script_lang_ss01 = script_lang
    if script_lang_ss01 is None:
        raise('Feature ss01 not found in Style Set 1')
    print('  Subtables:')
    for subtable in font.getLookupSubtables(lookup_ss01_name):
        print('    ' + subtable)

    font.addLookup(lookup_ss02_name,
                   type_name,
                   flags,
                   (('ss02', script_lang_ss01),),
                   lookup_ss01_name)
    font.addLookupSubtable(lookup_ss02_name, subtable_ss02_name)

    glyph_one = [glyph for glyph in font.selection.select('one').byGlyphs][0]
    glyph_one.addPosSub(subtable_ss02_name, 'one.ss01')

    print('=> ' + new_font_file_name)
    font.generate(new_font_file_name)

if __name__ == '__main__':
    main()

It generates files Vollkorn-Regular-ss02.otf, ... with an additional stylistic set 02 for the replacement of glyph one with one.ss01 and which can be activated:

\documentclass{article}

\usepackage{fontspec}

\setmainfont{Vollkorn-Regular-ss02.otf}[
  StylisticSet=2,
  ItalicFont=Vollkorn-Italic-ss02.otf,
  BoldFont=Vollkorn-Bold-ss02.otf,
  BoldItalicFont=Vollkorn-BoldItalic-ss02.otf,
]

\begin{document}

1234567890

\bfseries 1234567890

\itshape 1234567890

\mdseries 1234567890

\end{document}

Result with XeLaTeX:

Result