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

sile-typesetter / sile / 9324025270

31 May 2024 08:35PM UTC coverage: 66.168% (-8.0%) from 74.124%
9324025270

push

github

web-flow
Merge 235329972 into 70ff5c335

1753 of 2583 new or added lines in 108 files covered. (67.87%)

1498 existing lines in 74 files now uncovered.

11404 of 17235 relevant lines covered (66.17%)

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

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

11
   local names = {}
164✔
12
   local MacintoshLanguages = {
164✔
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 = {
164✔
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)
164✔
272
   name.records = {}
164✔
273
   if name.format == 1 then
164✔
274
      return
×
275
   end
276
   for i = 1, name.count do
56,805✔
277
      local record = vstruct.read(">platform:u2 encoding:u2 language:u2 name:u2 length:u2 offset:u2", fd)
56,641✔
278
      name.records[i] = record
56,641✔
279
      local language
280
      if (record.platform == 1 and record.encoding == 0) or (record.platform == 3 and record.encoding == 1) then
56,641✔
281
         if record.language < 0x8000 and record.platform == 1 then
56,641✔
282
            language = MacintoshLanguages[record.language]
27,807✔
283
         elseif record.language < 0x8000 and record.platform == 3 then
28,834✔
284
            language = WindowsLanguages[record.language]
28,834✔
285
         end
286
      end
287
      name.records[i].language = language
56,641✔
288
   end
289
   for i = 1, name.count do
56,805✔
290
      local record = name.records[i]
56,641✔
291
      local language = record.language
56,641✔
292
      if language then
56,641✔
293
         if not names[record.name] then
56,641✔
294
            names[record.name] = {}
28,917✔
295
         end
296
         if record.length > 0 then
56,641✔
297
            names[record.name][language] =
56,641✔
298
               vstruct.read(">@" .. name.sOffset + record.offset .. "s" .. record.length, fd)
113,282✔
299
            if record.platform == 3 then
56,641✔
300
               names[record.name][language] = { SU.utf16be_to_utf8(names[record.name][language][1]) }
57,668✔
301
            end
302
         end
303
      end
304
   end
305

306
   return names
164✔
307
end
308

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

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

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

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

UNCOV
329
   local colr = {}
×
330

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

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

UNCOV
346
   return colr
×
347
end
348

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

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

UNCOV
360
   local cpal = {}
×
361

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

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

UNCOV
378
   return cpal
×
379
end
380

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

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

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

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

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

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

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