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

sile-typesetter / sile / 11170735472

03 Oct 2024 10:32PM UTC coverage: 58.612% (-4.5%) from 63.103%
11170735472

push

github

web-flow
Merge bcab25790 into 783083345

15 of 64 new or added lines in 5 files covered. (23.44%)

828 existing lines in 41 files now uncovered.

10478 of 17877 relevant lines covered (58.61%)

2029.7 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 = "fault",
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
-- stylua: ignore start
109

110
-- Parser for feature strings
UNCOV
111
local featurename = C((1 - S",;:=")^1)
×
UNCOV
112
local value = C(SILE.parserBits.integer)
×
UNCOV
113
local tag = C(S"+-") * featurename * (P"=" * value)^0 * S",;:"^-1 / tagpos
×
UNCOV
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)
UNCOV
118
local fontspecsafe = R("AZ", "az", "09") + P":"
×
UNCOV
119
local ws = SILE.parserBits.ws
×
UNCOV
120
local fontspecsep = P"," * ws
×
UNCOV
121
local fontspecname = C(fontspecsafe^1)
×
UNCOV
122
local fontspeclist = ws * P"{" *
×
UNCOV
123
                     Ct(ws * fontspecname *
×
UNCOV
124
                        (fontspecsep * fontspecname * ws)^0) *
×
UNCOV
125
                     P"}" * ws
×
126

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

129
-- stylua: ignore end
130

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

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

UNCOV
151
function otFeatures:loadOption (name, val, invert)
×
UNCOV
152
   local posneg = not invert
×
UNCOV
153
   local key = otFeatureMap[name]
×
UNCOV
154
   if not key then
×
155
      SU.warn("Unknown OpenType feature " .. name)
×
156
   else
UNCOV
157
      local matches = lpeg.match(fontspeclist, val)
×
UNCOV
158
      for _, v in ipairs(matches or { val }) do
×
UNCOV
159
         v = v:gsub("^No", function ()
×
UNCOV
160
            posneg = false
×
UNCOV
161
            return ""
×
162
         end)
UNCOV
163
         local feat = type(key) == "function" and key(v) or key[v]
×
UNCOV
164
         if not feat then
×
165
            SU.warn("Bad OpenType value " .. v .. " for feature " .. name)
×
166
         else
UNCOV
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.
UNCOV
179
function otFeatures:loadOptions (options, invert)
×
UNCOV
180
   SU.debug("features", "Features was", self)
×
UNCOV
181
   for k, v in pairs(options) do
×
UNCOV
182
      self:loadOption(k, v, invert)
×
183
   end
UNCOV
184
   SU.debug("features", "Features interpreted as", self)
×
185
end
186

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

UNCOV
191
local fontfn = SILE.Commands.font
×
192

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

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

UNCOV
206
   self:registerCommand("font", function (options, content)
×
UNCOV
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. ⍩⃝
UNCOV
212
      for k, v in pairs(options) do
×
UNCOV
213
         if k:match("^[A-Z]") then
×
UNCOV
214
            otfeatures:loadOption(k, v)
×
UNCOV
215
            options[k] = nil
×
216
         end
217
      end
UNCOV
218
      SU.debug("features", "Font features parsed as:", otfeatures)
×
UNCOV
219
      options.features = (options.features and options.features .. ";" or "") .. tostring(otfeatures)
×
UNCOV
220
      return fontfn(options, content)
×
UNCOV
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{\code{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}
UNCOV
253
]]
×
254

UNCOV
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