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

sile-typesetter / sile / 9304049654

30 May 2024 02:12PM UTC coverage: 60.021% (-14.7%) from 74.707%
9304049654

push

github

web-flow
Merge 1a26b4f22 into a1fd105f8

6743 of 12900 new or added lines in 186 files covered. (52.27%)

347 existing lines in 49 files now uncovered.

10311 of 17179 relevant lines covered (60.02%)

3307.34 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")
140✔
2
local hb = require("justenoughharfbuzz")
140✔
3
local zlib = require("zlib")
140✔
4

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

11
   local names = {}
277✔
12
   local MacintoshLanguages = {
277✔
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 = {
277✔
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)
277✔
272
   name.records = {}
277✔
273
   if name.format == 1 then
277✔
NEW
274
      return
×
275
   end
276
   for i = 1, name.count do
109,821✔
277
      local record = vstruct.read(">platform:u2 encoding:u2 language:u2 name:u2 length:u2 offset:u2", fd)
109,544✔
278
      name.records[i] = record
109,544✔
279
      local language
280
      if (record.platform == 1 and record.encoding == 0) or (record.platform == 3 and record.encoding == 1) then
109,544✔
281
         if record.language < 0x8000 and record.platform == 1 then
109,544✔
282
            language = MacintoshLanguages[record.language]
54,092✔
283
         elseif record.language < 0x8000 and record.platform == 3 then
55,452✔
284
            language = WindowsLanguages[record.language]
55,452✔
285
         end
286
      end
287
      name.records[i].language = language
109,544✔
288
   end
289
   for i = 1, name.count do
109,821✔
290
      local record = name.records[i]
109,544✔
291
      local language = record.language
109,544✔
292
      if language then
109,544✔
293
         if not names[record.name] then
109,544✔
294
            names[record.name] = {}
55,595✔
295
         end
296
         if record.length > 0 then
109,544✔
297
            names[record.name][language] =
109,544✔
298
               vstruct.read(">@" .. name.sOffset + record.offset .. "s" .. record.length, fd)
219,088✔
299
            if record.platform == 3 then
109,544✔
300
               names[record.name][language] = { SU.utf16be_to_utf8(names[record.name][language][1]) }
110,904✔
301
            end
302
         end
303
      end
304
   end
305

306
   return names
277✔
307
end
308

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

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

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

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

NEW
329
   local colr = {}
×
330

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

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

NEW
346
   return colr
×
347
end
348

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

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

NEW
360
   local cpal = {}
×
361

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

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

NEW
378
   return cpal
×
379
end
380

381
local function parseSvg (str)
382
   if str:len() <= 0 then
554✔
383
      return
277✔
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
626✔
NEW
410
      return
×
411
   end
412
   local fd = vstruct.cursor(s)
313✔
413
   return vstruct.read(
313✔
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",
313✔
415
      fd
416
   )
313✔
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)
405✔
442
   if coverageFormat == 1 then
405✔
443
      local glyphCount = vstruct.readvals("> u2", fd)
81✔
444
      return vstruct.read("> " .. glyphCount .. "*u2", fd)
81✔
445
   elseif coverageFormat == 2 then
324✔
446
      local rangeCount = vstruct.readvals("> u2", fd)
324✔
447
      local ranges = vstruct.read("> " .. rangeCount .. "*{ &RangeRecord }", fd)
324✔
448
      local coverage = {}
324✔
449
      for i = 1, #ranges do
9,558✔
450
         for glyphID = ranges[i].startGlyphID, ranges[i].endGlyphID do
142,965✔
451
            local index = ranges[i].startCoverageIndex + glyphID - ranges[i].startGlyphID + 1 -- array in lua is one-based
133,731✔
452
            if coverage[index] then
133,731✔
NEW
453
               SU.error(glyphID .. " already exist in converage when processing " .. ranges[i])
×
454
            end
455
            coverage[index] = glyphID
133,731✔
456
         end
457
      end
458
      return coverage
324✔
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 }
119,394✔
468
   if record.deviceTableOffset ~= 0 then
119,394✔
NEW
469
      newRecord.deviceTable = parseDeviceTable(parent_offset + record.deviceTableOffset, fd)
×
470
   end
471
   return newRecord
119,394✔
472
end
473

474
local parseConstants = function (offset, fd)
475
   local mathConstantNames = {
81✔
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 = {
81✔
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
81✔
592
   for i = 1, #mathConstantNames do
4,617✔
593
      mathConstantFormat = mathConstantFormat .. " " .. mathConstantNames[i] .. ":" .. mathConstantTypes[i]
4,536✔
594
   end
595
   local mathConstants = vstruct.read(mathConstantFormat, fd)
81✔
596
   for k, v in pairs(mathConstants) do
4,617✔
597
      if v and type(v) == "table" then
4,536✔
598
         mathConstants[k] = fetchMathValueRecord(v, offset, fd)
8,262✔
599
      end
600
   end
601
   return mathConstants
81✔
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)
162✔
625
   local coverageTable = parseCoverage(offset + coverageOffset, fd)
162✔
626
   local count = vstruct.readvals(">@" .. (offset + 2) .. " u2", fd)
162✔
627
   if count ~= #coverageTable then
162✔
NEW
628
      SU.error("Coverage table corrupted")
×
629
   end
630
   local table = vstruct.read("> " .. count .. "*{ " .. type .. " }", fd)
162✔
631
   local result = {}
162✔
632
   for i = 1, count do
112,428✔
633
      if type == "&MathValueRecord" then
112,266✔
634
         result[coverageTable[i]] = fetchMathValueRecord(table[i], offset, fd)
224,532✔
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
162✔
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,997✔
661
      assembly.italicsCorrection = fetchMathValueRecord(assembly.italicsCorrection, inner_offset, inner_fd)
5,994✔
662
      assembly.partRecords = vstruct.read("> " .. assembly.partCount .. "*{ &GlyphPartRecord }", inner_fd)
5,994✔
663
      assembly.partCount = nil
2,997✔
664
      return assembly
2,997✔
665
   end
666
   local parseMathGlyphConstruction = function (inner_offset, inner_fd)
667
      local construction = vstruct.read(">@" .. inner_offset .. " glyphAssemblyOffset:u2 variantCount:u2", inner_fd)
6,156✔
668
      local mathGlyphVariantRecord =
669
         vstruct.read("> " .. construction.variantCount .. "*{ &MathGlyphVariantRecord }", inner_fd)
6,156✔
670
      return {
6,156✔
671
         glyphAssembly = construction.glyphAssemblyOffset ~= 0
6,156✔
672
               and parseGlyphAssembly(inner_offset + construction.glyphAssemblyOffset, inner_fd)
2,997✔
673
            or nil,
6,156✔
674
         mathGlyphVariantRecord = mathGlyphVariantRecord,
6,156✔
675
      }
6,156✔
676
   end
677
   local variants = vstruct.read(
162✔
678
      ">@"
679
         .. offset
81✔
680
         .. " minConnectorOverlap:u2 vertGlyphCoverageOffset:u2 horizGlyphCoverageOffset:u2 vertGlyphCount:u2 horizGlyphCount:u2",
81✔
681
      fd
682
   )
81✔
683
   local vertGlyphConstructionOffsets = vstruct.read("> " .. variants.vertGlyphCount .. "*u2", fd)
81✔
684
   local horizGlyphConstructionOffsets = vstruct.read("> " .. variants.horizGlyphCount .. "*u2", fd)
81✔
685
   local vertGlyphCoverage = {}
81✔
686
   if variants.vertGlyphCoverageOffset > 0 then
81✔
687
      vertGlyphCoverage = parseCoverage(offset + variants.vertGlyphCoverageOffset, fd)
162✔
688
   end
689
   local horizGlyphCoverage = {}
81✔
690
   if variants.horizGlyphCoverageOffset > 0 then
81✔
691
      horizGlyphCoverage = parseCoverage(offset + variants.horizGlyphCoverageOffset, fd)
162✔
692
   end
693
   if variants.vertGlyphCount ~= #vertGlyphCoverage or variants.horizGlyphCount ~= #horizGlyphCoverage then
81✔
NEW
694
      SU.error("MathVariants Table corrupted")
×
695
   end
696
   local vertGlyphConstructions = {}
81✔
697
   local horizGlyphConstructions = {}
81✔
698
   for i = 1, variants.vertGlyphCount do
4,050✔
699
      vertGlyphConstructions[vertGlyphCoverage[i]] =
3,969✔
700
         parseMathGlyphConstruction(offset + vertGlyphConstructionOffsets[i], fd)
7,938✔
701
   end
702
   for i = 1, variants.horizGlyphCount do
2,268✔
703
      horizGlyphConstructions[horizGlyphCoverage[i]] =
2,187✔
704
         parseMathGlyphConstruction(offset + horizGlyphConstructionOffsets[i], fd)
4,374✔
705
   end
706
   return {
81✔
707
      minConnectorOverlap = variants.minConnectorOverlap,
81✔
708
      vertGlyphConstructions = vertGlyphConstructions,
81✔
709
      horizGlyphConstructions = horizGlyphConstructions,
81✔
710
   }
81✔
711
end
712

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

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

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

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

824
local parseFont = function (face)
825
   if not face.font then
6,444✔
826
      local font = {}
277✔
827
      font.head = parseHead(hb.get_table(face, "head"))
554✔
828
      font.names = parseName(hb.get_table(face, "name"))
554✔
829
      font.maxp = parseMaxp(hb.get_table(face, "maxp"))
554✔
830
      font.colr = parseColr(hb.get_table(face, "COLR"))
554✔
831
      font.cpal = parseCpal(hb.get_table(face, "CPAL"))
554✔
832
      font.svg = parseSvg(hb.get_table(face, "SVG"))
554✔
833
      font.math = parseMath(hb.get_table(face, "MATH"))
554✔
834
      font.post = parsePost(hb.get_table(face, "post"))
554✔
835
      font.os2 = parseOs2(hb.get_table(face, "OS/2"))
554✔
836
      face.font = font
277✔
837
   end
838
   return face.font
6,444✔
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 }
140✔
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