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

sile-typesetter / sile / 9432254829

08 Jun 2024 11:20PM UTC coverage: 60.675% (+3.5%) from 57.223%
9432254829

push

github

alerque
fix(build): Bundle all assets in source distribution

...even when configured for doing a static binary build with embedded
assets. They don't get installed, but they should be in the dist file in
case the good folks building want to configure it a different way.

10441 of 17208 relevant lines covered (60.68%)

1913.98 hits per line

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

64.2
/core/opentype-parser.lua
1
local vstruct = require("vstruct")
47✔
2
local hb = require("justenoughharfbuzz")
47✔
3
local zlib = require("zlib")
47✔
4

5
local parseName = function (str)
6
   if str:len() <= 0 then
218✔
7
      return
×
8
   end
9
   local fd = vstruct.cursor(str)
109✔
10

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

133
   local WindowsLanguages = {
109✔
134
      [4] = "zh-CHS",
135
      [1025] = "ar-SA",
136
      [1026] = "bg-BG",
137
      [1027] = "ca-ES",
138
      [1028] = "zh-TW",
139
      [1029] = "cs-CZ",
140
      [1030] = "da-DK",
141
      [1031] = "de-DE",
142
      [1032] = "el-GR",
143
      [1033] = "en-US",
144
      [1035] = "fi-FI",
145
      [1036] = "fr-FR",
146
      [1037] = "he-IL",
147
      [1038] = "hu-HU",
148
      [1039] = "is-IS",
149
      [1040] = "it-IT",
150
      [1041] = "ja-JP",
151
      [1042] = "ko-KR",
152
      [1043] = "nl-NL",
153
      [1044] = "nb-NO",
154
      [1045] = "pl-PL",
155
      [1046] = "pt-BR",
156
      [1048] = "ro-RO",
157
      [1049] = "ru-RU",
158
      [1050] = "hr-HR",
159
      [1051] = "sk-SK",
160
      [1052] = "sq-AL",
161
      [1053] = "sv-SE",
162
      [1054] = "th-TH",
163
      [1055] = "tr-TR",
164
      [1056] = "ur-PK",
165
      [1057] = "id-ID",
166
      [1058] = "uk-UA",
167
      [1059] = "be-BY",
168
      [1060] = "sl-SI",
169
      [1061] = "et-EE",
170
      [1062] = "lv-LV",
171
      [1063] = "lt-LT",
172
      [1065] = "fa-IR",
173
      [1066] = "vi-VN",
174
      [1067] = "hy-AM",
175
      [1068] = "Lt-az-AZ",
176
      [1069] = "eu-ES",
177
      [1071] = "mk-MK",
178
      [1078] = "af-ZA",
179
      [1079] = "ka-GE",
180
      [1080] = "fo-FO",
181
      [1081] = "hi-IN",
182
      [1086] = "ms-MY",
183
      [1087] = "kk-KZ",
184
      [1088] = "ky-KZ",
185
      [1089] = "sw-KE",
186
      [1091] = "Lt-uz-UZ",
187
      [1092] = "tt-RU",
188
      [1094] = "pa-IN",
189
      [1095] = "gu-IN",
190
      [1097] = "ta-IN",
191
      [1098] = "te-IN",
192
      [1099] = "kn-IN",
193
      [1102] = "mr-IN",
194
      [1103] = "sa-IN",
195
      [1104] = "mn-MN",
196
      [1110] = "gl-ES",
197
      [1111] = "kok-IN",
198
      [1114] = "syr-SY",
199
      [1125] = "div-MV",
200
      [2049] = "ar-IQ",
201
      [2052] = "zh-CN",
202
      [2055] = "de-CH",
203
      [2057] = "en-GB",
204
      [2058] = "es-MX",
205
      [2060] = "fr-BE",
206
      [2064] = "it-CH",
207
      [2067] = "nl-BE",
208
      [2068] = "nn-NO",
209
      [2070] = "pt-PT",
210
      [2074] = "Lt-sr-SP",
211
      [2077] = "sv-FI",
212
      [2092] = "Cy-az-AZ",
213
      [2110] = "ms-BN",
214
      [2115] = "Cy-uz-UZ",
215
      [3073] = "ar-EG",
216
      [3076] = "zh-HK",
217
      [3079] = "de-AT",
218
      [3081] = "en-AU",
219
      [3082] = "es-ES",
220
      [3084] = "fr-CA",
221
      [3098] = "Cy-sr-SP",
222
      [4097] = "ar-LY",
223
      [4100] = "zh-SG",
224
      [4103] = "de-LU",
225
      [4105] = "en-CA",
226
      [4106] = "es-GT",
227
      [4108] = "fr-CH",
228
      [5121] = "ar-DZ",
229
      [5124] = "zh-MO",
230
      [5127] = "de-LI",
231
      [5129] = "en-NZ",
232
      [5130] = "es-CR",
233
      [5132] = "fr-LU",
234
      [6145] = "ar-MA",
235
      [6153] = "en-IE",
236
      [6154] = "es-PA",
237
      [6156] = "fr-MC",
238
      [7169] = "ar-TN",
239
      [7177] = "en-ZA",
240
      [7178] = "es-DO",
241
      [8193] = "ar-OM",
242
      [8201] = "en-JM",
243
      [8202] = "es-VE",
244
      [9217] = "ar-YE",
245
      [9225] = "en-CB",
246
      [9226] = "es-CO",
247
      [10241] = "ar-SY",
248
      [10249] = "en-BZ",
249
      [10250] = "es-PE",
250
      [11265] = "ar-JO",
251
      [11273] = "en-TT",
252
      [11274] = "es-AR",
253
      [12289] = "ar-LB",
254
      [12297] = "en-ZW",
255
      [12298] = "es-EC",
256
      [13313] = "ar-KW",
257
      [13321] = "en-PH",
258
      [13322] = "es-CL",
259
      [14337] = "ar-AE",
260
      [14346] = "es-UY",
261
      [15361] = "ar-BH",
262
      [15370] = "es-PY",
263
      [16385] = "ar-QA",
264
      [16394] = "es-BO",
265
      [17418] = "es-SV",
266
      [18442] = "es-HN",
267
      [19466] = "es-NI",
268
      [20490] = "es-PR",
269
      [31748] = "zh-CHT",
270
   }
271
   local name = vstruct.read(">format:u2 count:u2 sOffset:u2", fd)
109✔
272
   name.records = {}
109✔
273
   if name.format == 1 then
109✔
274
      return
×
275
   end
276
   for i = 1, name.count do
29,150✔
277
      local record = vstruct.read(">platform:u2 encoding:u2 language:u2 name:u2 length:u2 offset:u2", fd)
29,041✔
278
      name.records[i] = record
29,041✔
279
      local language
280
      if (record.platform == 1 and record.encoding == 0) or (record.platform == 3 and record.encoding == 1) then
29,041✔
281
         if record.language < 0x8000 and record.platform == 1 then
29,041✔
282
            language = MacintoshLanguages[record.language]
14,053✔
283
         elseif record.language < 0x8000 and record.platform == 3 then
14,988✔
284
            language = WindowsLanguages[record.language]
14,988✔
285
         end
286
      end
287
      name.records[i].language = language
29,041✔
288
   end
289
   for i = 1, name.count do
29,150✔
290
      local record = name.records[i]
29,041✔
291
      local language = record.language
29,041✔
292
      if language then
29,041✔
293
         if not names[record.name] then
29,041✔
294
            names[record.name] = {}
15,030✔
295
         end
296
         if record.length > 0 then
29,041✔
297
            names[record.name][language] =
29,041✔
298
               vstruct.read(">@" .. name.sOffset + record.offset .. "s" .. record.length, fd)
58,082✔
299
            if record.platform == 3 then
29,041✔
300
               names[record.name][language] = { SU.utf16be_to_utf8(names[record.name][language][1]) }
29,976✔
301
            end
302
         end
303
      end
304
   end
305

306
   return names
109✔
307
end
308

309
local parseMaxp = function (str)
310
   if str:len() <= 0 then
218✔
311
      return
×
312
   end
313
   local fd = vstruct.cursor(str)
109✔
314

315
   return vstruct.read(">version:u4 numGlyphs:u2", fd)
109✔
316
end
317

318
local function parseColr (str)
319
   if str:len() <= 0 then
218✔
320
      return
109✔
321
   end
322
   local fd = vstruct.cursor(str)
×
323

324
   local version = vstruct.readvals(">u2", fd)
×
325
   if version ~= 0 then
×
326
      return
×
327
   end
328

329
   local colr = {}
×
330

331
   local header = vstruct.read(">nBases:u2 oBases:u4 oLayers:u4 nLayers:u2", fd)
×
332
   local bases = vstruct.read(">@" .. header.oBases .. " " .. header.nBases .. "*{gid:u2 firstLayer:u2 nLayers:u2}", fd)
×
333
   local layers = vstruct.read(">@" .. header.oLayers .. " " .. header.nLayers .. "*{gid:u2 paletteIndex:u2}", fd)
×
334

335
   for i = 1, #bases do
×
336
      local base = bases[i]
×
337
      local glyphLayers = {}
×
338
      for j = base.firstLayer + 1, base.firstLayer + base.nLayers do
×
339
         local layer = layers[j]
×
340
         layer.paletteIndex = layer.paletteIndex + 1
×
341
         glyphLayers[#glyphLayers + 1] = layer
×
342
      end
343
      colr[base.gid] = glyphLayers
×
344
   end
345

346
   return colr
×
347
end
348

349
local function parseCpal (str)
350
   if str:len() <= 0 then
218✔
351
      return
109✔
352
   end
353
   local fd = vstruct.cursor(str)
×
354

355
   local version = vstruct.readvals(">u2", fd)
×
356
   if version > 1 then
×
357
      return
×
358
   end
359

360
   local cpal = {}
×
361

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

366
   for _ = 1, header.nPalettes do
×
367
      local palette = {}
×
368
      for j = 1, header.nPalettesEntries do
×
369
         local color = colors[j]
×
370
         for k, v in pairs(color) do
×
371
            color[k] = v / 255
×
372
         end
373
         palette[#palette + 1] = color
×
374
      end
375
      cpal[#cpal + 1] = palette
×
376
   end
377

378
   return cpal
×
379
end
380

381
local function parseSvg (str)
382
   if str:len() <= 0 then
218✔
383
      return
109✔
384
   end
385
   local fd = vstruct.cursor(str)
×
386

387
   local offsets = {}
×
388
   local header = vstruct.read(">version:u2 oDocIndex:u4", fd)
×
389
   if header.version > 0 then
×
390
      return
×
391
   end
392
   local numEntries = vstruct.read(">@" .. header.oDocIndex .. " u2", fd)
×
393
   local outlines =
394
      vstruct.read("> " .. numEntries[1] .. "*{startGlyphID:u2 endGlyphID:u2 svgDocOffset:u4 svgDocLength:u4}", fd)
×
395
   for i = 1, numEntries[1] do
×
396
      local outline = outlines[i]
×
397
      for j = outline.startGlyphID, outline.endGlyphID do
×
398
         offsets[j] = {
×
399
            svgDocLength = outline.svgDocLength,
400
            svgDocOffset = outline.svgDocOffset + header.oDocIndex,
401
            -- Note that now we are an offset from the beginning of the table
402
         }
403
      end
404
   end
405
   return offsets
×
406
end
407

408
local function parseHead (s)
409
   if s:len() <= 0 then
282✔
410
      return
×
411
   end
412
   local fd = vstruct.cursor(s)
141✔
413
   return vstruct.read(
141✔
414
      ">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",
141✔
415
      fd
416
   )
141✔
417
end
418

419
local parseDeviceTable = function (offset, fd)
420
   local header = vstruct.read(">@" .. offset .. " startSize:u2 endSize:u2 deltaFormat:u2", fd)
×
421
   local size = header.endSize - header.startSize + 1
×
422
   local buf
423
   if header.deltaFormat == 0x0001 then
×
424
      buf = vstruct.read("> " .. math.ceil(size + 7 / 8) .. "*[2| i2 i2 i2 i2 i2 i2 i2 i2 ]", fd)
×
425
   elseif header.deltaFormat == 0x0002 then
×
426
      buf = vstruct.read("> " .. math.ceil(size / 4) .. "*[2| i4 i4 i4 i4 ]", fd)
×
427
   elseif header.deltaFormat == 0x0003 then
×
428
      buf = vstruct.read("> " .. math.ceil(size / 2) .. "*[2| i8 i8 ]", fd)
×
429
   else
430
      SU.warn("DeltaFormat " .. header.deltaFormat .. " in Device Table is not supported. Ignore the table.")
×
431
      return nil
×
432
   end
433
   local deviceTable = {}
×
434
   for i = 1, size do
×
435
      deviceTable[header.startSize + i - 1] = buf[i]
×
436
   end
437
   return deviceTable
×
438
end
439

440
local parseCoverage = function (offset, fd)
441
   local coverageFormat = vstruct.readvals(">@" .. offset .. " u2", fd)
360✔
442
   if coverageFormat == 1 then
360✔
443
      local glyphCount = vstruct.readvals("> u2", fd)
72✔
444
      return vstruct.read("> " .. glyphCount .. "*u2", fd)
72✔
445
   elseif coverageFormat == 2 then
288✔
446
      local rangeCount = vstruct.readvals("> u2", fd)
288✔
447
      local ranges = vstruct.read("> " .. rangeCount .. "*{ &RangeRecord }", fd)
288✔
448
      local coverage = {}
288✔
449
      for i = 1, #ranges do
8,496✔
450
         for glyphID = ranges[i].startGlyphID, ranges[i].endGlyphID do
127,080✔
451
            local index = ranges[i].startCoverageIndex + glyphID - ranges[i].startGlyphID + 1 -- array in lua is one-based
118,872✔
452
            if coverage[index] then
118,872✔
453
               SU.error(glyphID .. " already exist in converage when processing " .. ranges[i])
×
454
            end
455
            coverage[index] = glyphID
118,872✔
456
         end
457
      end
458
      return coverage
288✔
459
   else
460
      SU.error("Unsupported coverage table format " .. coverageFormat)
×
461
   end
462
end
463

464
-- Removes the indirection in a MathValueRecord by replacing the
465
-- deviceTableOffset field by an actual device table in the deviceTable field.
466
local fetchMathValueRecord = function (record, parent_offset, fd)
467
   local newRecord = { value = record.value }
106,128✔
468
   if record.deviceTableOffset ~= 0 then
106,128✔
469
      newRecord.deviceTable = parseDeviceTable(parent_offset + record.deviceTableOffset, fd)
×
470
   end
471
   return newRecord
106,128✔
472
end
473

474
local parseConstants = function (offset, fd)
475
   local mathConstantNames = {
72✔
476
      "scriptPercentScaleDown",
477
      "scriptScriptPercentScaleDown",
478
      "delimitedSubFormulaMinHeight",
479
      "displayOperatorMinHeight",
480
      "mathLeading",
481
      "axisHeight",
482
      "accentBaseHeight",
483
      "flattenedAccentBaseHeight",
484
      "subscriptShiftDown",
485
      "subscriptTopMax",
486
      "subscriptBaselineDropMin",
487
      "superscriptShiftUp",
488
      "superscriptShiftUpCramped",
489
      "superscriptBottomMin",
490
      "superscriptBaselineDropMax",
491
      "subSuperscriptGapMin",
492
      "superscriptBottomMaxWithSubscript",
493
      "spaceAfterScript",
494
      "upperLimitGapMin",
495
      "upperLimitBaselineRiseMin",
496
      "lowerLimitGapMin",
497
      "lowerLimitBaselineDropMin",
498
      "stackTopShiftUp",
499
      "stackTopDisplayStyleShiftUp",
500
      "stackBottomShiftDown",
501
      "stackBottomDisplayStyleShiftDown",
502
      "stackGapMin",
503
      "stackDisplayStyleGapMin",
504
      "stretchStackTopShiftUp",
505
      "stretchStackBottomShiftDown",
506
      "stretchStackGapAboveMin",
507
      "stretchStackGapBelowMin",
508
      "fractionNumeratorShiftUp",
509
      "fractionNumeratorDisplayStyleShiftUp",
510
      "fractionDenominatorShiftDown",
511
      "fractionDenominatorDisplayStyleShiftDown",
512
      "fractionNumeratorGapMin",
513
      "fractionNumDisplayStyleGapMin",
514
      "fractionRuleThickness",
515
      "fractionDenominatorGapMin",
516
      "fractionDenomDisplayStyleGapMin",
517
      "skewedFractionHorizontalGap",
518
      "skewedFractionVerticalGap",
519
      "overbarVerticalGap",
520
      "overbarRuleThickness",
521
      "overbarExtraAscender",
522
      "underbarVerticalGap",
523
      "underbarRuleThickness",
524
      "underbarExtraDescender",
525
      "radicalVerticalGap",
526
      "radicalDisplayStyleVerticalGap",
527
      "radicalRuleThickness",
528
      "radicalExtraAscender",
529
      "radicalKernBeforeDegree",
530
      "radicalKernAfterDegree",
531
      "radicalDegreeBottomRaisePercent",
532
   }
533
   local mathConstantTypes = {
72✔
534
      "i2",
535
      "i2",
536
      "u2",
537
      "u2",
538
      "{ &MathValueRecord }",
539
      "{ &MathValueRecord }",
540
      "{ &MathValueRecord }",
541
      "{ &MathValueRecord }",
542
      "{ &MathValueRecord }",
543
      "{ &MathValueRecord }",
544
      "{ &MathValueRecord }",
545
      "{ &MathValueRecord }",
546
      "{ &MathValueRecord }",
547
      "{ &MathValueRecord }",
548
      "{ &MathValueRecord }",
549
      "{ &MathValueRecord }",
550
      "{ &MathValueRecord }",
551
      "{ &MathValueRecord }",
552
      "{ &MathValueRecord }",
553
      "{ &MathValueRecord }",
554
      "{ &MathValueRecord }",
555
      "{ &MathValueRecord }",
556
      "{ &MathValueRecord }",
557
      "{ &MathValueRecord }",
558
      "{ &MathValueRecord }",
559
      "{ &MathValueRecord }",
560
      "{ &MathValueRecord }",
561
      "{ &MathValueRecord }",
562
      "{ &MathValueRecord }",
563
      "{ &MathValueRecord }",
564
      "{ &MathValueRecord }",
565
      "{ &MathValueRecord }",
566
      "{ &MathValueRecord }",
567
      "{ &MathValueRecord }",
568
      "{ &MathValueRecord }",
569
      "{ &MathValueRecord }",
570
      "{ &MathValueRecord }",
571
      "{ &MathValueRecord }",
572
      "{ &MathValueRecord }",
573
      "{ &MathValueRecord }",
574
      "{ &MathValueRecord }",
575
      "{ &MathValueRecord }",
576
      "{ &MathValueRecord }",
577
      "{ &MathValueRecord }",
578
      "{ &MathValueRecord }",
579
      "{ &MathValueRecord }",
580
      "{ &MathValueRecord }",
581
      "{ &MathValueRecord }",
582
      "{ &MathValueRecord }",
583
      "{ &MathValueRecord }",
584
      "{ &MathValueRecord }",
585
      "{ &MathValueRecord }",
586
      "{ &MathValueRecord }",
587
      "{ &MathValueRecord }",
588
      "{ &MathValueRecord }",
589
      "i2",
590
   }
591
   local mathConstantFormat = ">@" .. offset
72✔
592
   for i = 1, #mathConstantNames do
4,104✔
593
      mathConstantFormat = mathConstantFormat .. " " .. mathConstantNames[i] .. ":" .. mathConstantTypes[i]
4,032✔
594
   end
595
   local mathConstants = vstruct.read(mathConstantFormat, fd)
72✔
596
   for k, v in pairs(mathConstants) do
4,104✔
597
      if v and type(v) == "table" then
4,032✔
598
         mathConstants[k] = fetchMathValueRecord(v, offset, fd)
7,344✔
599
      end
600
   end
601
   return mathConstants
72✔
602
end
603

604
local parseMathKern = function (offset, fd)
605
   local heightCount = vstruct.readvals(">@" .. offset .. " u2", fd)
×
606
   local mathKern = vstruct.read(
×
607
      "> correctionHeight:{ "
608
         .. heightCount
×
609
         .. "*{ &MathValueRecord } } kernValues:{ "
×
610
         .. (heightCount + 1)
×
611
         .. "*{ &MathValueRecord } }",
×
612
      fd
613
   )
614
   for i = 1, #mathKern.correctionHeight do
×
615
      mathKern.correctionHeight[i] = fetchMathValueRecord(mathKern.correctionHeight[i], offset, fd)
×
616
   end
617
   for i = 1, #mathKern.kernValues do
×
618
      mathKern.kernValues[i] = fetchMathValueRecord(mathKern.kernValues[i], offset, fd)
×
619
   end
620
   return mathKern
×
621
end
622

623
local parsePerGlyphTable = function (offset, type, fd)
624
   local coverageOffset = vstruct.readvals(">@" .. offset .. " u2", fd)
144✔
625
   local coverageTable = parseCoverage(offset + coverageOffset, fd)
144✔
626
   local count = vstruct.readvals(">@" .. (offset + 2) .. " u2", fd)
144✔
627
   if count ~= #coverageTable then
144✔
628
      SU.error("Coverage table corrupted")
×
629
   end
630
   local table = vstruct.read("> " .. count .. "*{ " .. type .. " }", fd)
144✔
631
   local result = {}
144✔
632
   for i = 1, count do
99,936✔
633
      if type == "&MathValueRecord" then
99,792✔
634
         result[coverageTable[i]] = fetchMathValueRecord(table[i], offset, fd)
199,584✔
635
      elseif type == "&MathKernInfoRecord" then
×
636
         result[coverageTable[i]] = {
×
637
            topRightMathKern = table[i].topRightMathKernOffset ~= 0
×
638
                  and parseMathKern(offset + table[i].topRightMathKernOffset, fd)
×
639
               or nil,
×
640
            topLeftMathKern = table[i].topLeftMathKernOffset ~= 0
×
641
                  and parseMathKern(offset + table[i].topLeftMathKernOffset, fd)
×
642
               or nil,
×
643
            bottomRightMathKern = table[i].bottomRightMathKernOffset ~= 0
×
644
                  and parseMathKern(offset + table[i].bottomRightMathKernOffset, fd)
×
645
               or nil,
×
646
            bottomLeftMathKern = table[i].bottomLeftMathKernOffset ~= 0
×
647
                  and parseMathKern(offset + table[i].bottomLeftMathKernOffset, fd)
×
648
               or nil,
×
649
         }
650
      else
651
         result[coverageTable[i]] = table[i]
×
652
      end
653
   end
654
   return result
144✔
655
end
656

657
local parseMathVariants = function (offset, fd)
658
   local parseGlyphAssembly = function (inner_offset, inner_fd)
659
      local assembly =
660
         vstruct.read(">@" .. inner_offset .. " italicsCorrection:{ &MathValueRecord } partCount:u2", inner_fd)
2,664✔
661
      assembly.italicsCorrection = fetchMathValueRecord(assembly.italicsCorrection, inner_offset, inner_fd)
5,328✔
662
      assembly.partRecords = vstruct.read("> " .. assembly.partCount .. "*{ &GlyphPartRecord }", inner_fd)
5,328✔
663
      assembly.partCount = nil
2,664✔
664
      return assembly
2,664✔
665
   end
666
   local parseMathGlyphConstruction = function (inner_offset, inner_fd)
667
      local construction = vstruct.read(">@" .. inner_offset .. " glyphAssemblyOffset:u2 variantCount:u2", inner_fd)
5,472✔
668
      local mathGlyphVariantRecord =
669
         vstruct.read("> " .. construction.variantCount .. "*{ &MathGlyphVariantRecord }", inner_fd)
5,472✔
670
      return {
5,472✔
671
         glyphAssembly = construction.glyphAssemblyOffset ~= 0
5,472✔
672
               and parseGlyphAssembly(inner_offset + construction.glyphAssemblyOffset, inner_fd)
2,664✔
673
            or nil,
5,472✔
674
         mathGlyphVariantRecord = mathGlyphVariantRecord,
5,472✔
675
      }
5,472✔
676
   end
677
   local variants = vstruct.read(
144✔
678
      ">@"
679
         .. offset
72✔
680
         .. " minConnectorOverlap:u2 vertGlyphCoverageOffset:u2 horizGlyphCoverageOffset:u2 vertGlyphCount:u2 horizGlyphCount:u2",
72✔
681
      fd
682
   )
72✔
683
   local vertGlyphConstructionOffsets = vstruct.read("> " .. variants.vertGlyphCount .. "*u2", fd)
72✔
684
   local horizGlyphConstructionOffsets = vstruct.read("> " .. variants.horizGlyphCount .. "*u2", fd)
72✔
685
   local vertGlyphCoverage = {}
72✔
686
   if variants.vertGlyphCoverageOffset > 0 then
72✔
687
      vertGlyphCoverage = parseCoverage(offset + variants.vertGlyphCoverageOffset, fd)
144✔
688
   end
689
   local horizGlyphCoverage = {}
72✔
690
   if variants.horizGlyphCoverageOffset > 0 then
72✔
691
      horizGlyphCoverage = parseCoverage(offset + variants.horizGlyphCoverageOffset, fd)
144✔
692
   end
693
   if variants.vertGlyphCount ~= #vertGlyphCoverage or variants.horizGlyphCount ~= #horizGlyphCoverage then
72✔
694
      SU.error("MathVariants Table corrupted")
×
695
   end
696
   local vertGlyphConstructions = {}
72✔
697
   local horizGlyphConstructions = {}
72✔
698
   for i = 1, variants.vertGlyphCount do
3,600✔
699
      vertGlyphConstructions[vertGlyphCoverage[i]] =
3,528✔
700
         parseMathGlyphConstruction(offset + vertGlyphConstructionOffsets[i], fd)
7,056✔
701
   end
702
   for i = 1, variants.horizGlyphCount do
2,016✔
703
      horizGlyphConstructions[horizGlyphCoverage[i]] =
1,944✔
704
         parseMathGlyphConstruction(offset + horizGlyphConstructionOffsets[i], fd)
3,888✔
705
   end
706
   return {
72✔
707
      minConnectorOverlap = variants.minConnectorOverlap,
72✔
708
      vertGlyphConstructions = vertGlyphConstructions,
72✔
709
      horizGlyphConstructions = horizGlyphConstructions,
72✔
710
   }
72✔
711
end
712

713
local parseIfPresent = function (baseOffset, subtableOffset, f)
714
   if subtableOffset == 0 then
288✔
715
      return nil
72✔
716
   else
717
      return f(baseOffset + subtableOffset)
216✔
718
   end
719
end
720

721
local function parseMath (s)
722
   if s:len() <= 0 then
282✔
723
      return
69✔
724
   end
725
   local fd = vstruct.cursor(s)
72✔
726
   local header = vstruct.read(
144✔
727
      ">majorVersion:u2 minorVersion:u2 mathConstantsOffset:u2 mathGlyphInfoOffset:u2 mathVariantsOffset:u2",
72✔
728
      fd
729
   )
72✔
730
   SU.debug("opentype-parser", "header =", header)
72✔
731
   if header.majorVersion > 1 then
72✔
732
      return
×
733
   end
734
   vstruct.compile("MathValueRecord", "value:i2 deviceTableOffset:u2")
72✔
735
   vstruct.compile("RangeRecord", "startGlyphID:u2 endGlyphID:u2 startCoverageIndex:u2")
72✔
736
   vstruct.compile(
144✔
737
      "MathKernInfoRecord",
72✔
738
      "topRightMathKernOffset:u2 topLeftMathKernOffset:u2 bottomRightMathKernOffset:u2 bottomLeftMathKernOffset:u2"
739
   )
72✔
740
   vstruct.compile("MathGlyphVariantRecord", "variantGlyph:u2 advanceMeasurement:u2")
72✔
741
   vstruct.compile(
144✔
742
      "GlyphPartRecord",
72✔
743
      "glyphID:u2 startConnectorLength:u2 endConnectorLength:u2 fullAdvance:u2 partFlags:u2"
744
   )
72✔
745
   local mathConstants = parseConstants(header.mathConstantsOffset, fd)
72✔
746
   local mathGlyphInfo = vstruct.read(
144✔
747
      ">@"
748
         .. header.mathGlyphInfoOffset
72✔
749
         .. " mathItalicsCorrectionInfoOffset:u2"
72✔
750
         .. " mathTopAccentAttachmentOffset:u2"
72✔
751
         .. " extendedShapeCoverageOffset:u2"
72✔
752
         .. " mathKernInfoOffset:u2",
72✔
753
      fd
754
   )
72✔
755
   SU.debug("opentype-parser", "mathGlyphInfoOffset =", header.mathGlyphInfoOffset)
72✔
756
   SU.debug("opentype-parser", "mathGlyphInfo =", mathGlyphInfo)
72✔
757
   local mathItalicsCorrection = parseIfPresent(
144✔
758
      header.mathGlyphInfoOffset,
72✔
759
      mathGlyphInfo.mathItalicsCorrectionInfoOffset,
72✔
760
      function (offset)
761
         return parsePerGlyphTable(offset, "&MathValueRecord", fd)
72✔
762
      end
763
   )
764
   local mathTopAccentAttachment = parseIfPresent(
144✔
765
      header.mathGlyphInfoOffset,
72✔
766
      mathGlyphInfo.mathTopAccentAttachmentOffset,
72✔
767
      function (offset)
768
         return parsePerGlyphTable(offset, "&MathValueRecord", fd)
72✔
769
      end
770
   )
771
   local extendedShapeCoverage = parseIfPresent(
144✔
772
      header.mathGlyphInfoOffset,
72✔
773
      mathGlyphInfo.extendedShapeCoverageOffset,
72✔
774
      function (offset)
775
         return parseCoverage(offset, fd)
72✔
776
      end
777
   )
778
   local mathKernInfo = parseIfPresent(header.mathGlyphInfoOffset, mathGlyphInfo.mathKernInfoOffset, function (offset)
144✔
779
      return parsePerGlyphTable(offset, "&MathKernInfoRecord", fd)
×
780
   end)
781
   local mathVariants = parseMathVariants(header.mathVariantsOffset, fd)
72✔
782
   return {
72✔
783
      mathConstants = mathConstants,
72✔
784
      mathItalicsCorrection = mathItalicsCorrection,
72✔
785
      mathTopAccentAttachment = mathTopAccentAttachment,
72✔
786
      extendedShapeCoverage = extendedShapeCoverage,
72✔
787
      mathKernInfo = mathKernInfo,
72✔
788
      mathVariants = mathVariants,
72✔
789
   }
72✔
790
end
791

792
local function parsePost (s)
793
   if s:len() <= 0 then
218✔
794
      return
×
795
   end
796
   local fd = vstruct.cursor(s)
109✔
797
   local header = vstruct.read(
218✔
798
      ">majorVersion:u2 minorVersion:u2 italicAngle:i4 underlinePosition:i2 underlineThickness:i2 isFixedPitch:u4",
109✔
799
      fd
800
   )
109✔
801
   local italicAngle = header.italicAngle / 65536 -- 1 << 16
109✔
802
   return {
109✔
803
      italicAngle = italicAngle,
109✔
804
      underlinePosition = header.underlinePosition,
109✔
805
      underlineThickness = header.underlineThickness,
109✔
806
   }
109✔
807
end
808

809
local function parseOs2 (s)
810
   if s:len() <= 0 then
218✔
811
      return
×
812
   end
813
   local fd = vstruct.cursor(s)
109✔
814
   local header = vstruct.read(
218✔
815
      ">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",
109✔
816
      fd
817
   )
109✔
818
   return {
109✔
819
      yStrikeoutPosition = header.yStrikeoutPosition,
109✔
820
      yStrikeoutSize = header.yStrikeoutSize,
109✔
821
   }
109✔
822
end
823

824
local parseFont = function (face)
825
   if not face.font then
2,999✔
826
      local font = {}
109✔
827
      font.head = parseHead(hb.get_table(face, "head"))
218✔
828
      font.names = parseName(hb.get_table(face, "name"))
218✔
829
      font.maxp = parseMaxp(hb.get_table(face, "maxp"))
218✔
830
      font.colr = parseColr(hb.get_table(face, "COLR"))
218✔
831
      font.cpal = parseCpal(hb.get_table(face, "CPAL"))
218✔
832
      font.svg = parseSvg(hb.get_table(face, "SVG"))
218✔
833
      font.math = parseMath(hb.get_table(face, "MATH"))
218✔
834
      font.post = parsePost(hb.get_table(face, "post"))
218✔
835
      font.os2 = parseOs2(hb.get_table(face, "OS/2"))
218✔
836
      face.font = font
109✔
837
   end
838
   return face.font
2,999✔
839
end
840

841
local decompress = function (str)
842
   local decompressed = {}
×
843
   while true do
844
      local chunk, eof = zlib.inflate(str)
×
845
      decompressed[#decompressed + 1] = chunk
×
846
      if eof then
×
847
         break
848
      end
849
   end
850
   return table.concat(decompressed, "")
×
851
end
852

853
local getSVG = function (face, gid)
854
   if not face.font then
×
855
      parseFont(face)
×
856
   end
857
   if not face.font.svg then
×
858
      return
×
859
   end
860
   local item = face.font.svg[gid]
×
861
   if not item then
×
862
      return
×
863
   end
864
   local str = hb.get_table(face, "SVG")
×
865
   local start = item.svgDocOffset + 1
×
866
   local svg = str:sub(start, start + item.svgDocLength - 1)
×
867
   if svg[1] == "\x1f" and svg[2] == "\x8b" then
×
868
      svg = decompress(svg)
×
869
   end
870
   return svg
×
871
end
872

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