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

sile-typesetter / sile / 9304060604

30 May 2024 02:07PM UTC coverage: 74.124% (-0.6%) from 74.707%
9304060604

push

github

alerque
style: Reformat Lua with stylua

8104 of 11995 new or added lines in 184 files covered. (67.56%)

15 existing lines in 11 files now uncovered.

12444 of 16788 relevant lines covered (74.12%)

7175.1 hits per line

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

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

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

11
   local names = {}
338✔
12
   local MacintoshLanguages = {
338✔
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 = {
338✔
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)
338✔
272
   name.records = {}
338✔
273
   if name.format == 1 then
338✔
NEW
274
      return
×
275
   end
276
   for i = 1, name.count do
128,175✔
277
      local record = vstruct.read(">platform:u2 encoding:u2 language:u2 name:u2 length:u2 offset:u2", fd)
127,837✔
278
      name.records[i] = record
127,837✔
279
      local language
280
      if (record.platform == 1 and record.encoding == 0) or (record.platform == 3 and record.encoding == 1) then
127,837✔
281
         if record.language < 0x8000 and record.platform == 1 then
127,837✔
282
            language = MacintoshLanguages[record.language]
63,005✔
283
         elseif record.language < 0x8000 and record.platform == 3 then
64,832✔
284
            language = WindowsLanguages[record.language]
64,832✔
285
         end
286
      end
287
      name.records[i].language = language
127,837✔
288
   end
289
   for i = 1, name.count do
128,175✔
290
      local record = name.records[i]
127,837✔
291
      local language = record.language
127,837✔
292
      if language then
127,837✔
293
         if not names[record.name] then
127,837✔
294
            names[record.name] = {}
64,981✔
295
         end
296
         if record.length > 0 then
127,837✔
297
            names[record.name][language] =
127,837✔
298
               vstruct.read(">@" .. name.sOffset + record.offset .. "s" .. record.length, fd)
255,674✔
299
            if record.platform == 3 then
127,837✔
300
               names[record.name][language] = { SU.utf16be_to_utf8(names[record.name][language][1]) }
129,664✔
301
            end
302
         end
303
      end
304
   end
305

306
   return names
338✔
307
end
308

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

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

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

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

329
   local colr = {}
3✔
330

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

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

346
   return colr
3✔
347
end
348

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

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

360
   local cpal = {}
3✔
361

362
   local header = vstruct.read(">nPalettesEntries:u2 nPalettes:u2 nColors:u2 oFirstColor:u4", fd)
3✔
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)
3✔
365

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

378
   return cpal
3✔
379
end
380

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

NEW
387
   local offsets = {}
×
NEW
388
   local header = vstruct.read(">version:u2 oDocIndex:u4", fd)
×
NEW
389
   if header.version > 0 then
×
NEW
390
      return
×
391
   end
NEW
392
   local numEntries = vstruct.read(">@" .. header.oDocIndex .. " u2", fd)
×
393
   local outlines =
NEW
394
      vstruct.read("> " .. numEntries[1] .. "*{startGlyphID:u2 endGlyphID:u2 svgDocOffset:u4 svgDocLength:u4}", fd)
×
NEW
395
   for i = 1, numEntries[1] do
×
NEW
396
      local outline = outlines[i]
×
NEW
397
      for j = outline.startGlyphID, outline.endGlyphID do
×
NEW
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
NEW
405
   return offsets
×
406
end
407

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

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

440
local parseCoverage = function (offset, fd)
441
   local coverageFormat = vstruct.readvals(">@" .. offset .. " u2", fd)
395✔
442
   if coverageFormat == 1 then
395✔
443
      local glyphCount = vstruct.readvals("> u2", fd)
79✔
444
      return vstruct.read("> " .. glyphCount .. "*u2", fd)
79✔
445
   elseif coverageFormat == 2 then
316✔
446
      local rangeCount = vstruct.readvals("> u2", fd)
316✔
447
      local ranges = vstruct.read("> " .. rangeCount .. "*{ &RangeRecord }", fd)
316✔
448
      local coverage = {}
316✔
449
      for i = 1, #ranges do
9,322✔
450
         for glyphID = ranges[i].startGlyphID, ranges[i].endGlyphID do
139,435✔
451
            local index = ranges[i].startCoverageIndex + glyphID - ranges[i].startGlyphID + 1 -- array in lua is one-based
130,429✔
452
            if coverage[index] then
130,429✔
NEW
453
               SU.error(glyphID .. " already exist in converage when processing " .. ranges[i])
×
454
            end
455
            coverage[index] = glyphID
130,429✔
456
         end
457
      end
458
      return coverage
316✔
459
   else
NEW
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 }
116,446✔
468
   if record.deviceTableOffset ~= 0 then
116,446✔
NEW
469
      newRecord.deviceTable = parseDeviceTable(parent_offset + record.deviceTableOffset, fd)
×
470
   end
471
   return newRecord
116,446✔
472
end
473

474
local parseConstants = function (offset, fd)
475
   local mathConstantNames = {
79✔
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 = {
79✔
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
79✔
592
   for i = 1, #mathConstantNames do
4,503✔
593
      mathConstantFormat = mathConstantFormat .. " " .. mathConstantNames[i] .. ":" .. mathConstantTypes[i]
4,424✔
594
   end
595
   local mathConstants = vstruct.read(mathConstantFormat, fd)
79✔
596
   for k, v in pairs(mathConstants) do
4,503✔
597
      if v and type(v) == "table" then
4,424✔
598
         mathConstants[k] = fetchMathValueRecord(v, offset, fd)
8,058✔
599
      end
600
   end
601
   return mathConstants
79✔
602
end
603

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

623
local parsePerGlyphTable = function (offset, type, fd)
624
   local coverageOffset = vstruct.readvals(">@" .. offset .. " u2", fd)
158✔
625
   local coverageTable = parseCoverage(offset + coverageOffset, fd)
158✔
626
   local count = vstruct.readvals(">@" .. (offset + 2) .. " u2", fd)
158✔
627
   if count ~= #coverageTable then
158✔
NEW
628
      SU.error("Coverage table corrupted")
×
629
   end
630
   local table = vstruct.read("> " .. count .. "*{ " .. type .. " }", fd)
158✔
631
   local result = {}
158✔
632
   for i = 1, count do
109,652✔
633
      if type == "&MathValueRecord" then
109,494✔
634
         result[coverageTable[i]] = fetchMathValueRecord(table[i], offset, fd)
218,988✔
NEW
635
      elseif type == "&MathKernInfoRecord" then
×
NEW
636
         result[coverageTable[i]] = {
×
NEW
637
            topRightMathKern = table[i].topRightMathKernOffset ~= 0
×
NEW
638
                  and parseMathKern(offset + table[i].topRightMathKernOffset, fd)
×
NEW
639
               or nil,
×
NEW
640
            topLeftMathKern = table[i].topLeftMathKernOffset ~= 0
×
NEW
641
                  and parseMathKern(offset + table[i].topLeftMathKernOffset, fd)
×
NEW
642
               or nil,
×
NEW
643
            bottomRightMathKern = table[i].bottomRightMathKernOffset ~= 0
×
NEW
644
                  and parseMathKern(offset + table[i].bottomRightMathKernOffset, fd)
×
NEW
645
               or nil,
×
NEW
646
            bottomLeftMathKern = table[i].bottomLeftMathKernOffset ~= 0
×
NEW
647
                  and parseMathKern(offset + table[i].bottomLeftMathKernOffset, fd)
×
NEW
648
               or nil,
×
649
         }
650
      else
NEW
651
         result[coverageTable[i]] = table[i]
×
652
      end
653
   end
654
   return result
158✔
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,923✔
661
      assembly.italicsCorrection = fetchMathValueRecord(assembly.italicsCorrection, inner_offset, inner_fd)
5,846✔
662
      assembly.partRecords = vstruct.read("> " .. assembly.partCount .. "*{ &GlyphPartRecord }", inner_fd)
5,846✔
663
      assembly.partCount = nil
2,923✔
664
      return assembly
2,923✔
665
   end
666
   local parseMathGlyphConstruction = function (inner_offset, inner_fd)
667
      local construction = vstruct.read(">@" .. inner_offset .. " glyphAssemblyOffset:u2 variantCount:u2", inner_fd)
6,004✔
668
      local mathGlyphVariantRecord =
669
         vstruct.read("> " .. construction.variantCount .. "*{ &MathGlyphVariantRecord }", inner_fd)
6,004✔
670
      return {
6,004✔
671
         glyphAssembly = construction.glyphAssemblyOffset ~= 0
6,004✔
672
               and parseGlyphAssembly(inner_offset + construction.glyphAssemblyOffset, inner_fd)
2,923✔
673
            or nil,
6,004✔
674
         mathGlyphVariantRecord = mathGlyphVariantRecord,
6,004✔
675
      }
6,004✔
676
   end
677
   local variants = vstruct.read(
158✔
678
      ">@"
679
         .. offset
79✔
680
         .. " minConnectorOverlap:u2 vertGlyphCoverageOffset:u2 horizGlyphCoverageOffset:u2 vertGlyphCount:u2 horizGlyphCount:u2",
79✔
681
      fd
682
   )
79✔
683
   local vertGlyphConstructionOffsets = vstruct.read("> " .. variants.vertGlyphCount .. "*u2", fd)
79✔
684
   local horizGlyphConstructionOffsets = vstruct.read("> " .. variants.horizGlyphCount .. "*u2", fd)
79✔
685
   local vertGlyphCoverage = {}
79✔
686
   if variants.vertGlyphCoverageOffset > 0 then
79✔
687
      vertGlyphCoverage = parseCoverage(offset + variants.vertGlyphCoverageOffset, fd)
158✔
688
   end
689
   local horizGlyphCoverage = {}
79✔
690
   if variants.horizGlyphCoverageOffset > 0 then
79✔
691
      horizGlyphCoverage = parseCoverage(offset + variants.horizGlyphCoverageOffset, fd)
158✔
692
   end
693
   if variants.vertGlyphCount ~= #vertGlyphCoverage or variants.horizGlyphCount ~= #horizGlyphCoverage then
79✔
NEW
694
      SU.error("MathVariants Table corrupted")
×
695
   end
696
   local vertGlyphConstructions = {}
79✔
697
   local horizGlyphConstructions = {}
79✔
698
   for i = 1, variants.vertGlyphCount do
3,950✔
699
      vertGlyphConstructions[vertGlyphCoverage[i]] =
3,871✔
700
         parseMathGlyphConstruction(offset + vertGlyphConstructionOffsets[i], fd)
7,742✔
701
   end
702
   for i = 1, variants.horizGlyphCount do
2,212✔
703
      horizGlyphConstructions[horizGlyphCoverage[i]] =
2,133✔
704
         parseMathGlyphConstruction(offset + horizGlyphConstructionOffsets[i], fd)
4,266✔
705
   end
706
   return {
79✔
707
      minConnectorOverlap = variants.minConnectorOverlap,
79✔
708
      vertGlyphConstructions = vertGlyphConstructions,
79✔
709
      horizGlyphConstructions = horizGlyphConstructions,
79✔
710
   }
79✔
711
end
712

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

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

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

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

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

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

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

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