This commit is contained in:
Ryan Oldenburg 2021-05-05 14:58:44 -05:00
parent 93a854c7b2
commit b0e7a1ae56
97 changed files with 368 additions and 159 deletions

View file

@ -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/ ## See https://docs.microsoft.com/en-us/typography/opentype/spec/
@ -158,6 +158,7 @@ type
version*: uint16 version*: uint16
nTables*: uint16 nTables*: uint16
subTables*: seq[KernSubTable] subTables*: seq[KernSubTable]
kerningPairs: Table[(uint16, uint16), float32]
TableRecord* = object TableRecord* = object
tag*: string tag*: string
@ -215,6 +216,7 @@ type
glyphArray: seq[uint16] glyphArray: seq[uint16]
rangeCount: uint16 rangeCount: uint16
rangeRecords: seq[RangeRecord] rangeRecords: seq[RangeRecord]
coveredGlyphs: HashSet[uint16]
ValueRecord = object ValueRecord = object
xPlacement: int16 xPlacement: int16
@ -271,6 +273,10 @@ type
classDef1: ClassDef classDef1: ClassDef
classDef2: ClassDef classDef2: ClassDef
coverage: Coverage coverage: Coverage
glyphIdToClass1: Table[uint16, uint16]
glyphIdToClass2: Table[uint16, uint16]
classPairAdjustments: Table[(uint16, uint16), int16]
glyphPairAdjustments: Table[(uint16, uint16), int16]
Lookup = object Lookup = object
lookupType: uint16 lookupType: uint16
@ -283,7 +289,7 @@ type
lookupCount: uint16 lookupCount: uint16
lookupOffsets: seq[uint16] lookupOffsets: seq[uint16]
lookups: seq[Lookup] lookups: seq[Lookup]
pairPos: PairPos pairPosTables: seq[PairPos]
GposTable = ref object GposTable = ref object
majorVersion: uint16 majorVersion: uint16
@ -316,7 +322,6 @@ type
kern*: KernTable kern*: KernTable
gpos*: GposTable gpos*: GposTable
glyphPaths: Table[Rune, Path] glyphPaths: Table[Rune, Path]
kerningPairs: Table[(Rune, Rune), float32]
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}
@ -657,7 +662,7 @@ proc parseGlyfTable(buf: string, offset: int, loca: LocaTable): GlyfTable =
for glyphId in 0 ..< loca.offsets.len: for glyphId in 0 ..< loca.offsets.len:
result.offsets[glyphId] = offset.uint32 + loca.offsets[glyphId] 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 var i = offset
buf.eofCheck(i + 2) buf.eofCheck(i + 2)
@ -701,6 +706,22 @@ proc parseKernTable(buf: string, offset: int): KernTable =
i += 6 i += 6
result.subTables.add(subTable) 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: elif version == 1:
discard # Mac format discard # Mac format
else: else:
@ -817,6 +838,10 @@ proc parseCoverage(buf: string, offset: int): Coverage =
buf.eofCheck(i + result.glyphCount.int * 2) buf.eofCheck(i + result.glyphCount.int * 2)
result.glyphArray = buf.readUint16Seq(i, result.glyphCount.int) result.glyphArray = buf.readUint16Seq(i, result.glyphCount.int)
for ci, glyphId in result.glyphArray:
result.coveredGlyphs.incl(glyphId)
of 2: of 2:
result.rangeCount = buf.readUint16(i + 0).swap() result.rangeCount = buf.readUint16(i + 0).swap()
i += 2 i += 2
@ -825,6 +850,13 @@ proc parseCoverage(buf: string, offset: int): Coverage =
for j in 0 ..< result.rangeCount.int: for j in 0 ..< result.rangeCount.int:
result.rangeRecords[j] = result.rangeRecords[j] =
parseRangeRecord(buf, i + j * sizeof(RangeRecord)) 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: else:
failUnsupported() failUnsupported()
@ -870,7 +902,7 @@ proc parsePairValueRecord(
i += 2 i += 2
result.valueRecord1 = parseValueRecord(buf, i, valueFormat1) result.valueRecord1 = parseValueRecord(buf, i, valueFormat1)
result.valueRecord1 = result.valueRecord2 =
parseValueRecord(buf, i + countSetBits(valueFormat1) * 2, valueFormat2) parseValueRecord(buf, i + countSetBits(valueFormat1) * 2, valueFormat2)
proc parsePairSet( proc parsePairSet(
@ -889,14 +921,21 @@ proc parsePairSet(
result.pairValueRecords.setLen(result.pairValueCount.int) result.pairValueRecords.setLen(result.pairValueCount.int)
for j in 0 ..< result.pairValueCount.int: for j in 0 ..< result.pairValueCount.int:
result.pairValueRecords[j] = parsePairValueRecord( result.pairValueRecords[j] = parsePairValueRecord(
buf, i + j * pairValueRecordSize, valueFormat1, valueFormat2 buf, i, valueFormat1, valueFormat2
) )
i += pairValueRecordSize
proc parseClass2Record( proc parseClass2Record(
buf: string, offset: int, valueFormat1, valueFormat2: uint16 buf: string, offset: int, valueFormat1, valueFormat2: uint16
): Class2Record = ): Class2Record =
let class2RecordSize = (
countSetBits(valueFormat1) + countSetBits(valueFormat2)
) * 2
buf.eofCheck(offset + class2RecordSize)
result.valueRecord1 = parseValueRecord(buf, offset, valueFormat1) result.valueRecord1 = parseValueRecord(buf, offset, valueFormat1)
result.valueRecord2 = parseValueRecord(buf, offset, valueFormat2) result.valueRecord2 = parseValueRecord(
buf, offset + countSetBits(valueFormat1) * 2, valueFormat2
)
proc parseClass1Record( proc parseClass1Record(
buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16 buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16
@ -957,14 +996,12 @@ proc parsePairPos(buf: string, offset: int): PairPos =
buf.eofCheck(i + 4) buf.eofCheck(i + 4)
let posFormat = buf.readUint16(i + 0).swap() result = PairPos()
result.posFormat = buf.readUint16(i + 0).swap()
i += 2 i += 2
case posFormat: case result.posFormat:
of 1: of 1:
result = PairPos()
result.posFormat = posFormat
buf.eofCheck(i + 8) buf.eofCheck(i + 8)
result.coverageOffset = buf.readUint16(i + 0).swap() 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) 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) buf.eofCheck(i + 14)
result.coverageOffset = buf.readUint16(i + 0).swap() result.coverageOffset = buf.readUint16(i + 0).swap()
@ -1013,12 +1075,52 @@ proc parsePairPos(buf: string, offset: int): PairPos =
result.class1Records[j] = parseClass1Record( result.class1Records[j] = parseClass1Record(
buf, i, result.valueFormat1, result.valueFormat2, result.class2Count buf, i, result.valueFormat1, result.valueFormat2, result.class2Count
) )
i += class2RecordSize i += class2RecordSize * result.class2Count.int
result.classDef1 = parseClassDef(buf, offset + result.classDef1Offset.int) result.classDef1 = parseClassDef(buf, offset + result.classDef1Offset.int)
result.classDef2 = parseClassDef(buf, offset + result.classDef2Offset.int) result.classDef2 = parseClassDef(buf, offset + result.classDef2Offset.int)
result.coverage = parseCoverage(buf, offset + result.coverageOffset.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: else:
failUnsupported() failUnsupported()
@ -1043,8 +1145,10 @@ proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup =
for subTableOffset in result.subTableOffsets: for subTableOffset in result.subTableOffsets:
if result.lookupType == 2: if result.lookupType == 2:
gpos.lookupList.pairPos = let pairPos = parsePairPos(buf, offset + subTableOffset.int)
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 = proc parseLookupList(buf: string, offset: int, gpos: GposTable): LookupList =
var i = offset var i = offset
@ -1377,9 +1481,38 @@ proc getAdvance*(opentype: OpenType, rune: Rune): float32 =
result = opentype.hmtx.hMetrics[^1].advanceWidth.float32 result = opentype.hmtx.hMetrics[^1].advanceWidth.float32
proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 = proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
let pair = (left, right) let
if pair in opentype.kerningPairs: leftGlyphId = opentype.cmap.runeToGlyphId[left]
result = opentype.kerningPairs[pair] 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 = proc parseOpenType*(buf: string): OpenType =
result = OpenType() result = OpenType()
@ -1427,119 +1560,15 @@ proc parseOpenType*(buf: string): OpenType =
result.loca = parseLocaTable( result.loca = parseLocaTable(
buf, result.tableRecords["loca"].offset.int, result.head, result.maxp buf, result.tableRecords["loca"].offset.int, result.head, result.maxp
) )
result.glyf = parseGlyfTable( result.glyf =
buf, result.tableRecords["glyf"].offset.int, result.loca parseGlyfTable(buf, result.tableRecords["glyf"].offset.int, result.loca)
)
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, result.cmap)
if result.kern != nil: if "GPOS" in result.tableRecords:
for table in result.kern.subTables: result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
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))
when defined(release): when defined(release):
{.pop.} {.pop.}

View file

@ -35,21 +35,21 @@ type
# tcSmallCaps # tcSmallCaps
# tcSmallCapsForced # tcSmallCapsForced
proc ascent(typeface: Typeface): float32 {.inline.} = proc ascent*(typeface: Typeface): float32 {.inline.} =
## The font ascender value in font units. ## The font ascender value in font units.
if typeface.opentype != nil: if typeface.opentype != nil:
typeface.opentype.hhea.ascender.float32 typeface.opentype.hhea.ascender.float32
else: else:
typeface.svgFont.ascent typeface.svgFont.ascent
proc descent(typeface: Typeface): float32 {.inline.} = proc descent*(typeface: Typeface): float32 {.inline.} =
## The font descender value in font units. ## The font descender value in font units.
if typeface.opentype != nil: if typeface.opentype != nil:
typeface.opentype.hhea.descender.float32 typeface.opentype.hhea.descender.float32
else: else:
typeface.svgFont.descent typeface.svgFont.descent
proc lineGap(typeface: Typeface): float32 {.inline.} = proc lineGap*(typeface: Typeface): float32 {.inline.} =
## The font line gap value in font units. ## The font line gap value in font units.
if typeface.opentype != nil: if typeface.opentype != nil:
result = typeface.opentype.hhea.lineGap.float32 result = typeface.opentype.hhea.lineGap.float32
@ -61,14 +61,14 @@ proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
else: else:
typeface.svgFont.getGlyphPath(rune) 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. ## The advance for the rune in pixels.
if typeface.opentype != nil: if typeface.opentype != nil:
typeface.opentype.getAdvance(rune) typeface.opentype.getAdvance(rune)
else: else:
typeface.svgFont.getAdvance(rune) typeface.svgFont.getAdvance(rune)
proc getKerningAdjustment( proc getKerningAdjustment*(
typeface: Typeface, left, right: Rune typeface: Typeface, left, right: Rune
): float32 {.inline.} = ): float32 {.inline.} =
## The kerning adjustment for the rune pair, in pixels. ## The kerning adjustment for the rune pair, in pixels.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
tests/fonts/diffs/huge1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
tests/fonts/diffs/huge2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
tests/fonts/diffs/huge3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View file

@ -1,7 +1,7 @@
import pixie, pixie/fileformats/png, strformat import pixie, pixie/fileformats/png, strformat
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64 font.size = 64
let image = newImage(200, 100) let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255)) image.fill(rgba(255, 255, 255, 255))
@ -9,7 +9,7 @@ block:
image.writeFile("tests/fonts/image_fill.png") image.writeFile("tests/fonts/image_fill.png")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64 font.size = 64
let image = newImage(200, 100) let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255)) image.fill(rgba(255, 255, 255, 255))
@ -17,14 +17,14 @@ block:
image.writeFile("tests/fonts/image_stroke.png") image.writeFile("tests/fonts/image_stroke.png")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64 font.size = 64
let mask = newMask(200, 100) let mask = newMask(200, 100)
mask.fillText(font, "fill") mask.fillText(font, "fill")
writeFile("tests/fonts/mask_fill.png", mask.encodePng()) writeFile("tests/fonts/mask_fill.png", mask.encodePng())
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 64 font.size = 64
let mask = newMask(200, 100) let mask = newMask(200, 100)
mask.strokeText(font, "stroke") mask.strokeText(font, "stroke")
@ -74,7 +74,7 @@ proc doDiff(rendered: Image, name: string) =
diffImage.writeFile(&"tests/fonts/diffs/{name}.png") diffImage.writeFile(&"tests/fonts/diffs/{name}.png")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 72 font.size = 72
let image = newImage(200, 100) let image = newImage(200, 100)
@ -84,7 +84,7 @@ block:
doDiff(image, "basic1") doDiff(image, "basic1")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 72 font.size = 72
let image = newImage(200, 100) let image = newImage(200, 100)
@ -94,7 +94,7 @@ block:
doDiff(image, "basic2") doDiff(image, "basic2")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -104,7 +104,7 @@ block:
doDiff(image, "basic3") doDiff(image, "basic3")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
font.lineHeight = 100 font.lineHeight = 100
@ -115,7 +115,7 @@ block:
doDiff(image, "basic4") doDiff(image, "basic4")
block: block:
var font = readFont("tests/fonts/Ubuntu-Regular.ttf") var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -125,7 +125,7 @@ block:
doDiff(image, "basic5") doDiff(image, "basic5")
block: block:
var font = readFont("tests/fonts/Aclonica-Regular.ttf") var font = readFont("tests/fonts/Aclonica-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -135,7 +135,7 @@ block:
doDiff(image, "basic6") doDiff(image, "basic6")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -155,7 +155,7 @@ block:
doDiff(image, "basic7") doDiff(image, "basic7")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -170,7 +170,7 @@ block:
doDiff(image, "basic8") doDiff(image, "basic8")
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24 font.size = 24
let image = newImage(200, 100) let image = newImage(200, 100)
@ -191,7 +191,7 @@ const
paragraphs = [paragraph, paragraph_2, paragraph_3] paragraphs = [paragraph, paragraph_2, paragraph_3]
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -209,7 +209,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/Roboto-Regular.ttf") var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -228,7 +228,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/Ubuntu-Regular.ttf") var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -246,7 +246,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/Ubuntu-Regular.ttf") var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -265,7 +265,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/IBMPlexSans-Regular.ttf") var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -283,7 +283,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/IBMPlexSans-Regular.ttf") var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -302,7 +302,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/NotoSans-Regular.ttf") var font = readFont("tests/fonts/NotoSans-Regular_4.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -320,7 +320,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/NotoSans-Regular.ttf") var font = readFont("tests/fonts/NotoSans-Regular_4.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -339,7 +339,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/Pacifico-Regular.ttf") var font = readFont("tests/fonts/Pacifico-Regular_4.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -357,7 +357,7 @@ block:
doDiff(image, name) doDiff(image, name)
block: block:
var font = readFont("tests/fonts/Pacifico-Regular.ttf") var font = readFont("tests/fonts/Pacifico-Regular_4.ttf")
font.size = 16 font.size = 16
let image = newImage(1000, 150) let image = newImage(1000, 150)
@ -374,3 +374,141 @@ block:
let name = if i > 0: &"paragraph5_nokern_{i + 1}" else: "paragraph5_nokern" let name = if i > 0: &"paragraph5_nokern_{i + 1}" else: "paragraph5_nokern"
doDiff(image, name) 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
View 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'))