• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 8288578143

14 Mar 2024 09:39PM UTC coverage: 64.155% (-10.6%) from 74.718%
8288578143

Pull #1904

github

alerque
chore(core): Fixup ec6ed657 which didn't shim old pack styles properly
Pull Request #1904: Merge develop into master (commit to next release being breaking)

1648 of 2421 new or added lines in 107 files covered. (68.07%)

1843 existing lines in 77 files now uncovered.

10515 of 16390 relevant lines covered (64.15%)

3306.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/features/init.lua
UNCOV
1
local base = require("packages.base")
×
2

UNCOV
3
local package = pl.class(base)
×
UNCOV
4
package._name = "features"
×
5

UNCOV
6
local lpeg = require("lpeg")
×
7

UNCOV
8
local R, S, P, C = lpeg.R, lpeg.S, lpeg.P, lpeg.C
×
UNCOV
9
local Cf, Ct = lpeg.Cf, lpeg.Ct
×
10

UNCOV
11
local otFeatureMap = {
×
UNCOV
12
  Ligatures = {
×
13
    Required = "rlig",
14
    Common = "liga",
15
    Contextual = "clig",
16
    Rare = "dlig",
17
    Discretionary = "dlig",
18
    Historic = "hlig"
×
19
  },
UNCOV
20
  Fractions = {
×
21
    On = "frac",
22
    Alternate = "afrc"
×
23
  },
24
  StylisticSet = function (i)
25
    return string.format("ss%02i", tonumber(i))
×
26
  end,
27
  CharacterVariant = function (i)
UNCOV
28
    return string.format("cv%02i", tonumber(i))
×
29
  end,
UNCOV
30
  Letters = {
×
31
    Uppercase = "case",
32
    SmallCaps = "smcp",
33
    PetiteCaps = "pcap",
34
    UppercaseSmallCaps = "c2sc",
35
    UppercasePetiteCaps = "c2pc",
36
    Unicase = "unic"
×
37
  },
UNCOV
38
  Numbers = {
×
39
    Uppercase = "lnum",
40
    Lining = "lnum",
41
    LowerCase = "onum",
42
    OldStyle = "onum",
43
    Proportional = "pnum",
44
    monospaced = "tnum",
45
    SlashedZero = "zero",
46
    Arabic = "anum"
×
47
  },
UNCOV
48
  Contextuals = {
×
49
    Swash = "cswh",
50
    Alternate = "calt",
51
    WordInitial = "init",
52
    WordFinal = "fina",
53
    LineFinal = "falt",
54
    Inner = "medi"
×
55
  },
UNCOV
56
  VerticalPosition = {
×
57
    Superior = "sups",
58
    Inferior = "subs",
59
    Numerator = "numr",
60
    Denominator = "dnom",
61
    ScientificInferior = "sinf",
62
    Ordinal = "ordn"
×
63
  },
UNCOV
64
  Style = {
×
65
    Alternate = "salt",
66
    Italic = "ital",
67
    Ruby = "ruby",
68
    Swash = "swsh",
69
    Historic = "hist",
70
    TitlingCaps = "titl",
71
    HorizontalKana = "hkna",
72
    VerticalKana = "vkna"
×
73
  },
UNCOV
74
  Diacritics = {
×
75
    MarkToBase = "mark",
76
    MarkToMark = "mkmk",
77
    AboveBase = "abvm",
78
    BelowBase = "blwm"
×
79
  },
UNCOV
80
  Kerning = {
×
81
    Uppercase = "cpsp",
82
    On = "kern"
×
83
  },
UNCOV
84
  CJKShape = {
×
85
    Traditional = "trad",
86
    Simplified = "smpl",
87
    JIS1978 = "jp78",
88
    JIS1983 = "jp83",
89
    JIS1990 = "jp90",
90
    Expert = "expt",
91
    NLC = "nlck"
×
92
  },
UNCOV
93
  CharacterWidth = {
×
94
    Proportional = "pwid",
95
    Full = "fwid",
96
    Half = "hwid",
97
    Third = "twid",
98
    Quarter = "qwid",
99
    AlternateProportional = "palt",
100
    AlternateHalf = "halt"
×
101
  }
102
}
103

104
local function tagpos (pos, k, v)
UNCOV
105
  return k, { posneg = pos, value = v }
×
106
end
107

108
-- Parser for feature strings
UNCOV
109
local featurename = C((1 - S",;:=")^1)
×
UNCOV
110
local value = C(SILE.parserBits.integer)
×
UNCOV
111
local tag = C(S"+-") * featurename * (P"=" * value)^0 * S",;:"^-1 / tagpos
×
UNCOV
112
local featurestring = Cf(Ct"" * tag^0, rawset)
×
113

114
-- Parser for fontspec strings
115
-- Refer to fontspec.pdf (see doc), Chapter 3, Table 4 (p. 37)
UNCOV
116
local fontspecsafe = R("AZ", "az", "09") + P":"
×
UNCOV
117
local ws = SILE.parserBits.ws
×
UNCOV
118
local fontspecsep = P"," * ws
×
UNCOV
119
local fontspecname = C(fontspecsafe^1)
×
UNCOV
120
local fontspeclist = ws * P"{" *
×
UNCOV
121
                     Ct(ws * fontspecname *
×
UNCOV
122
                        (fontspecsep * fontspecname * ws)^0) *
×
UNCOV
123
                     P"}" * ws
×
124

UNCOV
125
local otFeatures = pl.class(pl.Map)
×
126

UNCOV
127
function otFeatures:_init ()
×
UNCOV
128
  self:super()
×
UNCOV
129
  local str = SILE.settings:get("font.features")
×
UNCOV
130
  local tbl = featurestring:match(str)
×
UNCOV
131
  if not tbl then
×
132
    SU.error("Unparsable Opentype feature string '"..str.."'")
×
133
  end
UNCOV
134
  for feat, flag in pairs(tbl) do
×
UNCOV
135
    self:set(feat, flag.posneg == "+")
×
136
  end
137
end
138

UNCOV
139
function otFeatures:__tostring ()
×
UNCOV
140
  local ret = {}
×
UNCOV
141
  for _, f in ipairs(self:items()) do
×
UNCOV
142
    ret[#ret+1] = (f[2] and "+" or "-") .. f[1]
×
143
  end
UNCOV
144
  return table.concat(ret, ";")
×
145
end
146

UNCOV
147
function otFeatures:loadOption (name, val, invert)
×
UNCOV
148
  local posneg = not invert
×
UNCOV
149
  local key = otFeatureMap[name]
×
UNCOV
150
  if not key then
×
151
    SU.warn("Unknown OpenType feature " .. name)
×
152
  else
UNCOV
153
    local matches = lpeg.match(fontspeclist, val)
×
UNCOV
154
    for _, v in ipairs(matches or { val }) do
×
UNCOV
155
      v = v:gsub("^No", function () posneg = false; return "" end)
×
UNCOV
156
      local feat = type(key) == "function" and key(v) or key[v]
×
UNCOV
157
      if not feat then
×
158
        SU.warn("Bad OpenType value " .. v .. " for feature " .. name)
×
159
      else
UNCOV
160
        self:set(feat, posneg)
×
161
      end
162
    end
163
  end
164
end
165

166
-- Input like {Ligatures = Historic} or {Ligatures = "{Historic, Discretionary}"}
167
--
168
-- Most real-world use should be single value, but multiple value use is not
169
-- that odd.  Junicode, for example, a common font among medievalists, has many
170
-- Stylistic Sets and Character Variations, many of which make sense to enable
171
-- simultaneously.
UNCOV
172
function otFeatures:loadOptions (options, invert)
×
UNCOV
173
  SU.debug("features", "Features was", self)
×
UNCOV
174
  for k, v in pairs(options) do
×
UNCOV
175
    self:loadOption(k, v, invert)
×
176
  end
UNCOV
177
  SU.debug("features", "Features interpreted as", self)
×
178
end
179

UNCOV
180
function otFeatures:unloadOptions (options)
×
UNCOV
181
  self:loadOptions(options, true)
×
182
end
183

UNCOV
184
local fontfn = SILE.Commands.font
×
185

UNCOV
186
function package:registerCommands ()
×
187

UNCOV
188
  self:registerCommand("add-font-feature", function (options, _)
×
UNCOV
189
    local otfeatures = otFeatures()
×
UNCOV
190
    otfeatures:loadOptions(options)
×
UNCOV
191
    SILE.settings:set("font.features", tostring(otfeatures))
×
192
  end)
193

UNCOV
194
  self:registerCommand("remove-font-feature", function(options, _)
×
UNCOV
195
    local otfeatures = otFeatures()
×
UNCOV
196
    otfeatures:unloadOptions(options)
×
UNCOV
197
    SILE.settings:set("font.features", tostring(otfeatures))
×
198
  end)
199

UNCOV
200
  self:registerCommand("font", function (options, content)
×
UNCOV
201
    local otfeatures = otFeatures()
×
202
    -- It is guaranteed that future releases of SILE will not implement non-OT \font
203
    -- features with capital letters.
204
    -- Cf. https://github.com/sile-typesetter/sile/issues/992#issuecomment-665575353
205
    -- So, we reserve 'em all. ⍩⃝
UNCOV
206
    for k, v in pairs(options) do
×
UNCOV
207
      if k:match('^[A-Z]') then
×
UNCOV
208
        otfeatures:loadOption(k, v)
×
UNCOV
209
        options[k] = nil
×
210
      end
211
    end
UNCOV
212
    SU.debug("features", "Font features parsed as:", otfeatures)
×
UNCOV
213
    options.features = (options.features and options.features .. ";" or "") .. tostring(otfeatures)
×
UNCOV
214
    return fontfn(options, content)
×
UNCOV
215
  end, tostring(SILE.Help.font) .. " (overridden)")
×
216

217
end
218

219
package.documentation = [[
220
\begin{document}
221
SILE automatically applies ligatures defined by the fonts that you use.
222
These ligatures are defined by tables of \em{features} within the font file.
223
As well as ligatures (multiple glyphs displayed as a single glyph), the features tables also declare other glyph substitutions.
224

225
The standard \autodoc:command{\font} command provides an interface to selecting the features that you want SILE to apply to a font.
226
The features available will be specific to the font file; some fonts come with documentation explaining their supported features.
227
Discussion of OpenType features is beyond the scope of this manual.
228

229
These features can be turned on and off by passing “raw” feature names to the \autodoc:command{\font} command like so:
230

231
\begin[type=autodoc:codeblock]{raw}
232
\font[features="+dlig,+hlig"]{...} % turn on discretionary and historic ligatures
233
\end{raw}
234

235
However, this is unwieldy and requires memorizing the feature codes.
236

237
The \autodoc:package{features} package provides two commands, \autodoc:command{\add-font-feature} and \autodoc:command{\remove-font-feature}, which make it easier to access OpenType features.
238
The interface is patterned on the TeX package \code{fontspec}; for full documentation of the OpenType features supported, see the documentation for that package.\footnote{\code{http://texdoc.net/texmf-dist/doc/latex/fontspec/fontspec.pdf}}
239

240
Here is how you would turn on discretionary and historic ligatures with the \autodoc:package{features} package:
241

242
\begin[type=autodoc:codeblock]{raw}
243
\add-font-feature[Ligatures=Rare]\add-font-feature[Ligatures=Discretionary]
244
...
245
\remove-font-feature[Ligatures=Rare]\remove-font-feature[Ligatures=Discretionary]
246
\end{raw}
247
\end{document}
UNCOV
248
]]
×
249

UNCOV
250
return package
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc