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

sile-typesetter / sile / 6915845768

18 Nov 2023 07:23PM UTC coverage: 63.161% (-5.6%) from 68.751%
6915845768

push

github

web-flow
Merge 0f5c09a66 into f64e235fa

9802 of 15519 relevant lines covered (63.16%)

2045.54 hits per line

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

65.3
/core/opentype-parser.lua
1
local vstruct = require "vstruct"
49✔
2
local hb = require "justenoughharfbuzz"
49✔
3
local zlib = require "zlib"
49✔
4

5
local parseName = function(str)
6
  if str:len() <= 0 then return end
222✔
7
  local fd = vstruct.cursor(str)
111✔
8

9
  local names = {}
111✔
10
  local MacintoshLanguages = {
111✔
11
    [0] = 'en', [1] = 'fr', [2] = 'de', [3] = 'it', [4] = 'nl',
12
    [5] = 'sv', [6] = 'es', [7] = 'da', [8] = 'pt', [9] = 'no',
13
    [10] = 'he', [11] = 'ja', [12] = 'arb', [13] = 'fi', [14] = 'el',
14
    [15] = 'is', [16] = 'mt', [17] = 'tr', [18] = 'hr', [19] = 'zh-Hant',
15
    [20] = 'ur', [21] = 'hi', [22] = 'th', [23] = 'ko', [24] = 'lt',
16
    [25] = 'pl', [26] = 'hu', [27] = 'et', [28] = 'lv', [29] = 'smi',
17
    [30] = 'fo', [31] = 'fa', [32] = 'ru', [33] = 'zh-Hans', [34] = 'nl-BE',
18
    [35] = 'gle', [36] = 'sq', [37] = 'ro', [38] = 'cs', [39] = 'sk',
19
    [40] = 'sl', [41] = 'yi', [42] = 'sr', [43] = 'mk', [44] = 'bg',
20
    [45] = 'uk', [46] = 'be', [47] = 'uz', [48] = 'kk', [49] = 'az-Cyrl',
21
    [50] = 'az-Arab', [51] = 'hy', [52] = 'ka', [53] = 'mo', [54] = 'ky',
22
    [55] = 'tg', [56] = 'tk', [57] = 'mn-Mong', [58] = 'mn-Cyrl', [59] = 'ps',
23
    [60] = 'ku', [61] = 'ks', [62] = 'sd', [63] = 'bo', [64] = 'ne',
24
    [65] = 'sa', [66] = 'mr', [67] = 'bn', [68] = 'as', [69] = 'gu',
25
    [70] = 'pa', [71] = 'or', [72] = 'ml', [73] = 'kn', [74] = 'ta',
26
    [75] = 'te', [76] = 'se', [77] = 'my', [78] = 'km', [79] = 'lo',
27
    [80] = 'vi', [81] = 'id', [82] = 'tl', [83] = 'ms', [84] = 'ms',
28
    [85] = 'am', [86] = 'ti', [87] = 'gax', [88] = 'so', [89] = 'sw',
29
    [90] = 'rw', [91] = 'rn', [92] = 'ny', [93] = 'mg', [94] = 'eo',
30
    [128] = 'cy', [129] = 'eu', [130] = 'ca', [131] = 'la', [132] = 'qu',
31
    [133] = 'gn', [134] = 'ay', [135] = 'tt', [136] = 'ug', [137] = 'dz',
32
    [138] = 'jv', [139] = 'su', [140] = 'gl', [141] = 'af', [142] = 'br',
33
    [143] = 'iu', [144] = 'gd', [145] = 'gv', [146] = 'gle', [147] = 'to',
34
    [148] = 'el-polyton', [149] = 'kl', [150] = 'az'
×
35
  }
36

37
  local WindowsLanguages = {
111✔
38
    [4] = "zh-CHS", [1025] = "ar-SA", [1026] = "bg-BG", [1027] = "ca-ES",
39
    [1028] = "zh-TW", [1029] = "cs-CZ", [1030] = "da-DK", [1031] = "de-DE",
40
    [1032] = "el-GR", [1033] = "en-US", [1035] = "fi-FI", [1036] = "fr-FR",
41
    [1037] = "he-IL", [1038] = "hu-HU", [1039] = "is-IS", [1040] = "it-IT",
42
    [1041] = "ja-JP", [1042] = "ko-KR", [1043] = "nl-NL", [1044] = "nb-NO",
43
    [1045] = "pl-PL", [1046] = "pt-BR", [1048] = "ro-RO", [1049] = "ru-RU",
44
    [1050] = "hr-HR", [1051] = "sk-SK", [1052] = "sq-AL", [1053] = "sv-SE",
45
    [1054] = "th-TH", [1055] = "tr-TR", [1056] = "ur-PK", [1057] = "id-ID",
46
    [1058] = "uk-UA", [1059] = "be-BY", [1060] = "sl-SI", [1061] = "et-EE",
47
    [1062] = "lv-LV", [1063] = "lt-LT", [1065] = "fa-IR", [1066] = "vi-VN",
48
    [1067] = "hy-AM", [1068] = "Lt-az-AZ", [1069] = "eu-ES", [1071] = "mk-MK",
49
    [1078] = "af-ZA", [1079] = "ka-GE", [1080] = "fo-FO", [1081] = "hi-IN",
50
    [1086] = "ms-MY", [1087] = "kk-KZ", [1088] = "ky-KZ", [1089] = "sw-KE",
51
    [1091] = "Lt-uz-UZ", [1092] = "tt-RU", [1094] = "pa-IN", [1095] = "gu-IN",
52
    [1097] = "ta-IN", [1098] = "te-IN", [1099] = "kn-IN", [1102] = "mr-IN",
53
    [1103] = "sa-IN", [1104] = "mn-MN", [1110] = "gl-ES", [1111] = "kok-IN",
54
    [1114] = "syr-SY", [1125] = "div-MV", [2049] = "ar-IQ", [2052] = "zh-CN",
55
    [2055] = "de-CH", [2057] = "en-GB", [2058] = "es-MX", [2060] = "fr-BE",
56
    [2064] = "it-CH", [2067] = "nl-BE", [2068] = "nn-NO", [2070] = "pt-PT",
57
    [2074] = "Lt-sr-SP", [2077] = "sv-FI", [2092] = "Cy-az-AZ", [2110] = "ms-BN",
58
    [2115] = "Cy-uz-UZ", [3073] = "ar-EG", [3076] = "zh-HK", [3079] = "de-AT",
59
    [3081] = "en-AU", [3082] = "es-ES", [3084] = "fr-CA", [3098] = "Cy-sr-SP",
60
    [4097] = "ar-LY", [4100] = "zh-SG", [4103] = "de-LU", [4105] = "en-CA",
61
    [4106] = "es-GT", [4108] = "fr-CH", [5121] = "ar-DZ", [5124] = "zh-MO",
62
    [5127] = "de-LI", [5129] = "en-NZ", [5130] = "es-CR", [5132] = "fr-LU",
63
    [6145] = "ar-MA", [6153] = "en-IE", [6154] = "es-PA", [6156] = "fr-MC",
64
    [7169] = "ar-TN", [7177] = "en-ZA", [7178] = "es-DO", [8193] = "ar-OM",
65
    [8201] = "en-JM", [8202] = "es-VE", [9217] = "ar-YE", [9225] = "en-CB",
66
    [9226] = "es-CO", [10241] = "ar-SY", [10249] = "en-BZ", [10250] = "es-PE",
67
    [11265] = "ar-JO", [11273] = "en-TT", [11274] = "es-AR", [12289] = "ar-LB",
68
    [12297] = "en-ZW", [12298] = "es-EC", [13313] = "ar-KW", [13321] = "en-PH",
69
    [13322] = "es-CL", [14337] = "ar-AE", [14346] = "es-UY", [15361] = "ar-BH",
70
    [15370] = "es-PY", [16385] = "ar-QA", [16394] = "es-BO", [17418] = "es-SV",
71
    [18442] = "es-HN", [19466] = "es-NI", [20490] = "es-PR", [31748] = "zh-CHT"
×
72
  }
73
  local name = vstruct.read(">format:u2 count:u2 sOffset:u2", fd)
111✔
74
  name.records = {}
111✔
75
  if name.format == 1 then return end
111✔
76
  for i=1,name.count do
29,793✔
77
    local record = vstruct.read(">platform:u2 encoding:u2 language:u2 name:u2 length:u2 offset:u2", fd)
29,682✔
78
    name.records[i] = record
29,682✔
79
    local language
80
    if (record.platform == 1 and record.encoding == 0) or
29,682✔
81
       (record.platform == 3 and record.encoding == 1) then
15,317✔
82
      if record.language < 0x8000 and record.platform == 1 then
29,682✔
83
        language = MacintoshLanguages[record.language]
14,365✔
84
      elseif record.language < 0x8000 and record.platform == 3 then
15,317✔
85
        language = WindowsLanguages[record.language]
15,317✔
86
      end
87
    end
88
    name.records[i].language = language
29,682✔
89
  end
90
  for i=1,name.count do
29,793✔
91
    local record = name.records[i]
29,682✔
92
    local language = record.language
29,682✔
93
    if language then
29,682✔
94
      if not names[record.name] then names[record.name] = {} end
29,682✔
95
      if record.length > 0 then
29,682✔
96
        names[record.name][language] = vstruct.read(">@"..name.sOffset+record.offset.."s"..record.length, fd)
59,364✔
97
        if record.platform == 3 then
29,682✔
98
          names[record.name][language] = { SU.utf16be_to_utf8(names[record.name][language][1]) }
30,634✔
99
        end
100
      end
101
    end
102
  end
103

104
  return names
111✔
105
end
106

107
local parseMaxp = function(str)
108
  if str:len() <= 0 then return end
222✔
109
  local fd = vstruct.cursor(str)
111✔
110

111
  return vstruct.read(">version:u4 numGlyphs:u2", fd)
111✔
112
end
113

114
local function parseColr(str)
115
  if str:len() <= 0 then return end
222✔
116
  local fd = vstruct.cursor(str)
×
117

118
  local version = vstruct.readvals(">u2", fd)
×
119
  if version ~= 0 then return end
×
120

121
  local colr = {}
×
122

123
  local header = vstruct.read(">nBases:u2 oBases:u4 oLayers:u4 nLayers:u2", fd)
×
124
  local bases = vstruct.read(">@" .. header.oBases .. " " .. header.nBases .. "*{gid:u2 firstLayer:u2 nLayers:u2}", fd)
×
125
  local layers = vstruct.read(">@" .. header.oLayers .. " " .. header.nLayers .. "*{gid:u2 paletteIndex:u2}", fd)
×
126

127
  for i = 1, #bases do
×
128
    local base = bases[i]
×
129
    local glyphLayers = {}
×
130
    for j = base.firstLayer + 1, base.firstLayer + base.nLayers do
×
131
      local layer = layers[j]
×
132
      layer.paletteIndex = layer.paletteIndex + 1
×
133
      glyphLayers[#glyphLayers+1] = layer
×
134
    end
135
    colr[base.gid] = glyphLayers
×
136
  end
137

138
  return colr
×
139
end
140

141
local function parseCpal(str)
142
  if str:len() <= 0 then return end
222✔
143
  local fd = vstruct.cursor(str)
×
144

145
  local version = vstruct.readvals(">u2", fd)
×
146
  if version > 1 then return end
×
147

148
  local cpal = {}
×
149

150
  local header = vstruct.read(">nPalettesEntries:u2 nPalettes:u2 nColors:u2 oFirstColor:u4", fd)
×
151
  -- local colorIndices = vstruct.read("> " .. header.nPalettes .. "*u2", fd)
152
  local colors = vstruct.read(">@" .. header.oFirstColor .. " " .. header.nColors .. "*{b:u1 g:u1 r:u1 a:u1}", fd)
×
153

154
  for _ = 1, header.nPalettes do
×
155
    local palette = {}
×
156
    for j = 1, header.nPalettesEntries do
×
157
      local color = colors[j]
×
158
      for k, v in pairs(color) do
×
159
        color[k] = v / 255
×
160
      end
161
      palette[#palette+1] = color
×
162
    end
163
    cpal[#cpal+1] = palette
×
164
  end
165

166
  return cpal
×
167
end
168

169
local function parseSvg(str)
170
  if str:len() <= 0 then return end
222✔
171
  local fd = vstruct.cursor(str)
×
172

173
  local offsets = {}
×
174
  local header = vstruct.read(">version:u2 oDocIndex:u4", fd)
×
175
  if header.version > 0 then return end
×
176
  local numEntries = vstruct.read(">@"..header.oDocIndex.." u2", fd)
×
177
  local outlines = vstruct.read("> " .. numEntries[1] .. "*{startGlyphID:u2 endGlyphID:u2 svgDocOffset:u4 svgDocLength:u4}", fd)
×
178
  for i = 1, numEntries[1] do
×
179
    local outline = outlines[i]
×
180
    for j = outline.startGlyphID,outline.endGlyphID do
×
181
      offsets[j] = {
×
182
        svgDocLength = outline.svgDocLength,
183
        svgDocOffset = outline.svgDocOffset + header.oDocIndex
×
184
        -- Note that now we are an offset from the beginning of the table
185
      }
186
    end
187
  end
188
  return offsets
×
189
end
190

191
local function parseHead(s)
192
  if s:len() <= 0 then return end
286✔
193
  local fd = vstruct.cursor(s)
143✔
194
  return vstruct.read(">majorVersion:u2 minorVersion:u2 fontRevision:u4 checkSumAdjustment:u4 magicNumber:u4 flags:u2 unitsPerEm:u2 created:u8 modified:u8 xMin:i2 yMin:i2 xMax:i2 yMax:i2 macStyle:u2 lowestRecPPEM:u2 fontDirectionHint:i2 indexToLocFormat:i2 glyphDataFormat:i2", fd)
143✔
195
end
196

197
local parseDeviceTable = function(offset, fd)
198
  local header = vstruct.read(">@"..offset.." startSize:u2 endSize:u2 deltaFormat:u2", fd)
×
199
  local size = header.endSize - header.startSize + 1
×
200
  local buf
201
  if header.deltaFormat == 0x0001 then
×
202
    buf = vstruct.read("> "..math.ceil(size+7/8).."*[2| i2 i2 i2 i2 i2 i2 i2 i2 ]", fd)
×
203
  elseif header.deltaFormat == 0x0002 then
×
204
    buf = vstruct.read("> "..math.ceil(size/4).."*[2| i4 i4 i4 i4 ]", fd)
×
205
  elseif header.deltaFormat == 0x0003 then
×
206
    buf = vstruct.read("> "..math.ceil(size/2).."*[2| i8 i8 ]", fd)
×
207
  else
208
    SU.warn('DeltaFormat '..header.deltaFormat.." in Device Table is not supported. Ignore the table.")
×
209
    return nil
×
210
  end
211
  local deviceTable = {}
×
212
  for i = 1, size do
×
213
    deviceTable[header.startSize + i - 1] = buf[i]
×
214
  end
215
  return deviceTable
×
216
end
217

218
local parseCoverage = function (offset, fd)
219
  local coverageFormat = vstruct.readvals(">@"..offset.." u2", fd)
360✔
220
  if coverageFormat == 1 then
360✔
221
    local glyphCount = vstruct.readvals("> u2", fd)
72✔
222
    return vstruct.read("> "..glyphCount.."*u2", fd)
72✔
223
  elseif coverageFormat == 2 then
288✔
224
    local rangeCount = vstruct.readvals("> u2", fd)
288✔
225
    local ranges = vstruct.read("> "..rangeCount.."*{ &RangeRecord }", fd)
288✔
226
    local coverage = {}
288✔
227
    for i = 1, #(ranges) do
8,496✔
228
      for glyphID = ranges[i].startGlyphID, ranges[i].endGlyphID do
127,080✔
229
        local index = ranges[i].startCoverageIndex + glyphID - ranges[i].startGlyphID + 1 -- array in lua is one-based
118,872✔
230
        if coverage[index] then
118,872✔
231
          SU.error(glyphID .. " already exist in converage when processing " .. ranges[i])
×
232
        end
233
        coverage[index] = glyphID
118,872✔
234
      end
235
    end
236
    return coverage
288✔
237
  else
238
    SU.error('Unsupported coverage table format '..coverageFormat)
×
239
  end
240
end
241

242
-- Removes the indirection in a MathValueRecord by replacing the
243
-- deviceTableOffset field by an actual device table in the deviceTable field.
244
local fetchMathValueRecord = function(record, parent_offset, fd)
245
  local newRecord = { value = record.value }
106,128✔
246
  if record.deviceTableOffset ~= 0 then
106,128✔
247
    newRecord.deviceTable = parseDeviceTable(parent_offset + record.deviceTableOffset, fd)
×
248
  end
249
  return newRecord
106,128✔
250
end
251

252
local parseConstants = function(offset, fd)
253
  local mathConstantNames = {
72✔
254
    "scriptPercentScaleDown", "scriptScriptPercentScaleDown", "delimitedSubFormulaMinHeight",
255
    "displayOperatorMinHeight", "mathLeading", "axisHeight",
256
    "accentBaseHeight", "flattenedAccentBaseHeight", "subscriptShiftDown",
257
    "subscriptTopMax", "subscriptBaselineDropMin", "superscriptShiftUp",
258
    "superscriptShiftUpCramped", "superscriptBottomMin", "superscriptBaselineDropMax",
259
    "subSuperscriptGapMin", "superscriptBottomMaxWithSubscript", "spaceAfterScript",
260
    "upperLimitGapMin", "upperLimitBaselineRiseMin", "lowerLimitGapMin",
261
    "lowerLimitBaselineDropMin", "stackTopShiftUp", "stackTopDisplayStyleShiftUp",
262
    "stackBottomShiftDown", "stackBottomDisplayStyleShiftDown", "stackGapMin",
263
    "stackDisplayStyleGapMin", "stretchStackTopShiftUp", "stretchStackBottomShiftDown",
264
    "stretchStackGapAboveMin", "stretchStackGapBelowMin", "fractionNumeratorShiftUp",
265
    "fractionNumeratorDisplayStyleShiftUp", "fractionDenominatorShiftDown", "fractionDenominatorDisplayStyleShiftDown",
266
    "fractionNumeratorGapMin", "fractionNumDisplayStyleGapMin", "fractionRuleThickness",
267
    "fractionDenominatorGapMin", "fractionDenomDisplayStyleGapMin", "skewedFractionHorizontalGap",
268
    "skewedFractionVerticalGap", "overbarVerticalGap", "overbarRuleThickness",
269
    "overbarExtraAscender", "underbarVerticalGap", "underbarRuleThickness",
270
    "underbarExtraDescender", "radicalVerticalGap", "radicalDisplayStyleVerticalGap",
271
    "radicalRuleThickness", "radicalExtraAscender", "radicalKernBeforeDegree",
272
    "radicalKernAfterDegree", "radicalDegreeBottomRaisePercent" }
×
273
  local mathConstantTypes = { "i2", "i2", "u2",
72✔
274
    "u2", "{ &MathValueRecord }", "{ &MathValueRecord }",
275
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
276
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
277
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
278
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
279
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
280
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
281
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
282
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
283
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
284
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
285
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
286
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
287
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
288
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
289
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
290
    "{ &MathValueRecord }", "{ &MathValueRecord }", "{ &MathValueRecord }",
291
    "{ &MathValueRecord }", "i2" }
×
292
  local mathConstantFormat = ">@"..offset
72✔
293
  for i = 1, #(mathConstantNames) do
4,104✔
294
    mathConstantFormat = mathConstantFormat.." "..mathConstantNames[i]..":"..mathConstantTypes[i]
4,032✔
295
  end
296
  local mathConstants = vstruct.read(mathConstantFormat, fd)
72✔
297
  for k,v in pairs(mathConstants) do
4,104✔
298
    if v and type(v) == "table" then
4,032✔
299
      mathConstants[k] = fetchMathValueRecord(v, offset, fd)
7,344✔
300
    end
301
  end
302
  return mathConstants
72✔
303
end
304

305
local parseMathKern = function(offset, fd)
306
  local heightCount        = vstruct.readvals(">@"..offset.." u2", fd)
×
307
  local mathKern = vstruct.read("> correctionHeight:{ "..heightCount.."*{ &MathValueRecord } } kernValues:{ "..(heightCount+1).."*{ &MathValueRecord } }", fd)
×
308
  for i = 1, #(mathKern.correctionHeight) do
×
309
    mathKern.correctionHeight[i] = fetchMathValueRecord(mathKern.correctionHeight[i], offset, fd)
×
310
  end
311
  for i = 1, #(mathKern.kernValues) do
×
312
    mathKern.kernValues[i] = fetchMathValueRecord(mathKern.kernValues[i], offset, fd)
×
313
  end
314
  return mathKern
×
315
end
316

317
local parsePerGlyphTable = function(offset, type, fd)
318
  local coverageOffset = vstruct.readvals(">@"..offset.." u2", fd)
144✔
319
  local coverageTable = parseCoverage(offset + coverageOffset, fd)
144✔
320
  local count = vstruct.readvals(">@"..(offset+2).." u2", fd)
144✔
321
  if count ~= #(coverageTable) then
144✔
322
    SU.error("Coverage table corrupted")
×
323
  end
324
  local table = vstruct.read("> "..count.."*{ "..type.." }", fd)
144✔
325
  local result = {}
144✔
326
  for i = 1, count do
99,936✔
327
    if type == "&MathValueRecord" then
99,792✔
328
      result[coverageTable[i]] = fetchMathValueRecord(table[i], offset, fd)
199,584✔
329
    elseif type == "&MathKernInfoRecord" then
×
330
      result[coverageTable[i]] = {
×
331
        topRightMathKern = table[i].topRightMathKernOffset ~= 0 and parseMathKern(offset + table[i].topRightMathKernOffset, fd) or nil,
332
        topLeftMathKern = table[i].topLeftMathKernOffset ~= 0 and parseMathKern(offset + table[i].topLeftMathKernOffset, fd) or nil,
333
        bottomRightMathKern =  table[i].bottomRightMathKernOffset ~= 0 and parseMathKern(offset + table[i].bottomRightMathKernOffset, fd) or nil,
334
        bottomLeftMathKern = table[i].bottomLeftMathKernOffset ~= 0 and parseMathKern(offset + table[i].bottomLeftMathKernOffset, fd) or nil
×
335
      }
336
    else
337
      result[coverageTable[i]] = table[i]
×
338
    end
339
  end
340
  return result
144✔
341
end
342

343
local parseMathVariants = function(offset, fd)
344
  local parseGlyphAssembly = function(inner_offset, inner_fd)
345
    local assembly = vstruct.read(">@"..inner_offset.." italicsCorrection:{ &MathValueRecord } partCount:u2", inner_fd)
2,664✔
346
    assembly.italicsCorrection = fetchMathValueRecord(assembly.italicsCorrection, inner_offset, inner_fd)
5,328✔
347
    assembly.partRecords = vstruct.read("> "..assembly.partCount.."*{ &GlyphPartRecord }", inner_fd)
5,328✔
348
    assembly.partCount = nil
2,664✔
349
    return assembly
2,664✔
350
  end
351
  local parseMathGlyphConstruction = function(inner_offset, inner_fd)
352
    local construction = vstruct.read(">@"..inner_offset.." glyphAssemblyOffset:u2 variantCount:u2", inner_fd)
5,472✔
353
    local mathGlyphVariantRecord = vstruct.read("> "..construction.variantCount.."*{ &MathGlyphVariantRecord }", inner_fd)
5,472✔
354
    return {
5,472✔
355
      glyphAssembly = construction.glyphAssemblyOffset ~= 0 and parseGlyphAssembly(inner_offset + construction.glyphAssemblyOffset, inner_fd) or nil,
8,136✔
356
      mathGlyphVariantRecord = mathGlyphVariantRecord
5,472✔
357
    }
5,472✔
358
  end
359
  local variants = vstruct.read(">@"..offset.." minConnectorOverlap:u2 vertGlyphCoverageOffset:u2 horizGlyphCoverageOffset:u2 vertGlyphCount:u2 horizGlyphCount:u2", fd)
72✔
360
  local vertGlyphConstructionOffsets = vstruct.read("> "..variants.vertGlyphCount.."*u2", fd)
72✔
361
  local horizGlyphConstructionOffsets = vstruct.read("> "..variants.horizGlyphCount.."*u2", fd)
72✔
362
  local vertGlyphCoverage = {}
72✔
363
  if variants.vertGlyphCoverageOffset > 0 then
72✔
364
    vertGlyphCoverage = parseCoverage(offset + variants.vertGlyphCoverageOffset, fd)
144✔
365
  end
366
  local horizGlyphCoverage = {}
72✔
367
  if variants.horizGlyphCoverageOffset > 0 then
72✔
368
    horizGlyphCoverage = parseCoverage(offset + variants.horizGlyphCoverageOffset, fd)
144✔
369
  end
370
  if variants.vertGlyphCount ~= #(vertGlyphCoverage) or variants.horizGlyphCount ~= #(horizGlyphCoverage) then
72✔
371
    SU.error('MathVariants Table corrupted')
×
372
  end
373
  local vertGlyphConstructions = {}
72✔
374
  local horizGlyphConstructions = {}
72✔
375
  for i = 1, variants.vertGlyphCount do
3,600✔
376
    vertGlyphConstructions[vertGlyphCoverage[i]] = parseMathGlyphConstruction(offset + vertGlyphConstructionOffsets[i], fd)
7,056✔
377
  end
378
  for i = 1, variants.horizGlyphCount do
2,016✔
379
    horizGlyphConstructions[horizGlyphCoverage[i]] = parseMathGlyphConstruction(offset + horizGlyphConstructionOffsets[i], fd)
3,888✔
380
  end
381
  return {
72✔
382
    minConnectorOverlap = variants.minConnectorOverlap,
72✔
383
    vertGlyphConstructions = vertGlyphConstructions,
72✔
384
    horizGlyphConstructions = horizGlyphConstructions
72✔
385
  }
72✔
386
end
387

388
local parseIfPresent = function(baseOffset, subtableOffset, f)
389
  if subtableOffset == 0 then return nil
288✔
390
  else return f(baseOffset + subtableOffset)
216✔
391
  end
392
end
393

394
local function parseMath(s)
395
  if s:len() <= 0 then return end
286✔
396
  local fd = vstruct.cursor(s)
72✔
397
  local header = vstruct.read(">majorVersion:u2 minorVersion:u2 mathConstantsOffset:u2 mathGlyphInfoOffset:u2 mathVariantsOffset:u2", fd)
72✔
398
  SU.debug("opentype-parser", "header =", header)
72✔
399
  if header.majorVersion > 1 then return end
72✔
400
  vstruct.compile("MathValueRecord", "value:i2 deviceTableOffset:u2")
72✔
401
  vstruct.compile("RangeRecord", "startGlyphID:u2 endGlyphID:u2 startCoverageIndex:u2")
72✔
402
  vstruct.compile("MathKernInfoRecord", "topRightMathKernOffset:u2 topLeftMathKernOffset:u2 bottomRightMathKernOffset:u2 bottomLeftMathKernOffset:u2")
72✔
403
  vstruct.compile("MathGlyphVariantRecord", "variantGlyph:u2 advanceMeasurement:u2")
72✔
404
  vstruct.compile("GlyphPartRecord", "glyphID:u2 startConnectorLength:u2 endConnectorLength:u2 fullAdvance:u2 partFlags:u2")
72✔
405
  local mathConstants = parseConstants(header.mathConstantsOffset, fd)
72✔
406
  local mathGlyphInfo = vstruct.read(">@"..header.mathGlyphInfoOffset..
144✔
407
                                     " mathItalicsCorrectionInfoOffset:u2"..
72✔
408
                                     " mathTopAccentAttachmentOffset:u2"..
72✔
409
                                     " extendedShapeCoverageOffset:u2"..
72✔
410
                                     " mathKernInfoOffset:u2", fd)
72✔
411
  SU.debug("opentype-parser", "mathGlyphInfoOffset =", header.mathGlyphInfoOffset)
72✔
412
  SU.debug("opentype-parser", "mathGlyphInfo =", mathGlyphInfo)
72✔
413
  local mathItalicsCorrection = parseIfPresent(header.mathGlyphInfoOffset, mathGlyphInfo.mathItalicsCorrectionInfoOffset, function(offset)
144✔
414
    return parsePerGlyphTable(offset, "&MathValueRecord", fd)
72✔
415
  end)
416
  local mathTopAccentAttachment = parseIfPresent(header.mathGlyphInfoOffset, mathGlyphInfo.mathTopAccentAttachmentOffset, function(offset)
144✔
417
    return parsePerGlyphTable(offset, "&MathValueRecord", fd)
72✔
418
  end)
419
  local extendedShapeCoverage = parseIfPresent(header.mathGlyphInfoOffset, mathGlyphInfo.extendedShapeCoverageOffset, function(offset)
144✔
420
    return parseCoverage(offset, fd)
72✔
421
  end)
422
  local mathKernInfo = parseIfPresent(header.mathGlyphInfoOffset, mathGlyphInfo.mathKernInfoOffset, function(offset)
144✔
423
    return parsePerGlyphTable(offset, "&MathKernInfoRecord", fd)
×
424
  end)
425
  local mathVariants = parseMathVariants(header.mathVariantsOffset, fd)
72✔
426
  return {
72✔
427
    mathConstants = mathConstants,
72✔
428
    mathItalicsCorrection = mathItalicsCorrection,
72✔
429
    mathTopAccentAttachment = mathTopAccentAttachment,
72✔
430
    extendedShapeCoverage = extendedShapeCoverage,
72✔
431
    mathKernInfo = mathKernInfo,
72✔
432
    mathVariants = mathVariants
72✔
433
  }
72✔
434
end
435

436
local function parsePost(s)
437
  if s:len() <= 0 then return end
222✔
438
  local fd = vstruct.cursor(s)
111✔
439
  local header = vstruct.read(">majorVersion:u2 minorVersion:u2 italicAngle:i4 underlinePosition:i2 underlineThickness:i2 isFixedPitch:u4", fd)
111✔
440
  local italicAngle = header.italicAngle / 65536 -- 1 << 16
111✔
441
  return {
111✔
442
    italicAngle = italicAngle,
111✔
443
    underlinePosition = header.underlinePosition,
111✔
444
    underlineThickness = header.underlineThickness
111✔
445
  }
111✔
446
end
447

448
local function parseOs2(s)
449
  if s:len() <= 0 then return end
222✔
450
  local fd = vstruct.cursor(s)
111✔
451
  local header = vstruct.read(">version:u2 xAvgCharWidth:i2 usWeightClass:u2 usWidthClass:u2 fsType:u2 ySubscriptXSize:i2 ySubscriptYSize:i2 ySubscriptXOffset:i2 ySubscriptYOffset:i2 ySuperscriptXSize:i2 ySuperscriptYSize:i2 ySuperscriptXOffset:i2 ySuperscriptYOffset:i2, yStrikeoutSize:i2 yStrikeoutPosition:i2", fd)
111✔
452
  return {
111✔
453
    yStrikeoutPosition = header.yStrikeoutPosition,
111✔
454
    yStrikeoutSize = header.yStrikeoutSize,
111✔
455
  }
111✔
456
end
457

458
local parseFont = function(face)
459
  if not face.font then
2,945✔
460
    local font = {}
111✔
461
    font.head = parseHead(hb.get_table(face, "head"))
222✔
462
    font.names = parseName(hb.get_table(face, "name"))
222✔
463
    font.maxp = parseMaxp(hb.get_table(face, "maxp"))
222✔
464
    font.colr = parseColr(hb.get_table(face, "COLR"))
222✔
465
    font.cpal = parseCpal(hb.get_table(face, "CPAL"))
222✔
466
    font.svg  = parseSvg(hb.get_table(face, "SVG"))
222✔
467
    font.math = parseMath(hb.get_table(face, "MATH"))
222✔
468
    font.post = parsePost(hb.get_table(face, "post"))
222✔
469
    font.os2 = parseOs2(hb.get_table(face, "OS/2"))
222✔
470
    face.font = font
111✔
471
  end
472
  return face.font
2,945✔
473
end
474

475
local decompress = function (str)
476
  local decompressed = {}
×
477
  while true do
478
    local chunk, eof = zlib.inflate(str)
×
479
    decompressed[#decompressed+1] = chunk
×
480
    if eof then break end
×
481
  end
482
  return table.concat(decompressed, "")
×
483
end
484

485
local getSVG = function(face, gid)
486
  if not face.font then parseFont(face) end
×
487
  if not face.font.svg then return end
×
488
  local item = face.font.svg[gid]
×
489
  if not item then return end
×
490
  local str = hb.get_table(face, "SVG")
×
491
  local start = item.svgDocOffset+1
×
492
  local svg = str:sub(start, start + item.svgDocLength-1)
×
493
  if svg[1] == "\x1f" and svg[2] == "\x8b" then
×
494
    svg = decompress(svg)
×
495
  end
496
  return svg
×
497
end
498

499
return { parseHead = parseHead, parseMath = parseMath, parseFont = parseFont, getSVG = getSVG }
49✔
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