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

sile-typesetter / sile / 15507594683

07 Jun 2025 11:54AM UTC coverage: 30.951% (-30.4%) from 61.309%
15507594683

push

github

alerque
chore(tooling): Add post-checkout hook to clear makedeps on branch switch

6363 of 20558 relevant lines covered (30.95%)

3445.44 hits per line

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

88.76
/fontmanagers/base.lua
1
--- SILE fontmanager class.
2
-- @interfaces fontmanagers
3

4
local module = require("types.module")
53✔
5
local fontmanager = pl.class(module)
53✔
6
fontmanager.type = "fontmanager"
53✔
7

8
local icu = require("justenoughicu")
53✔
9

10
local bits = require("core.parserbits")
53✔
11
local lpeg = require("lpeg")
53✔
12
local Ct, Cg, P = lpeg.Ct, lpeg.Cg, lpeg.P
53✔
13
local adjust_metric = P("ex-height") + P("cap-height")
53✔
14
-- stylua: ignore start
15
local adjustment = Ct(Cg(bits.number, "amount")^-1 * bits.ws * Cg(adjust_metric, "unit"))
53✔
16
-- stylua: ignore end
17

18
function fontmanager:_init ()
53✔
19
   module._init(self)
53✔
20
end
21

22
function fontmanager:face (_) end
53✔
23

24
function fontmanager:_declareSettings ()
53✔
25
   self.settings:declare({ parameter = "font.family", type = "string or nil", default = "Gentium Plus" })
106✔
26
   self.settings:declare({ parameter = "font.size", type = "number or integer", default = 10 })
106✔
27
   self.settings:declare({ parameter = "font.weight", type = "integer", default = 400 })
106✔
28
   self.settings:declare({ parameter = "font.variant", type = "string", default = "normal" })
106✔
29
   self.settings:declare({ parameter = "font.script", type = "string", default = "" })
106✔
30
   self.settings:declare({ parameter = "font.style", type = "string", default = "" })
106✔
31
   self.settings:declare({ parameter = "font.direction", type = "string", default = "" })
106✔
32
   self.settings:declare({ parameter = "font.filename", type = "string or nil", default = "" })
106✔
33
   self.settings:declare({ parameter = "font.features", type = "string", default = "" })
106✔
34
   self.settings:declare({ parameter = "font.variations", type = "string", default = "" })
106✔
35
   self.settings:declare({ parameter = "font.hyphenchar", type = "string", default = "-" })
106✔
36
end
37

38
function fontmanager:_registerCommands ()
53✔
39
   local function measureFontAdjustment (metric)
40
      if metric == "ex-height" then
6✔
41
         -- Uses the height of lowercase letters.
42
         -- This is used to normalize lowercase letters across fonts.
43
         -- The height of the lowercase letter "x" is used as the reference.
44
         -- Another option would be to use the OS/2 font table sxHeight value when available.
45
         return SILE.shaper:measureChar("x").height
12✔
46
      end
47
      if metric == "cap-height" then
×
48
         -- Uses the the height of uppercase letters.
49
         -- This is used to normalize uppercase letters across fonts.
50
         -- The height of the uppercase letter "H" is used as the reference.
51
         -- Another option would be to use the OS/2 font table sCapHeight value when available.
52
         return SILE.shaper:measureChar("H").height
×
53
      end
54
      SU.error("Unknown font adjust metric " .. metric)
×
55
   end
56

57
   local function adjustedFontSize (options)
58
      local adjust = options.adjust
3✔
59
      local parsed = adjustment:match(adjust)
3✔
60
      if not parsed then
3✔
61
         SU.error("Couldn't parse font adjust value " .. adjust)
×
62
      end
63
      -- Shallow copy: we don't want to modify the original AST as content may be reused
64
      -- in other contexts (e.g. running headers) and may need to adapt to different font sizes.
65
      local baseOpts = pl.tablex.copy(options)
3✔
66
      baseOpts.adjust = nil -- cancel for target font size calculation
3✔
67
      local currentMeasure = measureFontAdjustment(parsed.unit)
3✔
68
      local ratio = parsed.amount or 1
3✔
69
      local newMeasure
70
      -- Apply the target font size to measure the new font
71
      SILE.call("font", baseOpts, function ()
6✔
72
         newMeasure = measureFontAdjustment(parsed.unit)
6✔
73
      end)
74
      return self.settings:get("font.size") * ratio * (currentMeasure / newMeasure)
9✔
75
   end
76

77
   self.commands:register("font", function (options, content)
159✔
78
      if SU.ast.hasContent(content) then
818✔
79
         self.settings:pushState()
64✔
80
      end
81
      if options.adjust then
409✔
82
         if options.size then
3✔
83
            SU.error("Can't specify both 'size' and 'adjust' in a \\font command")
×
84
         end
85
         self.settings:set("font.size", adjustedFontSize(options))
9✔
86
      end
87
      if options.filename then
409✔
88
         self.settings:set("font.filename", options.filename)
×
89
      end
90
      if options.family then
409✔
91
         self.settings:set("font.family", options.family)
752✔
92
         self.settings:set("font.filename", "")
752✔
93
      end
94
      if options.size then
409✔
95
         local size = SU.cast("measurement", options.size)
395✔
96
         if not size then
395✔
97
            SU.error("Couldn't parse font size " .. options.size)
×
98
         end
99
         self.settings:set("font.size", size:absolute())
1,185✔
100
      end
101
      if options.weight then
409✔
102
         self.settings:set("font.weight", 0 + options.weight)
710✔
103
      end
104
      if options.style then
409✔
105
         self.settings:set("font.style", options.style)
710✔
106
      end
107
      if options.variant then
409✔
108
         self.settings:set("font.variant", options.variant)
708✔
109
      end
110
      if options.features then
409✔
111
         self.settings:set("font.features", options.features)
708✔
112
      end
113
      if options.variations then
409✔
114
         self.settings:set("font.variations", options.variations)
708✔
115
      end
116
      if options.direction then
409✔
117
         self.settings:set("font.direction", options.direction)
708✔
118
      end
119
      if options.language then
409✔
120
         if options.language ~= "und" and icu and icu.canonicalize_language then
358✔
121
            local newlang = icu.canonicalize_language(options.language)
358✔
122
            -- if newlang ~= options.language then
123
            -- SU.warn("Language '"..options.language.."' not canonical, '"..newlang.."' will be used instead")
124
            -- end
125
            options.language = newlang
358✔
126
         end
127
         self.settings:set("document.language", options.language)
716✔
128
      end
129
      if options.script then
409✔
130
         self.settings:set("font.script", options.script)
710✔
131
      end
132
      if options.hyphenchar then
409✔
133
         -- must be in the form of, for example, "-" or "U+2010" or "0x2010" (Unicode hex codepoint)
134
         self.settings:set("font.hyphenchar", SU.utf8charfromcodepoint(options.hyphenchar))
1,065✔
135
      end
136

137
      -- We must *actually* load the font here, because by the time we're inside
138
      -- SILE.shaper.shapeToken, it's too late to respond appropriately to things
139
      -- that the post-load hook might want to do.
140
      SILE.font.cache(SILE.font.loadDefaults(options), SILE.shaper:_getFaceCallback())
1,227✔
141

142
      if SU.ast.hasContent(content) then
818✔
143
         SILE.process(content)
32✔
144
         self.settings:popState()
64✔
145
         if SILE.shaper._name == "harfbuzz-color" and SILE.scratch._lastshaper then
32✔
146
            SU.debug("color-fonts", "Switching from color fonts shaper back to previous shaper")
×
147
            SILE.typesetter:leaveHmode(true)
×
148
            SILE.scratch._lastshaper, SILE.shaper = nil, SILE.scratch._lastshaper
×
149
         end
150
      end
151
   end, "Set current font family, size, weight, style, variant, script, direction and language")
462✔
152
end
153

154
return fontmanager
53✔
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