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

sile-typesetter / sile / 14502192980

16 Apr 2025 08:26PM UTC coverage: 57.267% (-5.4%) from 62.627%
14502192980

push

github

alerque
chore(packages): Remove unused package interdependency, url doesn't need verbatim

Reported-by: Omikhleia <didier.willis@gmail.com>

12352 of 21569 relevant lines covered (57.27%)

871.56 hits per line

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

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

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

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

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

11
local otFeatureMap = {
×
12
   Ligatures = {
×
13
      Required = "rlig",
14
      Common = "liga",
15
      Contextual = "clig",
16
      Rare = "dlig",
17
      Discretionary = "dlig",
18
      Historic = "hlig",
19
   },
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)
28
      return string.format("cv%02i", tonumber(i))
×
29
   end,
30
   Letters = {
×
31
      Uppercase = "case",
32
      SmallCaps = "smcp",
33
      PetiteCaps = "pcap",
34
      UppercaseSmallCaps = "c2sc",
35
      UppercasePetiteCaps = "c2pc",
36
      Unicase = "unic",
37
   },
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
   },
48
   Contextuals = {
×
49
      Swash = "cswh",
50
      Alternate = "calt",
51
      WordInitial = "init",
52
      WordFinal = "fina",
53
      LineFinal = "fault",
54
      Inner = "medi",
55
   },
56
   VerticalPosition = {
×
57
      Superior = "sups",
58
      Inferior = "subs",
59
      Numerator = "numr",
60
      Denominator = "dnom",
61
      ScientificInferior = "sinf",
62
      Ordinal = "ordn",
63
   },
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
   },
74
   Diacritics = {
×
75
      MarkToBase = "mark",
76
      MarkToMark = "mkmk",
77
      AboveBase = "abvm",
78
      BelowBase = "blwm",
79
   },
80
   Kerning = {
×
81
      Uppercase = "cpsp",
82
      On = "kern",
83
   },
84
   CJKShape = {
×
85
      Traditional = "trad",
86
      Simplified = "smpl",
87
      JIS1978 = "jp78",
88
      JIS1983 = "jp83",
89
      JIS1990 = "jp90",
90
      Expert = "expt",
91
      NLC = "nlck",
92
   },
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)
105
   return k, { posneg = pos, value = v }
×
106
end
107

108
-- stylua: ignore start
109

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

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

127
local otFeatures = pl.class(pl.Map)
×
128

129
-- stylua: ignore end
130

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

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

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

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

187
function otFeatures:unloadOptions (options)
×
188
   self:loadOptions(options, true)
×
189
end
190

191
local fontfn = SILE.Commands.font
×
192

193
function package:registerCommands ()
×
194
   self:registerCommand("add-font-feature", function (options, _)
×
195
      local otfeatures = otFeatures()
×
196
      otfeatures:loadOptions(options)
×
197
      SILE.settings:set("font.features", tostring(otfeatures))
×
198
   end)
199

200
   self:registerCommand("remove-font-feature", function (options, _)
×
201
      local otfeatures = otFeatures()
×
202
      otfeatures:unloadOptions(options)
×
203
      SILE.settings:set("font.features", tostring(otfeatures))
×
204
   end)
205

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

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

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

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

236
\begin[type=autodoc:codeblock]{raw}
237
\font[features="+dlig,+hlig"]{...} % turn on discretionary and historic ligatures
238
\end{raw}
239

240
However, this is unwieldy and requires memorizing the feature codes.
241

242
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.
243
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{\href{http://texdoc.net/texmf-dist/doc/latex/fontspec/fontspec.pdf}}
244

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

247
\begin[type=autodoc:codeblock]{raw}
248
\add-font-feature[Ligatures=Rare]\add-font-feature[Ligatures=Discretionary]
249
...
250
\remove-font-feature[Ligatures=Rare]\remove-font-feature[Ligatures=Discretionary]
251
\end{raw}
252
\end{document}
253
]]
×
254

255
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

© 2025 Coveralls, Inc