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

sile-typesetter / sile / 6713098919

31 Oct 2023 10:21PM UTC coverage: 52.831% (-21.8%) from 74.636%
6713098919

push

github

web-flow
Merge d0a2a1ee9 into b185d4972

45 of 45 new or added lines in 3 files covered. (100.0%)

8173 of 15470 relevant lines covered (52.83%)

6562.28 hits per line

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

75.22
/shapers/harfbuzz.lua
1
local hb = require("justenoughharfbuzz")
7✔
2
local icu = require("justenoughicu")
7✔
3
local bitshim = require("bitshim")
7✔
4

5
SILE.settings:declare({
7✔
6
  parameter = "harfbuzz.subshapers",
7
  type = "string or nil",
8
  default = "",
9
  help = "Comma-separated shaper list to pass to Harfbuzz"
×
10
})
11

12
local base = require("shapers.base")
7✔
13

14
local smallTokenSize = 20 -- Small words will be cached
7✔
15
local shapeCache = {}
7✔
16
local _key = function (options, text)
17
  return table.concat({
507✔
18
    text,
507✔
19
    options.tracking or "1",
507✔
20
    options.language,
507✔
21
    options.script,
507✔
22
    SILE.font._key(options)
1,014✔
23
  }, ";")
507✔
24
end
25

26
local substwarnings = {}
7✔
27
local usedfonts = {}
7✔
28

29
local shaper = pl.class(base)
7✔
30
shaper._name = "harfbuzz"
7✔
31

32
function shaper:shapeToken (text, options)
7✔
33
  local items
34
  if #text < smallTokenSize then items = shapeCache[_key(options, text)]; if items then return items end end
873✔
35
  local face = SILE.font.cache(options, self.getFace)
99✔
36
  if self:checkHBProblems(text, face) then return {} end
198✔
37
  if not face then
99✔
38
    SU.error("Could not find requested font "..options.." or any suitable substitutes")
×
39
  end
40
  if not(options.filename) and face.family ~= options.family and not substwarnings[options.family] then
99✔
41
    substwarnings[options.family] = true
×
42
    SU.warn("Font family '"..options.family.."' not available, falling back to '"..face.family.."'")
×
43
  end
44
  usedfonts[face] = true
99✔
45
  items = { hb._shape(text,
198✔
46
      face,
99✔
47
      options.script,
99✔
48
      options.direction,
99✔
49
      options.language,
99✔
50
      face.pointsize,
99✔
51
      options.features,
99✔
52
      SILE.settings:get("harfbuzz.subshapers") or ""
198✔
53
    ) }
99✔
54
  for i = 1, #items do
1,655✔
55
    local j = (i == #items) and #text or items[i+1].index
1,556✔
56
    items[i].text = text:sub(items[i].index+1, j) -- Lua strings are 1-indexed
3,112✔
57
    if options.tracking then
1,556✔
58
      items[i].width = items[i].width * options.tracking
×
59
    end
60
  end
61
  if #text < smallTokenSize then shapeCache[_key(options, text)] = items end
179✔
62
  return items
99✔
63
end
64

65
local _pretty_varitions = function (face)
66
  local text = face.filename
×
67
  if face.variations and face.variations ~= "" then
×
68
    text = text.."@"..face.variations
×
69
  end
70
  local index = bitshim.band(face.index, 0xFFFF) or 0
×
71
  local instance = bitshim.rshift(face.index, 16) or 0
×
72
  if index or instance then
×
73
    text = text.."["..index..","..instance.."]"
×
74
  end
75
  return text
×
76
end
77

78
-- TODO: normalize this method to accept self as first arg
79
function shaper.getFace (opts)
7✔
80
  local face = SILE.fontManager:face(opts)
16✔
81
  SU.debug("fonts", "Resolved font family", opts.family, "->", face and face.filename)
16✔
82
  if not face or not face.filename then SU.error("Couldn't find face '"..opts.family.."'") end
16✔
83
  if SILE.makeDeps then SILE.makeDeps:add(face.filename) end
16✔
84
  face.variations = opts.variations or ""
16✔
85
  face.pointsize = ("%g"):format(SILE.measurement(opts.size):tonumber())
48✔
86
  face.weight = ("%d"):format(opts.weight or 0)
16✔
87

88
  -- Try instanciating the font, hb.instanciate() will return nil if it is not
89
  -- a variable font or if instanciation failed.
90
  face.tempfilename = face.filename
16✔
91
  local data = hb.instanciate(face)
16✔
92
  if data then
16✔
93
    local tmp = os.tmpname()
×
94
    local file = io.open(tmp, "wb")
×
95
    file:write(data)
×
96
    file:close()
×
97
    face.tempfilename = tmp
×
98
    SU.debug("fonts", "Instanciated", _pretty_varitions(face), "as", face.tempfilename)
×
99
  elseif (face.variations ~= "") or (bitshim.rshift(face.index, 16) ~= 0) then
16✔
100
    if not SILE.features.font_variations then
×
101
      SU.warn([[This build of SILE was compiled with font variations support disabled,
×
102
  likely due to not having the subsetter library included in HarfBuzz >= 6.
103
  This document specifies font variations which cannot be correctly rendered.
104
  Please rebuild SILE with the necessary library support. Alternatively to procede
105
  anyway *incorrectly* render this document run:
106
      sile -e 'SILE.features.font_variations = true' ....
107
  Or modify the document to remove variations options from font commands.]])
×
108
    end
109
    SU.error("Failed to instanciate: " .. _pretty_varitions(face))
×
110
  end
111

112
  return face
16✔
113
end
114

115
function shaper.preAddNodes (_, items, nnodeValue) -- Check for complex nodes
7✔
116
  for i = 1, #items do
1,893✔
117
    if items[i].y_offset or items[i].x_offset or items[i].width ~= items[i].glyphAdvance then
1,413✔
118
      nnodeValue.complex = true; break
94✔
119
    end
120
  end
121
end
122

123
function shaper.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
7✔
124
  if nnodevalue.complex then
1,259✔
125

126
    if not nnodevalue.items then nnodevalue.items = {} end
324✔
127
    nnodevalue.items[#nnodevalue.items+1] = shapedglyph
324✔
128
  end
129
  if not nnodevalue.glyphString then nnodevalue.glyphString = {} end
1,259✔
130
  if not nnodevalue.glyphNames then nnodevalue.glyphNames = {} end
1,259✔
131
  table.insert(nnodevalue.glyphString, shapedglyph.gid)
1,259✔
132
  table.insert(nnodevalue.glyphNames, shapedglyph.name)
1,259✔
133
end
134

135
function shaper.debugVersions (_)
7✔
136
  local ot = require("core.opentype-parser")
7✔
137
  print("Harfbuzz version: "..hb.version())
7✔
138
  print("Shapers enabled: ".. table.concat({ hb.shapers() }, ", "))
7✔
139
  if icu then
7✔
140
    print("ICU support enabled")
7✔
141
  end
142
  print("")
7✔
143
  print("Fonts used:")
7✔
144
  for face, _ in pairs(usedfonts) do
23✔
145
    local font = ot.parseFont(face)
16✔
146
    local version = "Unknown version"
16✔
147
    if font and font.names and font.names[5] then
16✔
148
      -- luacheck: ignore 512
149
      -- (It's OK to grab the first version we find in the name table)
150
      for _, v in pairs(font.names[5]) do version = v[1]; break end
32✔
151
    end
152
    print(face.filename..":"..face.index, version)
16✔
153
  end
154
end
155

156
function shaper.checkHBProblems (_, text, face)
7✔
157
  if hb.version_lessthan(1, 0, 4) and #text < 1 then
99✔
158
    return true
×
159
  end
160
  if hb.version_lessthan(2, 3, 0)
99✔
161
    and hb.get_table(face, "CFF "):len() > 0
99✔
162
    and not substwarnings["CFF "] then
×
163
    SILE.status.unsupported = true
×
164
    SU.warn("Vertical spacing of CFF fonts may be subtly inconsistent between systems. Upgrade to Harfbuzz 2.3.0 if you need absolute consistency.")
×
165
    substwarnings["CFF "] = true
×
166
  end
167
  return false
99✔
168
end
169

170
return shaper
7✔
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