通过功能文件定义初始变体

通过功能文件定义初始变体
\documentclass{article}

\usepackage{fontspec,filecontents}

\begin{filecontents*}{junicode.fea}
languagesystem DFLT dflt;
languagesystem latn dflt;

feature calt {
    sub \beta by \uni03D0;
} calt;
\end{filecontents*}

\setmainfont[FeatureFile={junicode.fea}]{junicode}

\begin{document}
βιβλίον\par
\addfontfeature{Contextuals=Alternate}
βιβλίον
\end{document}

生产

在此处输入图片描述

是否可以改变替换,以便只在单词内部使用 slim beta,而将普通 beta 保留在单词开头?即,以便我们得到以下内容?

在此处输入图片描述

答案1

这可以通过上下文功能实现。该类@greekletter定义所有希腊字母(示例仍需完成),然后希腊字母后的 beta 被其细长形式替换:

\documentclass{article}

\usepackage{fontspec,filecontents}

\begin{filecontents*}{junicode.fea}
languagesystem DFLT dflt;
languagesystem latn dflt;

# The list of Greek letters is now almost complete (some ligatures missing).
@greekletter = [
    Alpha Alphatonos Beta Chi Delta Epsilon Epsilontonos Eta Etatonos
    Gamma Iota Iotadieresis Iotatonos Kappa Lambda Mu Nu Omega
    Omegatonos Omicron Omicrontonos Phi Pi Psi Rho Sigma Tau Theta
    Upsilon Upsilondieresis Upsilontonos Xi Zeta alpha alphatonos beta
    chi delta epsilon epsilontonos eta etatonos gamma iota iotadieresis
    iotadieresistonos iotatonos kappa lambda mu nu omega omega1
    omegatonos omicron omicrontonos phi phi1 pi psi rho sigma sigma1 tau
    theta theta1 uni03D0 uni03D7-uni03D9 uni03DA-uni03DD uni03E0 uni03E1
    uni03F0 uni03F1 uni1F00-uni1F09 uni1F0A-uni1F0F uni1F10-uni1F15
    uni1F18 uni1F19 uni1F1A-uni1F1D uni1F20-uni1F29 uni1F2A-uni1F2F
    uni1F30-uni1F39 uni1F3A-uni1F3F uni1F40-uni1F45 uni1F48 uni1F49
    uni1F4A-uni1F4D uni1F50-uni1F57 uni1F59 uni1F5B uni1F5D uni1F5F
    uni1F60-uni1F69 uni1F6A-uni1F6F uni1F70-uni1F79 uni1F7A-uni1F7D
    uni1F80-uni1F89 uni1F8A-uni1F8F uni1F90-uni1F99 uni1F9A-uni1F9F
    uni1FA0-uni1FA9 uni1FAA-uni1FAF uni1FB0-uni1FB4 uni1FB6-uni1FB9
    uni1FBA-uni1FBC uni1FBE uni1FC2-uni1FC4 uni1FC6-uni1FC9
    uni1FCA-uni1FCC uni1FD0-uni1FD3 uni1FD6-uni1FD9 uni1FDA uni1FDB
    uni1FE0-uni1FE9 uni1FEA-uni1FEC uni1FF2-uni1FF4 uni1FF6-uni1FF9
    uni1FFA-uni1FFC upsilon upsilondieresis upsilondieresistonos
    upsilontonos xi zeta
];
feature calt {
    sub @greekletter beta' by uni03D0;
} calt;
\end{filecontents*}

\setmainfont[FeatureFile={junicode.fea}]{junicode}

\begin{document}
βιβλίον\par
\addfontfeature{Contextuals=Alternate}
βιβλίον\par
βιβββλίον\par
Αβ
\end{document}

结果

字体的希腊字母列表Junicode.ttf可以通过编程生成:

  • Unicode 为希腊字母定义了两个块(Blocks.txt

    0370..03FF; Greek and Coptic
    1F00..1FFF; Greek Extended
    
  • 此外,Unicode 还定义了代码点的类别,即大写字母和小写字母,例如:Ω 有Lu大写字母。我们只需要第一个字母即可将其分类为字母。

  • FontForge 可以访问字形名称及其 Unicode 代码点,并且可以通过其 Python 绑定进行脚本编写。

以下 Python 脚本首先从 Unicode 标准中收集希腊字母。然后它调用 FontForge 并尝试找到这些字母的字形名称。

排序后的字形名称将被格式化并打印出来,以便包含在特征文件中。格式化是通过根据特征文件的规则将连续的字形名称折叠到范围来优化的(请参阅“2.gi 范围”一节)。OpenType 特征文件规范)。

#!/usr/bin/env python2
import fontforge
import re
import subprocess
import unicodedata

# Unicode ranges for Greek letters
greek_unicode_range = range(0x370, 0x3ff) + range(0x1f00, 0x1fff)

# Filter the unicode ranges for letters
greek_unicode_letters = []
for slot in greek_unicode_range:
    category = unicodedata.category(unichr(slot))
    if category[0] == 'L':
        greek_unicode_letters.append(slot)

# Get font file
font_name = subprocess.check_output(['kpsewhich', 'Junicode.ttf']).rstrip()
# print('Font: ' + font_name)

# Load font in fontforge
font = fontforge.open(font_name)

# Get all glyphs from the font
glyphs = [glyph
          for glyph in font.selection.byGlyphs.all()
          if glyph.isWorthOutputting]

glyph_names = [glyph.glyphname
               for glyph in glyphs
               if glyph.unicode in greek_unicode_letters]

# Unformatted output:
# glyph_names.sort()
# print(' '.join(glyph_names))

# Formatted output with ranges

def is_next_in_line(a, b):
    test_string = a + '-' + b
    match = is_next_in_line.letter_pattern.match(test_string)
    if match:
        a_num = ord(match.group(2))
        b_num = ord(match.group(4))
        signature = match.group(1) + '-' + match.group(3)
        if a_num + 1 == b_num:
            return (True, signature)
    match = is_next_in_line.number_pattern.match(test_string)
    if match:
        a_num = int(match.group(2))
        b_num = int(match.group(4))
        signature = match.group(1) + '-' + match.group(3)
        if a_num + 1 == b_num:
            return (True, signature)
    return (False, None)
is_next_in_line.letter_pattern = re.compile(
    r'(.*)([A-Za-z])(.*)-\1([A-Za-z])\3$')
is_next_in_line.number_pattern = re.compile(
    r'(.*)([0-9]{1,3})(.*)-\1([0-9]{1,3})\3$')

def make_range(names):
    if len(names) <= 2:
        return names
    return [names[0] + '-' + names[-1]]

def compact_names_with_same_length(names):
    result = []
    tmp = []
    tmp_signature = None
    for name in names:
        if len(tmp) == 0:
            tmp.append(name)
            continue
        (is_consecutive, signature) = is_next_in_line(tmp[-1], name)
        if is_consecutive:
            if tmp_signature is None:
                tmp_signature = signature
                tmp.append(name)
                continue
            if tmp_signature == signature:
                tmp.append(name)
                continue
        result += make_range(tmp)
        tmp = [name]
        tmp_signature = None
    result += make_range(tmp)
    return result

def compact_names(names):
    """Replaces three and more consecutive elements by ranges."""
    names.sort()
    result = []
    current_length = 0
    tmp = []
    for name in names:
        if len(tmp) == 0:
            tmp.append(name)
            current_length = len(name)
            continue
        length = len(name)
        if length == current_length:
            tmp.append(name)
            continue
        result += compact_names_with_same_length(tmp)
        tmp = [name]
        current_length = length
    result += compact_names_with_same_length(tmp)
    return result

names = compact_names(glyph_names)
lines = []
line = ''
max_length = 68
for name in names:
    if len(line) == 0:
        line = name
        continue
    if len(line) + 1 + len(name) <= max_length:
        line += ' ' + name
        continue
    lines.append(line)
    line = name
lines.append(line)

print('@greekletter = [')
for line in lines:
    print('    ' + line)
print('];')

结果:

@greekletter = [
    Alpha Alphatonos Beta Chi Delta Epsilon Epsilontonos Eta Etatonos
    Gamma Iota Iotadieresis Iotatonos Kappa Lambda Mu Nu Omega
    Omegatonos Omicron Omicrontonos Phi Pi Psi Rho Sigma Tau Theta
    Upsilon Upsilondieresis Upsilontonos Xi Zeta alpha alphatonos beta
    chi delta epsilon epsilontonos eta etatonos gamma iota iotadieresis
    iotadieresistonos iotatonos kappa lambda mu nu omega omega1
    omegatonos omicron omicrontonos phi phi1 pi psi rho sigma sigma1 tau
    theta theta1 uni03D0 uni03D7-uni03D9 uni03DA-uni03DD uni03E0 uni03E1
    uni03F0 uni03F1 uni1F00-uni1F09 uni1F0A-uni1F0F uni1F10-uni1F15
    uni1F18 uni1F19 uni1F1A-uni1F1D uni1F20-uni1F29 uni1F2A-uni1F2F
    uni1F30-uni1F39 uni1F3A-uni1F3F uni1F40-uni1F45 uni1F48 uni1F49
    uni1F4A-uni1F4D uni1F50-uni1F57 uni1F59 uni1F5B uni1F5D uni1F5F
    uni1F60-uni1F69 uni1F6A-uni1F6F uni1F70-uni1F79 uni1F7A-uni1F7D
    uni1F80-uni1F89 uni1F8A-uni1F8F uni1F90-uni1F99 uni1F9A-uni1F9F
    uni1FA0-uni1FA9 uni1FAA-uni1FAF uni1FB0-uni1FB4 uni1FB6-uni1FB9
    uni1FBA-uni1FBC uni1FBE uni1FC2-uni1FC4 uni1FC6-uni1FC9
    uni1FCA-uni1FCC uni1FD0-uni1FD3 uni1FD6-uni1FD9 uni1FDA uni1FDB
    uni1FE0-uni1FE9 uni1FEA-uni1FEC uni1FF2-uni1FF4 uni1FF6-uni1FF9
    uni1FFA-uni1FFC upsilon upsilondieresis upsilondieresistonos
    upsilontonos xi zeta
];

相关内容