tests
|
@ -1,4 +1,4 @@
|
|||
import bitops, flatty/binny, math, pixie/common, pixie/paths, tables, unicode, vmath
|
||||
import bitops, flatty/binny, math, pixie/common, pixie/paths, sets, tables, unicode, vmath
|
||||
|
||||
## See https://docs.microsoft.com/en-us/typography/opentype/spec/
|
||||
|
||||
|
@ -158,6 +158,7 @@ type
|
|||
version*: uint16
|
||||
nTables*: uint16
|
||||
subTables*: seq[KernSubTable]
|
||||
kerningPairs: Table[(uint16, uint16), float32]
|
||||
|
||||
TableRecord* = object
|
||||
tag*: string
|
||||
|
@ -215,6 +216,7 @@ type
|
|||
glyphArray: seq[uint16]
|
||||
rangeCount: uint16
|
||||
rangeRecords: seq[RangeRecord]
|
||||
coveredGlyphs: HashSet[uint16]
|
||||
|
||||
ValueRecord = object
|
||||
xPlacement: int16
|
||||
|
@ -271,6 +273,10 @@ type
|
|||
classDef1: ClassDef
|
||||
classDef2: ClassDef
|
||||
coverage: Coverage
|
||||
glyphIdToClass1: Table[uint16, uint16]
|
||||
glyphIdToClass2: Table[uint16, uint16]
|
||||
classPairAdjustments: Table[(uint16, uint16), int16]
|
||||
glyphPairAdjustments: Table[(uint16, uint16), int16]
|
||||
|
||||
Lookup = object
|
||||
lookupType: uint16
|
||||
|
@ -283,7 +289,7 @@ type
|
|||
lookupCount: uint16
|
||||
lookupOffsets: seq[uint16]
|
||||
lookups: seq[Lookup]
|
||||
pairPos: PairPos
|
||||
pairPosTables: seq[PairPos]
|
||||
|
||||
GposTable = ref object
|
||||
majorVersion: uint16
|
||||
|
@ -316,7 +322,6 @@ type
|
|||
kern*: KernTable
|
||||
gpos*: GposTable
|
||||
glyphPaths: Table[Rune, Path]
|
||||
kerningPairs: Table[(Rune, Rune), float32]
|
||||
|
||||
when defined(release):
|
||||
{.push checks: off.}
|
||||
|
@ -657,7 +662,7 @@ proc parseGlyfTable(buf: string, offset: int, loca: LocaTable): GlyfTable =
|
|||
for glyphId in 0 ..< loca.offsets.len:
|
||||
result.offsets[glyphId] = offset.uint32 + loca.offsets[glyphId]
|
||||
|
||||
proc parseKernTable(buf: string, offset: int): KernTable =
|
||||
proc parseKernTable(buf: string, offset: int, cmap: CmapTable): KernTable =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
@ -701,6 +706,22 @@ proc parseKernTable(buf: string, offset: int): KernTable =
|
|||
i += 6
|
||||
|
||||
result.subTables.add(subTable)
|
||||
|
||||
for table in result.subTables:
|
||||
if (table.coverage and 1) != 0: # Horizontal data
|
||||
for pair in table.kernPairs:
|
||||
if pair.value != 0 and
|
||||
pair.left in cmap.glyphIdToRune and
|
||||
pair.right in cmap.glyphIdToRune:
|
||||
let key = (pair.left, pair.right)
|
||||
var value = pair.value.float32
|
||||
if key in result.kerningPairs:
|
||||
if (table.coverage and 0b1000) != 0: # Override
|
||||
discard
|
||||
else: # Accumulate
|
||||
value += result.kerningPairs[key]
|
||||
result.kerningPairs[key] = value
|
||||
|
||||
elif version == 1:
|
||||
discard # Mac format
|
||||
else:
|
||||
|
@ -817,6 +838,10 @@ proc parseCoverage(buf: string, offset: int): Coverage =
|
|||
buf.eofCheck(i + result.glyphCount.int * 2)
|
||||
|
||||
result.glyphArray = buf.readUint16Seq(i, result.glyphCount.int)
|
||||
|
||||
for ci, glyphId in result.glyphArray:
|
||||
result.coveredGlyphs.incl(glyphId)
|
||||
|
||||
of 2:
|
||||
result.rangeCount = buf.readUint16(i + 0).swap()
|
||||
i += 2
|
||||
|
@ -825,6 +850,13 @@ proc parseCoverage(buf: string, offset: int): Coverage =
|
|||
for j in 0 ..< result.rangeCount.int:
|
||||
result.rangeRecords[j] =
|
||||
parseRangeRecord(buf, i + j * sizeof(RangeRecord))
|
||||
|
||||
for rangeRecord in result.rangeRecords:
|
||||
var ci = rangeRecord.startCoverageIndex.int
|
||||
for glyphId in rangeRecord.startGlyphID .. rangeRecord.endGlyphID:
|
||||
result.coveredGlyphs.incl(glyphId)
|
||||
inc ci
|
||||
|
||||
else:
|
||||
failUnsupported()
|
||||
|
||||
|
@ -870,7 +902,7 @@ proc parsePairValueRecord(
|
|||
i += 2
|
||||
|
||||
result.valueRecord1 = parseValueRecord(buf, i, valueFormat1)
|
||||
result.valueRecord1 =
|
||||
result.valueRecord2 =
|
||||
parseValueRecord(buf, i + countSetBits(valueFormat1) * 2, valueFormat2)
|
||||
|
||||
proc parsePairSet(
|
||||
|
@ -889,14 +921,21 @@ proc parsePairSet(
|
|||
result.pairValueRecords.setLen(result.pairValueCount.int)
|
||||
for j in 0 ..< result.pairValueCount.int:
|
||||
result.pairValueRecords[j] = parsePairValueRecord(
|
||||
buf, i + j * pairValueRecordSize, valueFormat1, valueFormat2
|
||||
buf, i, valueFormat1, valueFormat2
|
||||
)
|
||||
i += pairValueRecordSize
|
||||
|
||||
proc parseClass2Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||
): Class2Record =
|
||||
let class2RecordSize = (
|
||||
countSetBits(valueFormat1) + countSetBits(valueFormat2)
|
||||
) * 2
|
||||
buf.eofCheck(offset + class2RecordSize)
|
||||
result.valueRecord1 = parseValueRecord(buf, offset, valueFormat1)
|
||||
result.valueRecord2 = parseValueRecord(buf, offset, valueFormat2)
|
||||
result.valueRecord2 = parseValueRecord(
|
||||
buf, offset + countSetBits(valueFormat1) * 2, valueFormat2
|
||||
)
|
||||
|
||||
proc parseClass1Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16
|
||||
|
@ -957,14 +996,12 @@ proc parsePairPos(buf: string, offset: int): PairPos =
|
|||
|
||||
buf.eofCheck(i + 4)
|
||||
|
||||
let posFormat = buf.readUint16(i + 0).swap()
|
||||
result = PairPos()
|
||||
result.posFormat = buf.readUint16(i + 0).swap()
|
||||
i += 2
|
||||
|
||||
case posFormat:
|
||||
case result.posFormat:
|
||||
of 1:
|
||||
result = PairPos()
|
||||
result.posFormat = posFormat
|
||||
|
||||
buf.eofCheck(i + 8)
|
||||
|
||||
result.coverageOffset = buf.readUint16(i + 0).swap()
|
||||
|
@ -988,10 +1025,35 @@ proc parsePairPos(buf: string, offset: int): PairPos =
|
|||
)
|
||||
|
||||
result.coverage = parseCoverage(buf, offset + result.coverageOffset.int)
|
||||
of 2:
|
||||
result = PairPos()
|
||||
result.posFormat = posFormat
|
||||
|
||||
if (result.valueFormat1 and 0b100) != 0:
|
||||
case result.coverage.coverageFormat:
|
||||
of 1:
|
||||
if result.coverage.glyphCount != result.pairSetCount:
|
||||
failUnsupported()
|
||||
for ci, glyphId in result.coverage.glyphArray:
|
||||
if ci < 0 or ci >= result.pairSets.len:
|
||||
failUnsupported()
|
||||
for pairValueRecord in result.pairSets[ci].pairValueRecords:
|
||||
if pairValueRecord.valueRecord1.xAdvance != 0:
|
||||
let glyphPair = (glyphId, pairValueRecord.secondGlyph)
|
||||
result.glyphPairAdjustments[glyphPair] =
|
||||
pairValueRecord.valueRecord1.xAdvance
|
||||
of 2:
|
||||
for rangeRecord in result.coverage.rangeRecords:
|
||||
var ci = rangeRecord.startCoverageIndex.int
|
||||
for glyphId in rangeRecord.startGlyphID .. rangeRecord.endGlyphID:
|
||||
if ci < 0 or ci >= result.pairSets.len:
|
||||
failUnsupported()
|
||||
for pairValueRecord in result.pairSets[ci].pairValueRecords:
|
||||
if pairValueRecord.valueRecord1.xAdvance != 0:
|
||||
let glyphPair = (glyphId, pairValueRecord.secondGlyph)
|
||||
result.glyphPairAdjustments[glyphPair] =
|
||||
pairValueRecord.valueRecord1.xAdvance
|
||||
inc ci
|
||||
else:
|
||||
discard
|
||||
of 2:
|
||||
buf.eofCheck(i + 14)
|
||||
|
||||
result.coverageOffset = buf.readUint16(i + 0).swap()
|
||||
|
@ -1013,12 +1075,52 @@ proc parsePairPos(buf: string, offset: int): PairPos =
|
|||
result.class1Records[j] = parseClass1Record(
|
||||
buf, i, result.valueFormat1, result.valueFormat2, result.class2Count
|
||||
)
|
||||
i += class2RecordSize
|
||||
i += class2RecordSize * result.class2Count.int
|
||||
|
||||
result.classDef1 = parseClassDef(buf, offset + result.classDef1Offset.int)
|
||||
result.classDef2 = parseClassDef(buf, offset + result.classDef2Offset.int)
|
||||
|
||||
result.coverage = parseCoverage(buf, offset + result.coverageOffset.int)
|
||||
|
||||
proc classDefFormat1(
|
||||
classDef: ClassDef, table: var Table[uint16, uint16]
|
||||
) =
|
||||
for i in 0.uint16 ..< classDef.glyphCount:
|
||||
if classDef.classValueArray[i] != 0:
|
||||
table[classDef.startGlyphID + i] = classDef.classValueArray[i]
|
||||
|
||||
proc classDefFormat2(
|
||||
classDef: ClassDef, table: var Table[uint16, uint16]
|
||||
) =
|
||||
for record in classDef.classRangeRecords:
|
||||
if record.startGlyphID > record.endGlyphID:
|
||||
failUnsupported()
|
||||
if record.class != 0:
|
||||
for glyphId in record.startGlyphID .. record.endGlyphID:
|
||||
table[glyphId] = record.class
|
||||
|
||||
case result.classDef1.classFormat:
|
||||
of 1:
|
||||
classDefFormat1(result.classDef1, result.glyphIdToClass1)
|
||||
of 2:
|
||||
classDefFormat2(result.classDef1, result.glyphIdToClass1)
|
||||
else:
|
||||
discard
|
||||
|
||||
case result.classDef2.classFormat:
|
||||
of 1:
|
||||
classDefFormat1(result.classDef2, result.glyphIdToClass2)
|
||||
of 2:
|
||||
classDefFormat2(result.classDef2, result.glyphIdToClass2)
|
||||
else:
|
||||
discard
|
||||
|
||||
if (result.valueFormat1 and 0b100) != 0:
|
||||
for class1, class1Record in result.class1Records:
|
||||
for class2, class2Record in class1Record.class2Records:
|
||||
if class2Record.valueRecord1.xAdvance != 0:
|
||||
result.classPairAdjustments[(class1.uint16, class2.uint16)] =
|
||||
class2Record.valueRecord1.xAdvance
|
||||
else:
|
||||
failUnsupported()
|
||||
|
||||
|
@ -1043,8 +1145,10 @@ proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup =
|
|||
|
||||
for subTableOffset in result.subTableOffsets:
|
||||
if result.lookupType == 2:
|
||||
gpos.lookupList.pairPos =
|
||||
parsePairPos(buf, offset + subTableOffset.int)
|
||||
let pairPos = parsePairPos(buf, offset + subTableOffset.int)
|
||||
if pairPos.glyphPairAdjustments.len > 0 or
|
||||
pairPos.classPairAdjustments.len > 0:
|
||||
gpos.lookupList.pairPosTables.add(pairPos)
|
||||
|
||||
proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList =
|
||||
var i = offset
|
||||
|
@ -1377,9 +1481,38 @@ proc getAdvance*(opentype: OpenType, rune: Rune): float32 =
|
|||
result = opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||
|
||||
proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
|
||||
let pair = (left, right)
|
||||
if pair in opentype.kerningPairs:
|
||||
result = opentype.kerningPairs[pair]
|
||||
let
|
||||
leftGlyphId = opentype.cmap.runeToGlyphId[left]
|
||||
rightGlyphId = opentype.cmap.runeToGlyphId[right]
|
||||
glyphPair = (leftGlyphId, rightGlyphId)
|
||||
|
||||
if opentype.gpos != nil:
|
||||
for pairPos in opentype.gpos.lookupList.pairPosTables:
|
||||
if leftGlyphId notin pairPos.coverage.coveredGlyphs:
|
||||
continue
|
||||
|
||||
case pairPos.posFormat:
|
||||
of 1:
|
||||
if glyphPair in pairPos.glyphPairAdjustments:
|
||||
result = pairPos.glyphPairAdjustments[glyphPair].float32
|
||||
break
|
||||
of 2:
|
||||
var leftClass, rightClass: uint16
|
||||
if leftGlyphId in pairPos.glyphIdToClass1:
|
||||
leftClass = pairPos.glyphIdToClass1[leftGlyphId]
|
||||
if rightGlyphId in pairPos.glyphIdToClass2:
|
||||
rightClass = pairPos.glyphIdToClass2[rightGlyphId]
|
||||
|
||||
let classPair = (leftClass, rightClass)
|
||||
if classPair in pairPos.classPairAdjustments:
|
||||
result = pairPos.classPairAdjustments[classPair].float32
|
||||
break
|
||||
else:
|
||||
discard
|
||||
|
||||
elif opentype.kern != nil:
|
||||
if glyphPair in opentype.kern.kerningPairs:
|
||||
result = opentype.kern.kerningPairs[glyphPair]
|
||||
|
||||
proc parseOpenType*(buf: string): OpenType =
|
||||
result = OpenType()
|
||||
|
@ -1427,119 +1560,15 @@ proc parseOpenType*(buf: string): OpenType =
|
|||
result.loca = parseLocaTable(
|
||||
buf, result.tableRecords["loca"].offset.int, result.head, result.maxp
|
||||
)
|
||||
result.glyf = parseGlyfTable(
|
||||
buf, result.tableRecords["glyf"].offset.int, result.loca
|
||||
)
|
||||
result.glyf =
|
||||
parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca)
|
||||
|
||||
if "kern" in result.tableRecords:
|
||||
result.kern = parseKernTable(buf, result.tableRecords["kern"].offset.int)
|
||||
result.kern =
|
||||
parseKernTable(buf, result.tableRecords["kern"].offset.int, result.cmap)
|
||||
|
||||
if result.kern != nil:
|
||||
for table in result.kern.subTables:
|
||||
if (table.coverage and 1) != 0: # Horizontal data
|
||||
for pair in table.kernPairs:
|
||||
if pair.value != 0 and
|
||||
pair.left in result.cmap.glyphIdToRune and
|
||||
pair.right in result.cmap.glyphIdToRune:
|
||||
let key = (
|
||||
result.cmap.glyphIdToRune[pair.left],
|
||||
result.cmap.glyphIdToRune[pair.right]
|
||||
)
|
||||
var value = pair.value.float32
|
||||
if key in result.kerningPairs:
|
||||
if (table.coverage and 0b1000) != 0: # Override
|
||||
discard
|
||||
else: # Accumulate
|
||||
value += result.kerningPairs[key]
|
||||
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 "GLYPH COUNT:", result.gpos.lookupList.pairPos.coverage.glyphCount
|
||||
# # of 2:
|
||||
# # echo "RANGE COUNT:", result.gpos.lookupList.pairPos.coverage.rangeCount
|
||||
# # else:
|
||||
# # failUnsupported()
|
||||
|
||||
# case result.gpos.lookupList.pairPos.posFormat:
|
||||
# of 1:
|
||||
# echo "posFormat 1 not implemented"
|
||||
# of 2:
|
||||
# proc classDefFormat1(classDef: ClassDef): Table[uint16, uint16] =
|
||||
# for i in 0.uint16 ..< classDef.glyphCount:
|
||||
# result[classDef.startGlyphID + i] = classDef.classValueArray[i]
|
||||
|
||||
# proc classDefFormat2(classDef: ClassDef): Table[uint16, uint16] =
|
||||
# for record in classDef.classRangeRecords:
|
||||
# if record.startGlyphID > record.endGlyphID:
|
||||
# failUnsupported()
|
||||
# for glyphId in record.startGlyphID .. record.endGlyphID:
|
||||
# result[glyphId] = record.class
|
||||
|
||||
# var glyphIdToClass1: Table[uint16, uint16]
|
||||
# case result.gpos.lookupList.pairPos.classDef1.classFormat:
|
||||
# of 1:
|
||||
# glyphIdToClass1 =
|
||||
# classDefFormat1(result.gpos.lookupList.pairPos.classDef1)
|
||||
# of 2:
|
||||
# glyphIdToClass1 =
|
||||
# classDefFormat2(result.gpos.lookupList.pairPos.classDef1)
|
||||
# else:
|
||||
# failUnsupported()
|
||||
|
||||
# var glyphIdToClass2: Table[uint16, uint16]
|
||||
# case result.gpos.lookupList.pairPos.classDef2.classFormat:
|
||||
# of 1:
|
||||
# glyphIdToClass2 =
|
||||
# classDefFormat1(result.gpos.lookupList.pairPos.classDef2)
|
||||
# of 2:
|
||||
# glyphIdToClass2 =
|
||||
# classDefFormat2(result.gpos.lookupList.pairPos.classDef2)
|
||||
# else:
|
||||
# failUnsupported()
|
||||
|
||||
# var runeToClass1: Table[Rune, uint16]
|
||||
# for glyphId, class in glyphIdToClass1:
|
||||
# if glyphId in result.cmap.glyphIdToRune:
|
||||
# let rune = result.cmap.glyphIdToRune[glyphId]
|
||||
# runeToClass1[rune] = class
|
||||
|
||||
# var runeToClass2: Table[Rune, uint16]
|
||||
# for glyphId, class in glyphIdToClass2:
|
||||
# if glyphId in result.cmap.glyphIdToRune:
|
||||
# let rune = result.cmap.glyphIdToRune[glyphId]
|
||||
# runeToClass2[rune] = class
|
||||
|
||||
# var classPairs: Table[(uint16, uint16), Class2Record]
|
||||
# for i, class1Record in result.gpos.lookupList.pairPos.class1Records:
|
||||
# for j, class2Record in class1Record.class2Records:
|
||||
# classPairs[(i.uint16, j.uint16)] = class2Record
|
||||
|
||||
# for left in result.cmap.runeToGlyphId.keys:
|
||||
# for right in result.cmap.runeToGlyphId.keys:
|
||||
# var leftClass, rightClass: uint16
|
||||
# if left in runeToClass1:
|
||||
# leftClass = runeToClass1[left]
|
||||
# if right in runeToClass2:
|
||||
# rightClass = runeToClass2[right]
|
||||
|
||||
# let pair = (leftClass, rightClass)
|
||||
# if pair in classPairs:
|
||||
# let classPair = classPairs[pair]
|
||||
# if classPair.valueRecord1.xAdvance != 0:
|
||||
# result.kerningPairs[(left, right)] =
|
||||
# classPair.valueRecord1.xAdvance.float32
|
||||
# else:
|
||||
# failUnsupported()
|
||||
|
||||
# echo Rune(32) in result.cmap.runeToGlyphId
|
||||
# echo getAdvance(result, Rune(32))
|
||||
# echo result.head.unitsPerEm
|
||||
# echo result.getGlyphId(Rune(32))
|
||||
if "GPOS" in result.tableRecords:
|
||||
result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
|
|
|
@ -35,21 +35,21 @@ type
|
|||
# tcSmallCaps
|
||||
# tcSmallCapsForced
|
||||
|
||||
proc ascent(typeface: Typeface): float32 {.inline.} =
|
||||
proc ascent*(typeface: Typeface): float32 {.inline.} =
|
||||
## The font ascender value in font units.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.ascender.float32
|
||||
else:
|
||||
typeface.svgFont.ascent
|
||||
|
||||
proc descent(typeface: Typeface): float32 {.inline.} =
|
||||
proc descent*(typeface: Typeface): float32 {.inline.} =
|
||||
## The font descender value in font units.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.descender.float32
|
||||
else:
|
||||
typeface.svgFont.descent
|
||||
|
||||
proc lineGap(typeface: Typeface): float32 {.inline.} =
|
||||
proc lineGap*(typeface: Typeface): float32 {.inline.} =
|
||||
## The font line gap value in font units.
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.hhea.lineGap.float32
|
||||
|
@ -61,14 +61,14 @@ proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
|||
else:
|
||||
typeface.svgFont.getGlyphPath(rune)
|
||||
|
||||
proc getAdvance(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
||||
proc getAdvance*(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
||||
## The advance for the rune in pixels.
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getAdvance(rune)
|
||||
else:
|
||||
typeface.svgFont.getAdvance(rune)
|
||||
|
||||
proc getKerningAdjustment(
|
||||
proc getKerningAdjustment*(
|
||||
typeface: Typeface, left, right: Rune
|
||||
): float32 {.inline.} =
|
||||
## The kerning adjustment for the rune pair, in pixels.
|
||||
|
|
BIN
tests/fonts/NotoSans-Regular_4.ttf
Normal file
BIN
tests/fonts/Roboto-Regular_1.ttf
Normal file
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
tests/fonts/diffs/huge1.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
tests/fonts/diffs/huge1_nokern.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
tests/fonts/diffs/huge2.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
tests/fonts/diffs/huge2_nokern.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
tests/fonts/diffs/huge3.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/huge3_nokern.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/pairs1.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
tests/fonts/diffs/pairs2.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
tests/fonts/diffs/pairs3.png
Normal file
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 712 B |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
tests/fonts/masters/huge1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
tests/fonts/masters/huge1_nokern.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
tests/fonts/masters/huge2.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/fonts/masters/huge2_nokern.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
tests/fonts/masters/huge3.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
tests/fonts/masters/huge3_nokern.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/fonts/masters/pairs1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
tests/fonts/masters/pairs2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/pairs3.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8.1 KiB |
BIN
tests/fonts/rendered/huge1.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
tests/fonts/rendered/huge1_nokern.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
tests/fonts/rendered/huge2.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
tests/fonts/rendered/huge2_nokern.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
tests/fonts/rendered/huge3.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
tests/fonts/rendered/huge3_nokern.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
tests/fonts/rendered/pairs1.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
tests/fonts/rendered/pairs2.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
tests/fonts/rendered/pairs3.png
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 76 KiB |
|
@ -1,7 +1,7 @@
|
|||
import pixie, pixie/fileformats/png, strformat
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 64
|
||||
let image = newImage(200, 100)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
@ -9,7 +9,7 @@ block:
|
|||
image.writeFile("tests/fonts/image_fill.png")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 64
|
||||
let image = newImage(200, 100)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
|
@ -17,14 +17,14 @@ block:
|
|||
image.writeFile("tests/fonts/image_stroke.png")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 64
|
||||
let mask = newMask(200, 100)
|
||||
mask.fillText(font, "fill")
|
||||
writeFile("tests/fonts/mask_fill.png", mask.encodePng())
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 64
|
||||
let mask = newMask(200, 100)
|
||||
mask.strokeText(font, "stroke")
|
||||
|
@ -74,7 +74,7 @@ proc doDiff(rendered: Image, name: string) =
|
|||
diffImage.writeFile(&"tests/fonts/diffs/{name}.png")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 72
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -84,7 +84,7 @@ block:
|
|||
doDiff(image, "basic1")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 72
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -94,7 +94,7 @@ block:
|
|||
doDiff(image, "basic2")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -104,7 +104,7 @@ block:
|
|||
doDiff(image, "basic3")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 24
|
||||
font.lineHeight = 100
|
||||
|
||||
|
@ -115,7 +115,7 @@ block:
|
|||
doDiff(image, "basic4")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -125,7 +125,7 @@ block:
|
|||
doDiff(image, "basic5")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Aclonica-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Aclonica-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -135,7 +135,7 @@ block:
|
|||
doDiff(image, "basic6")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -155,7 +155,7 @@ block:
|
|||
doDiff(image, "basic7")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -170,7 +170,7 @@ block:
|
|||
doDiff(image, "basic8")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 24
|
||||
|
||||
let image = newImage(200, 100)
|
||||
|
@ -191,7 +191,7 @@ const
|
|||
paragraphs = [paragraph, paragraph_2, paragraph_3]
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -209,7 +209,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -228,7 +228,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -246,7 +246,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -265,7 +265,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/IBMPlexSans-Regular.ttf")
|
||||
var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -283,7 +283,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/IBMPlexSans-Regular.ttf")
|
||||
var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -302,7 +302,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/NotoSans-Regular.ttf")
|
||||
var font = readFont("tests/fonts/NotoSans-Regular_4.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -320,7 +320,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/NotoSans-Regular.ttf")
|
||||
var font = readFont("tests/fonts/NotoSans-Regular_4.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -339,7 +339,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Pacifico-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Pacifico-Regular_4.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -357,7 +357,7 @@ block:
|
|||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Pacifico-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Pacifico-Regular_4.ttf")
|
||||
font.size = 16
|
||||
|
||||
let image = newImage(1000, 150)
|
||||
|
@ -374,3 +374,141 @@ block:
|
|||
|
||||
let name = if i > 0: &"paragraph5_nokern_{i + 1}" else: "paragraph5_nokern"
|
||||
doDiff(image, name)
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Shehadcometotheconclusion",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "huge1")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Shehadcometotheconclusion",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh,
|
||||
kerning = false
|
||||
)
|
||||
|
||||
doDiff(image, "huge1_nokern")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Shehadcometotheconclusion",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "huge2")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Shehadcometotheconclusion",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh,
|
||||
kerning = false
|
||||
)
|
||||
|
||||
doDiff(image, "huge2_nokern")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Wrapping text to the next line",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "huge3")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 200
|
||||
|
||||
let image = newImage(2800, 400)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"Wrapping text to the next line",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh,
|
||||
kerning = false
|
||||
)
|
||||
|
||||
doDiff(image, "huge3_nokern")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
|
||||
font.size = 100
|
||||
|
||||
let image = newImage(2800, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"HA HT HX HY IA IT IX IY MA MT MX MY NA NT NX NY",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "pairs1")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
|
||||
font.size = 100
|
||||
|
||||
let image = newImage(2800, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"V( V) V- V/ V: v; v? v@ VT VV VW VX VY V] Vu Vz V{",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "pairs2")
|
||||
|
||||
block:
|
||||
var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf")
|
||||
font.size = 100
|
||||
|
||||
let image = newImage(2800, 200)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(
|
||||
font,
|
||||
"B, B. BA BJ BT BW BY Bf Bg Bt bw By",
|
||||
rgba(0, 0, 0, 255),
|
||||
bounds = image.wh
|
||||
)
|
||||
|
||||
doDiff(image, "pairs3")
|
||||
|
|
42
tests/validate_fonts.nim
Normal file
|
@ -0,0 +1,42 @@
|
|||
import pixie, stb_truetype, unicode
|
||||
|
||||
let fontFiles = [
|
||||
# "tests/fonts/Roboto-Regular_1.ttf"
|
||||
# "tests/fonts/Aclonica-Regular_1.ttf"
|
||||
# "tests/fonts/Ubuntu-Regular_1.ttf"
|
||||
# "tests/fonts/IBMPlexSans-Regular_2.ttf"
|
||||
# "tests/fonts/NotoSans-Regular_4.ttf"
|
||||
"tests/fonts/Pacifico-Regular_4.ttf"
|
||||
]
|
||||
|
||||
for fontFile in fontFiles:
|
||||
let stbtt = initFont(readFile(fontFile))
|
||||
var font = readFont(fontFile)
|
||||
|
||||
var ascent, descent, lineGap: cint
|
||||
stbtt.getFontVMetrics(ascent, descent, lineGap)
|
||||
|
||||
doAssert font.typeface.ascent == ascent.float32
|
||||
doAssert font.typeface.descent == descent.float32
|
||||
doAssert font.typeface.lineGap == lineGap.float32
|
||||
|
||||
for i in 32 .. 126:
|
||||
var advanceWidth, leftSideBearing: cint
|
||||
stbtt.getCodepointHMetrics(Rune(i), advanceWidth, leftSideBearing)
|
||||
|
||||
doAssert font.typeface.getAdvance(Rune(i)) == advanceWidth.float32
|
||||
|
||||
for i in 32 .. 126:
|
||||
for j in 32 .. 126:
|
||||
# echo i, ": ", $Rune(i), " ", j, ": ", $Rune(j)
|
||||
let
|
||||
a = stbtt.getCodepointKernAdvance(Rune(i), Rune(j)).float32
|
||||
b = font.typeface.getKerningAdjustment(Rune(i), Rune(j))
|
||||
if a != b:
|
||||
# echo fontFile
|
||||
echo i, ": ", $Rune(i), " ", j, ": ", $Rune(j)
|
||||
echo "DISAGREE: ", a, " != ", b, " <<<<<<<<<<<<<<<<<<<<<<<<<<<"
|
||||
# quit()
|
||||
|
||||
# echo stbtt.getCodepointKernAdvance(Rune('r'), Rune('s')).float32
|
||||
# echo font.typeface.getKerningAdjustment(Rune('r'), Rune('s'))
|