f
This commit is contained in:
parent
dc773555a8
commit
cbfccea851
2 changed files with 422 additions and 2 deletions
|
@ -1,4 +1,4 @@
|
||||||
import flatty/binny, math, pixie/common, pixie/paths, tables, unicode, vmath
|
import bitops, flatty/binny, math, pixie/common, pixie/paths, tables, unicode, vmath
|
||||||
|
|
||||||
## See https://docs.microsoft.com/en-us/typography/opentype/spec/
|
## See https://docs.microsoft.com/en-us/typography/opentype/spec/
|
||||||
|
|
||||||
|
@ -165,6 +165,110 @@ type
|
||||||
offset*: uint32
|
offset*: uint32
|
||||||
length*: uint32
|
length*: uint32
|
||||||
|
|
||||||
|
# LangSys = object
|
||||||
|
# lookupOrderOffset: uint16
|
||||||
|
# requiredFeatureIndex: uint16
|
||||||
|
# featureIndexCount: uint16
|
||||||
|
# featureIndices: seq[uint16]
|
||||||
|
|
||||||
|
# LangSysRecord = object
|
||||||
|
# langSysTag: string
|
||||||
|
# langSysOffset: uint16
|
||||||
|
# langSys: LangSys
|
||||||
|
|
||||||
|
# Script = object
|
||||||
|
# defaultLangSysOffset: uint16
|
||||||
|
# langSysCount: uint16
|
||||||
|
# langSysRecords: seq[LangSysRecord]
|
||||||
|
|
||||||
|
# ScriptRecord = object
|
||||||
|
# scriptTag: string
|
||||||
|
# scriptOffset: uint16
|
||||||
|
# script: Script
|
||||||
|
|
||||||
|
# ScriptList = object
|
||||||
|
# scriptCount: uint16
|
||||||
|
# scriptRecords: seq[ScriptRecord]
|
||||||
|
|
||||||
|
# Feature = object
|
||||||
|
# featureParamsOffset: uint16
|
||||||
|
# lookupIndexCount: uint16
|
||||||
|
# lookupListIndices: seq[uint16]
|
||||||
|
|
||||||
|
# FeatureRecord = object
|
||||||
|
# featureTag: string
|
||||||
|
# featureOffset: uint16
|
||||||
|
# feature: Feature
|
||||||
|
|
||||||
|
# FeatureList = object
|
||||||
|
# featureCount: uint16
|
||||||
|
# featureRecords: seq[FeatureRecord]
|
||||||
|
|
||||||
|
RangeRecord = object
|
||||||
|
startGlyphID: uint16
|
||||||
|
endGlyphID: uint16
|
||||||
|
startCoverageIndex: uint16
|
||||||
|
|
||||||
|
Coverage = object
|
||||||
|
coverageFormat: uint16
|
||||||
|
glyphCount: uint16
|
||||||
|
glyphArray: seq[uint16]
|
||||||
|
rangeCount: uint16
|
||||||
|
rangeRecords: seq[RangeRecord]
|
||||||
|
|
||||||
|
ValueRecord = object
|
||||||
|
xPlacement: int16
|
||||||
|
yPlacement: int16
|
||||||
|
xAdvance: int16
|
||||||
|
yAdvance: int16
|
||||||
|
xPlaDeviceOffset: uint16
|
||||||
|
yPlaDeviceOffset: uint16
|
||||||
|
xAdvDeviceOffset: uint16
|
||||||
|
yAdvDeviceOffset: uint16
|
||||||
|
|
||||||
|
PairValueRecord = object
|
||||||
|
secondGlyph: uint16
|
||||||
|
valueRecord1: ValueRecord
|
||||||
|
valueRecord2: ValueRecord
|
||||||
|
|
||||||
|
PairSet = object
|
||||||
|
pairValueCount: uint16
|
||||||
|
pairValueRecords: seq[PairValueRecord]
|
||||||
|
|
||||||
|
PairPos = ref object
|
||||||
|
posFormat: uint16
|
||||||
|
coverageOffset: uint16
|
||||||
|
valueFormat1: uint16
|
||||||
|
valueFormat2: uint16
|
||||||
|
pairSetCount: uint16
|
||||||
|
pairSetOffsets: seq[uint16]
|
||||||
|
pairSets: seq[PairSet]
|
||||||
|
coverage: Coverage
|
||||||
|
|
||||||
|
Lookup = object
|
||||||
|
lookupType: uint16
|
||||||
|
lookupFlag: uint16
|
||||||
|
subTableCount: uint16
|
||||||
|
subTableOffsets: seq[uint16]
|
||||||
|
markFilteringSet: uint16
|
||||||
|
|
||||||
|
LookupList = object
|
||||||
|
lookupCount: uint16
|
||||||
|
lookupOffsets: seq[uint16]
|
||||||
|
lookups: seq[Lookup]
|
||||||
|
pairPos: PairPos
|
||||||
|
|
||||||
|
GposTable = ref object
|
||||||
|
majorVersion: uint16
|
||||||
|
minorVersion: uint16
|
||||||
|
scriptListOffset: uint16
|
||||||
|
featureListOffset: uint16
|
||||||
|
lookupListOffset: uint16
|
||||||
|
featureVariationsOffset: uint32
|
||||||
|
# scriptList: ScriptList
|
||||||
|
# featureList: FeatureList
|
||||||
|
lookupList: LookupList
|
||||||
|
|
||||||
OpenType* = ref object
|
OpenType* = ref object
|
||||||
buf*: string
|
buf*: string
|
||||||
version*: uint32
|
version*: uint32
|
||||||
|
@ -183,6 +287,7 @@ type
|
||||||
loca*: LocaTable
|
loca*: LocaTable
|
||||||
glyf*: GlyfTable
|
glyf*: GlyfTable
|
||||||
kern*: KernTable
|
kern*: KernTable
|
||||||
|
gpos*: GposTable
|
||||||
glyphPaths: Table[Rune, Path]
|
glyphPaths: Table[Rune, Path]
|
||||||
kerningPairs: Table[(Rune, Rune), float32]
|
kerningPairs: Table[(Rune, Rune), float32]
|
||||||
|
|
||||||
|
@ -574,6 +679,303 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
||||||
else:
|
else:
|
||||||
failUnsupported()
|
failUnsupported()
|
||||||
|
|
||||||
|
# proc parseLangSys(buf: string, offset: int): LangSys =
|
||||||
|
# var i = offset
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 6)
|
||||||
|
|
||||||
|
# result.lookupOrderOffset = buf.readUint16(i + 0).swap()
|
||||||
|
# result.requiredFeatureIndex = buf.readUint16(i + 2).swap()
|
||||||
|
# result.featureIndexCount = buf.readUint16(i + 4).swap()
|
||||||
|
# i += 6
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 2 * result.featureIndexCount.int)
|
||||||
|
|
||||||
|
# result.featureIndices = buf.readUint16Seq(i, result.featureIndexCount.int)
|
||||||
|
|
||||||
|
# proc parseScript(buf: string, offset: int): Script =
|
||||||
|
# var i = offset
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 4)
|
||||||
|
|
||||||
|
# result.defaultLangSysOffset = buf.readUint16(i + 0).swap()
|
||||||
|
# result.langSysCount = buf.readUint16(i + 2).swap()
|
||||||
|
# i += 4
|
||||||
|
|
||||||
|
# buf.eofCheck(i + result.langSysCount.int * 6)
|
||||||
|
|
||||||
|
# for _ in 0 ..< result.langSysCount.int:
|
||||||
|
# var langSysRecord: LangSysRecord
|
||||||
|
# langSysRecord.langSysTag = buf.readStr(i + 0, 4)
|
||||||
|
# langSysRecord.langSysOffset = buf.readUint16(i + 4).swap()
|
||||||
|
# langSysRecord.langSys = parseLangSys(
|
||||||
|
# buf, offset + langSysRecord.langSysOffset.int
|
||||||
|
# )
|
||||||
|
# result.langSysRecords.add(langSysRecord)
|
||||||
|
# i += 6
|
||||||
|
|
||||||
|
# proc parseScriptList(buf: string, offset: int): ScriptList =
|
||||||
|
# var i = offset
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 2)
|
||||||
|
|
||||||
|
# result.scriptCount = buf.readUint16(i + 0).swap()
|
||||||
|
# i += 2
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 6 * result.scriptCount.int)
|
||||||
|
|
||||||
|
# for _ in 0 ..< result.scriptCount.int:
|
||||||
|
# var scriptRecord: ScriptRecord
|
||||||
|
# scriptRecord.scriptTag = buf.readStr(i + 0, 4)
|
||||||
|
# scriptRecord.scriptOffset = buf.readUint16(i + 4).swap()
|
||||||
|
# scriptRecord.script = parseScript(
|
||||||
|
# buf, offset + scriptRecord.scriptOffset.int
|
||||||
|
# )
|
||||||
|
# result.scriptRecords.add(scriptRecord)
|
||||||
|
# i += 6
|
||||||
|
|
||||||
|
# proc parseFeature(buf: string, offset: int): Feature =
|
||||||
|
# var i = offset
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 4)
|
||||||
|
|
||||||
|
# result.featureParamsOffset = buf.readUint16(i + 0).swap()
|
||||||
|
# result.lookupIndexCount = buf.readUint16(i + 2).swap()
|
||||||
|
# i += 4
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 2 * result.lookupIndexCount.int)
|
||||||
|
|
||||||
|
# result.lookupListIndices = buf.readUint16Seq(i, result.lookupIndexCount.int)
|
||||||
|
|
||||||
|
# proc parseFeatureList(buf: string, offset: int): FeatureList =
|
||||||
|
# var i = offset
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 2)
|
||||||
|
|
||||||
|
# result.featureCount = buf.readUint16(i + 0).swap()
|
||||||
|
# i += 2
|
||||||
|
|
||||||
|
# buf.eofCheck(i + 6 * result.featureCount.int)
|
||||||
|
|
||||||
|
# for _ in 0 ..< result.featureCount.int:
|
||||||
|
# var featureRecord: FeatureRecord
|
||||||
|
# featureRecord.featureTag = buf.readStr(i + 0, 4)
|
||||||
|
# featureRecord.featureOffset = buf.readUint16(i + 4).swap()
|
||||||
|
# featureRecord.feature = parseFeature(
|
||||||
|
# buf, offset + featureRecord.featureOffset.int
|
||||||
|
# )
|
||||||
|
# result.featureRecords.add(featureRecord)
|
||||||
|
# i += 6
|
||||||
|
|
||||||
|
proc parseRangeRecord(buf: string, offset: int): RangeRecord =
|
||||||
|
buf.eofCheck(offset + 6)
|
||||||
|
|
||||||
|
result.startGlyphID = buf.readUint16(offset + 0).swap()
|
||||||
|
result.endGlyphID = buf.readUint16(offset + 2).swap()
|
||||||
|
result.startCoverageIndex = buf.readUint16(offset + 4).swap()
|
||||||
|
|
||||||
|
proc parseCoverage(buf: string, offset: int): Coverage =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 4)
|
||||||
|
|
||||||
|
result.coverageFormat = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
case result.coverageFormat:
|
||||||
|
of 1:
|
||||||
|
result.glyphCount = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
buf.eofCheck(i + result.glyphCount.int * 2)
|
||||||
|
|
||||||
|
result.glyphArray = buf.readUint16Seq(i, result.glyphCount.int)
|
||||||
|
of 2:
|
||||||
|
result.rangeCount = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
result.rangeRecords.setLen(result.rangeCount.int)
|
||||||
|
for j in 0 ..< result.rangeCount.int:
|
||||||
|
result.rangeRecords[j] =
|
||||||
|
parseRangeRecord(buf, i + j * sizeof(RangeRecord))
|
||||||
|
else:
|
||||||
|
failUnsupported()
|
||||||
|
|
||||||
|
proc parseValueRecord(
|
||||||
|
buf: string, offset: int, valueFormat: uint16
|
||||||
|
): ValueRecord =
|
||||||
|
buf.eofCheck(offset + countSetBits(valueFormat) * 2)
|
||||||
|
|
||||||
|
var i = offset
|
||||||
|
if (valueFormat and 0b1) != 0:
|
||||||
|
result.xPlacement = buf.readInt16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b10) != 0:
|
||||||
|
result.yPlacement = buf.readInt16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b100) != 0:
|
||||||
|
result.xAdvance = buf.readInt16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b1000) != 0:
|
||||||
|
result.yAdvance = buf.readInt16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b10000) != 0:
|
||||||
|
result.xPlaDeviceOffset = buf.readUint16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b100000) != 0:
|
||||||
|
result.yPlaDeviceOffset = buf.readUint16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b1000000) != 0:
|
||||||
|
result.xAdvDeviceOffset = buf.readUint16(i).swap()
|
||||||
|
i += 2
|
||||||
|
if (valueFormat and 0b10000000) != 0:
|
||||||
|
result.yAdvDeviceOffset = buf.readUint16(i).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
proc parsePairValueRecord(
|
||||||
|
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||||
|
): PairValueRecord =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2)
|
||||||
|
|
||||||
|
result.secondGlyph = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
result.valueRecord1 = parseValueRecord(buf, i, valueFormat1)
|
||||||
|
result.valueRecord1 =
|
||||||
|
parseValueRecord(buf, i + countSetBits(valueFormat1) * 2, valueFormat2)
|
||||||
|
|
||||||
|
proc parsePairSet(
|
||||||
|
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||||
|
): PairSet =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2)
|
||||||
|
|
||||||
|
result.pairValueCount = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
let pairValueRecordSize =
|
||||||
|
2 + (countSetBits(valueFormat1) + countSetBits(valueFormat2)) * 2
|
||||||
|
|
||||||
|
result.pairValueRecords.setLen(result.pairValueCount.int)
|
||||||
|
for j in 0 ..< result.pairValueCount.int:
|
||||||
|
result.pairValueRecords[j] = parsePairValueRecord(
|
||||||
|
buf, i + j * pairValueRecordSize, valueFormat1, valueFormat2
|
||||||
|
)
|
||||||
|
|
||||||
|
proc parsePairPos(buf: string, offset: int): PairPos =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 4)
|
||||||
|
|
||||||
|
let posFormat = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
case posFormat:
|
||||||
|
of 1:
|
||||||
|
result = PairPos()
|
||||||
|
result.posFormat = posFormat
|
||||||
|
|
||||||
|
buf.eofCheck(i + 8)
|
||||||
|
|
||||||
|
result.coverageOffset = buf.readUint16(i + 0).swap()
|
||||||
|
result.valueFormat1 = buf.readUint16(i + 2).swap()
|
||||||
|
result.valueFormat2 = buf.readUint16(i + 4).swap()
|
||||||
|
result.pairSetCount = buf.readUint16(i + 6).swap()
|
||||||
|
i += 8
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2 * result.pairSetCount.int)
|
||||||
|
|
||||||
|
let pairSetOffsets = buf.readUint16Seq(i + 0, result.pairSetCount.int)
|
||||||
|
i += 2 * result.pairSetCount.int
|
||||||
|
|
||||||
|
result.pairSets.setLen(result.pairSetCount.int)
|
||||||
|
for j in 0 ..< result.pairSetCount.int:
|
||||||
|
result.pairSets[j] = parsePairSet(
|
||||||
|
buf,
|
||||||
|
offset + pairSetOffsets[j].int,
|
||||||
|
result.valueFormat1,
|
||||||
|
result.valueFormat2
|
||||||
|
)
|
||||||
|
|
||||||
|
result.coverage = parseCoverage(buf, offset + result.coverageOffset.int)
|
||||||
|
of 2:
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
failUnsupported()
|
||||||
|
|
||||||
|
proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 6)
|
||||||
|
|
||||||
|
result.lookupType = buf.readUint16(i + 0).swap()
|
||||||
|
result.lookupFlag = buf.readUint16(i + 2).swap()
|
||||||
|
result.subTableCount = buf.readUint16(i + 4).swap()
|
||||||
|
i += 6
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2 * result.subTableCount.int)
|
||||||
|
|
||||||
|
result.subTableOffsets = buf.readUint16Seq(i, result.subTableCount.int)
|
||||||
|
i += 2 * result.subTableCount.int
|
||||||
|
|
||||||
|
if (result.lookupFlag and 0x0010) != 0: # USE_MARK_FILTERING_SET
|
||||||
|
buf.eofCheck(i + 2)
|
||||||
|
result.markFilteringSet = buf.readUint16(i).swap()
|
||||||
|
|
||||||
|
for subTableOffset in result.subTableOffsets:
|
||||||
|
if result.lookupType == 2:
|
||||||
|
gpos.lookupList.pairPos =
|
||||||
|
parsePairPos(buf, offset + subTableOffset.int)
|
||||||
|
|
||||||
|
proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2)
|
||||||
|
|
||||||
|
result.lookupCount = buf.readUint16(i + 0).swap()
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
buf.eofCheck(i + 2 * result.lookupCount.int)
|
||||||
|
|
||||||
|
result.lookupOffsets = buf.readUint16Seq(i, result.lookupCount.int)
|
||||||
|
|
||||||
|
for lookupOffset in result.lookupoffsets:
|
||||||
|
result.lookups.add(parseLookup(buf, offset + lookupOffset.int, gpos))
|
||||||
|
|
||||||
|
proc parseGposTable(buf: string, offset: int): GPOSTable =
|
||||||
|
var i = offset
|
||||||
|
|
||||||
|
buf.eofCheck(i + 10)
|
||||||
|
|
||||||
|
result = GPOSTable()
|
||||||
|
result.majorVersion = buf.readUint16(i + 0).swap()
|
||||||
|
result.minorVersion = buf.readUint16(i + 2).swap()
|
||||||
|
result.scriptListOffset = buf.readUint16(i + 4).swap()
|
||||||
|
result.featureListOffset = buf.readUint16(i + 6).swap()
|
||||||
|
result.lookupListOffset = buf.readUint16(i + 8).swap()
|
||||||
|
i += 10
|
||||||
|
|
||||||
|
if result.majorVersion != 1:
|
||||||
|
failUnsupported()
|
||||||
|
|
||||||
|
if result.minorVersion == 0:
|
||||||
|
discard
|
||||||
|
elif result.minorVersion == 1:
|
||||||
|
buf.eofCheck(i + 4)
|
||||||
|
result.featureVariationsOffset = buf.readUint32(i + 0).swap()
|
||||||
|
i += 4
|
||||||
|
else:
|
||||||
|
failUnsupported()
|
||||||
|
|
||||||
|
# result.scriptList = parseScriptList(buf, offset + result.scriptListOffset.int)
|
||||||
|
# result.featureList =
|
||||||
|
# parseFeatureList(buf, offset + result.featureListOffset.int)
|
||||||
|
result.lookupList =
|
||||||
|
parseLookupList(buf, offset + result.lookupListOffset.int, result)
|
||||||
|
|
||||||
proc getGlyphId(opentype: OpenType, rune: Rune): uint16 =
|
proc getGlyphId(opentype: OpenType, rune: Rune): uint16 =
|
||||||
if rune in opentype.cmap.runeToGlyphId:
|
if rune in opentype.cmap.runeToGlyphId:
|
||||||
result = opentype.cmap.runeToGlyphId[rune]
|
result = opentype.cmap.runeToGlyphId[rune]
|
||||||
|
@ -907,6 +1309,7 @@ proc parseOpenType*(buf: string): OpenType =
|
||||||
if "kern" in result.tableRecords:
|
if "kern" in result.tableRecords:
|
||||||
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
||||||
|
|
||||||
|
if result.kern != nil:
|
||||||
for table in result.kern.subTables:
|
for table in result.kern.subTables:
|
||||||
if (table.coverage and 1) != 0: # Horizontal data
|
if (table.coverage and 1) != 0: # Horizontal data
|
||||||
for pair in table.kernPairs:
|
for pair in table.kernPairs:
|
||||||
|
@ -925,5 +1328,21 @@ proc parseOpenType*(buf: string): OpenType =
|
||||||
value += result.kerningPairs[key]
|
value += result.kerningPairs[key]
|
||||||
result.kerningPairs[key] = value
|
result.kerningPairs[key] = value
|
||||||
|
|
||||||
|
if "GPOS" in result.tableRecords:
|
||||||
|
result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
|
||||||
|
|
||||||
|
if result.gpos != nil and result.gpos.lookupList.pairPos != nil:
|
||||||
|
case result.gpos.lookupList.pairPos.coverage.coverageFormat:
|
||||||
|
of 1:
|
||||||
|
echo result.gpos.lookupList.pairPos.coverage.glyphCount
|
||||||
|
of 2:
|
||||||
|
echo result.gpos.lookupList.pairPos.coverage.rangeCount
|
||||||
|
else:
|
||||||
|
failUnsupported()
|
||||||
|
|
||||||
|
for pairSet in result.gpos.lookupList.pairPos.pairSets:
|
||||||
|
for pairValue in pairSet.pairValueRecords:
|
||||||
|
discard
|
||||||
|
|
||||||
when defined(release):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import common, pixie
|
import common, pixie, flatty/memoryused
|
||||||
|
|
||||||
# Clone https://github.com/google/fonts
|
# Clone https://github.com/google/fonts
|
||||||
# Check out commit ebaa6a7aab9b700da4e30a4682687acdf427eae7
|
# Check out commit ebaa6a7aab9b700da4e30a4682687acdf427eae7
|
||||||
|
@ -8,3 +8,4 @@ let fontPaths = findAllFonts("../fonts")
|
||||||
for fontPath in fontPaths:
|
for fontPath in fontPaths:
|
||||||
echo fontPath
|
echo fontPath
|
||||||
let font = readFont(fontPath)
|
let font = readFont(fontPath)
|
||||||
|
# echo memoryUsed(font)
|
||||||
|
|
Loading…
Reference in a new issue