more tests
|
@ -332,12 +332,22 @@ proc fillText*(
|
|||
text: string,
|
||||
color: SomeColor,
|
||||
transform: Vec2 | Mat3 = vec2(0, 0),
|
||||
bounds = vec2(0, 0)
|
||||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
) =
|
||||
let typeset = font.typeset(text, bounds)
|
||||
for i in 0 ..< typeset.runes.len:
|
||||
var path = font.getGlyphPath(typeset.runes[i])
|
||||
path.transform(translate(typeset.positions[i]) * scale(vec2(font.scale)))
|
||||
for path in font.typesetPaths(
|
||||
text,
|
||||
bounds,
|
||||
hAlign,
|
||||
vAlign,
|
||||
textCase,
|
||||
wrap,
|
||||
kerning
|
||||
):
|
||||
image.fillPath(path, color, transform)
|
||||
|
||||
proc fillText*(
|
||||
|
@ -345,12 +355,22 @@ proc fillText*(
|
|||
font: Font,
|
||||
text: string,
|
||||
transform: Vec2 | Mat3 = vec2(0, 0),
|
||||
bounds = vec2(0, 0)
|
||||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
) =
|
||||
let typeset = font.typeset(text, bounds)
|
||||
for i in 0 ..< typeset.runes.len:
|
||||
var path = font.getGlyphPath(typeset.runes[i])
|
||||
path.transform(translate(typeset.positions[i]) * scale(vec2(font.scale)))
|
||||
for path in font.typesetPaths(
|
||||
text,
|
||||
bounds,
|
||||
hAlign,
|
||||
vAlign,
|
||||
textCase,
|
||||
wrap,
|
||||
kerning
|
||||
):
|
||||
mask.fillPath(path, transform)
|
||||
|
||||
proc strokeText*(
|
||||
|
@ -360,12 +380,22 @@ proc strokeText*(
|
|||
color: SomeColor,
|
||||
transform: Vec2 | Mat3 = vec2(0, 0),
|
||||
strokeWidth = 1.0,
|
||||
bounds = vec2(0, 0)
|
||||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
) =
|
||||
let typeset = font.typeset(text, bounds)
|
||||
for i in 0 ..< typeset.runes.len:
|
||||
var path = font.getGlyphPath(typeset.runes[i])
|
||||
path.transform(translate(typeset.positions[i]) * scale(vec2(font.scale)))
|
||||
for path in font.typesetPaths(
|
||||
text,
|
||||
bounds,
|
||||
hAlign,
|
||||
vAlign,
|
||||
textCase,
|
||||
wrap,
|
||||
kerning
|
||||
):
|
||||
image.strokePath(path, color, transform, strokeWidth)
|
||||
|
||||
proc strokeText*(
|
||||
|
@ -374,10 +404,20 @@ proc strokeText*(
|
|||
text: string,
|
||||
transform: Vec2 | Mat3 = vec2(0, 0),
|
||||
strokeWidth = 1.0,
|
||||
bounds = vec2(0, 0)
|
||||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
) =
|
||||
let typeset = font.typeset(text, bounds)
|
||||
for i in 0 ..< typeset.runes.len:
|
||||
var path = font.getGlyphPath(typeset.runes[i])
|
||||
path.transform(translate(typeset.positions[i]) * scale(vec2(font.scale)))
|
||||
for path in font.typesetPaths(
|
||||
text,
|
||||
bounds,
|
||||
hAlign,
|
||||
vAlign,
|
||||
textCase,
|
||||
wrap,
|
||||
kerning
|
||||
):
|
||||
mask.strokePath(path, transform, strokeWidth)
|
||||
|
|
|
@ -235,6 +235,26 @@ type
|
|||
pairValueCount: uint16
|
||||
pairValueRecords: seq[PairValueRecord]
|
||||
|
||||
Class2Record = object
|
||||
valueRecord1: ValueRecord
|
||||
valueRecord2: ValueRecord
|
||||
|
||||
Class1Record = object
|
||||
class2Records: seq[Class2Record]
|
||||
|
||||
ClassRangeRecord = object
|
||||
startGlyphID: uint16
|
||||
endGlyphID: uint16
|
||||
class: uint16
|
||||
|
||||
ClassDef = object
|
||||
classFormat: uint16
|
||||
startGlyphID: uint16
|
||||
glyphCount: uint16
|
||||
classValueArray: seq[uint16]
|
||||
classRangeCount: uint16
|
||||
classRangeRecords: seq[ClassRangeRecord]
|
||||
|
||||
PairPos = ref object
|
||||
posFormat: uint16
|
||||
coverageOffset: uint16
|
||||
|
@ -243,6 +263,13 @@ type
|
|||
pairSetCount: uint16
|
||||
pairSetOffsets: seq[uint16]
|
||||
pairSets: seq[PairSet]
|
||||
classDef1Offset: uint16
|
||||
classDef2Offset: uint16
|
||||
class1Count: uint16
|
||||
class2Count: uint16
|
||||
class1Records: seq[Class1Record]
|
||||
classDef1: ClassDef
|
||||
classDef2: ClassDef
|
||||
coverage: Coverage
|
||||
|
||||
Lookup = object
|
||||
|
@ -865,6 +892,66 @@ proc parsePairSet(
|
|||
buf, i + j * pairValueRecordSize, valueFormat1, valueFormat2
|
||||
)
|
||||
|
||||
proc parseClass2Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2: uint16
|
||||
): Class2Record =
|
||||
result.valueRecord1 = parseValueRecord(buf, offset, valueFormat1)
|
||||
result.valueRecord2 = parseValueRecord(buf, offset, valueFormat2)
|
||||
|
||||
proc parseClass1Record(
|
||||
buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16
|
||||
): Class1Record =
|
||||
var i = offset
|
||||
|
||||
let class2RecordSize = (
|
||||
countSetBits(valueFormat1) + countSetBits(valueFormat2)
|
||||
) * 2
|
||||
|
||||
result.class2Records.setLen(class2Count.int)
|
||||
for j in 0 ..< class2Count.int:
|
||||
result.class2Records[j] =
|
||||
parseClass2Record(buf, i, valueFormat1, valueFormat2)
|
||||
i += class2RecordSize
|
||||
|
||||
proc parseClassRangeRecord(buf: string, offset: int): ClassRangeRecord =
|
||||
buf.eofCheck(offset + 6)
|
||||
|
||||
result.startGlyphID = buf.readUint16(offset + 0).swap()
|
||||
result.endGlyphID = buf.readUint16(offset + 2).swap()
|
||||
result.class = buf.readUint16(offset + 4).swap()
|
||||
|
||||
proc parseClassDef(buf: string, offset: int): ClassDef =
|
||||
var i = offset
|
||||
|
||||
buf.eofCheck(i + 2)
|
||||
|
||||
result.classFormat = buf.readUint16(i + 0).swap()
|
||||
i += 2
|
||||
|
||||
case result.classFormat:
|
||||
of 1:
|
||||
buf.eofCheck(i + 4)
|
||||
|
||||
result.startGlyphID = buf.readUint16(i + 0).swap()
|
||||
result.glyphCount = buf.readUint16(i + 2).swap()
|
||||
i += 4
|
||||
|
||||
buf.eofCheck(i + result.glyphCount.int * 2)
|
||||
|
||||
result.classValueArray = buf.readUint16Seq(i + 0, result.glyphCount.int)
|
||||
of 2:
|
||||
buf.eofCheck(i + 2)
|
||||
|
||||
result.classRangeCount = buf.readUint16(i + 0).swap()
|
||||
i += 2
|
||||
|
||||
result.classRangeRecords.setLen(result.classRangeCount.int)
|
||||
for j in 0 ..< result.classRangeCount.int:
|
||||
result.classRangeRecords[j] = parseClassRangeRecord(buf, i)
|
||||
i += sizeof(ClassRangeRecord)
|
||||
else:
|
||||
failUnsupported()
|
||||
|
||||
proc parsePairPos(buf: string, offset: int): PairPos =
|
||||
var i = offset
|
||||
|
||||
|
@ -902,7 +989,36 @@ proc parsePairPos(buf: string, offset: int): PairPos =
|
|||
|
||||
result.coverage = parseCoverage(buf, offset + result.coverageOffset.int)
|
||||
of 2:
|
||||
discard
|
||||
result = PairPos()
|
||||
result.posFormat = posFormat
|
||||
|
||||
buf.eofCheck(i + 14)
|
||||
|
||||
result.coverageOffset = buf.readUint16(i + 0).swap()
|
||||
result.valueFormat1 = buf.readUint16(i + 2).swap()
|
||||
result.valueFormat2 = buf.readUint16(i + 4).swap()
|
||||
result.classDef1Offset = buf.readUint16(i + 6).swap()
|
||||
result.classDef2Offset = buf.readUint16(i + 8).swap()
|
||||
result.class1Count = buf.readUint16(i + 10).swap()
|
||||
result.class2Count = buf.readUint16(i + 12).swap()
|
||||
|
||||
i += 14
|
||||
|
||||
let class2RecordSize = (
|
||||
countSetBits(result.valueFormat1) + countSetBits(result.valueFormat2)
|
||||
) * 2
|
||||
|
||||
result.class1Records.setLen(result.class1Count.int)
|
||||
for j in 0 ..< result.class1Count.int:
|
||||
result.class1Records[j] = parseClass1Record(
|
||||
buf, i, result.valueFormat1, result.valueFormat2, result.class2Count
|
||||
)
|
||||
i += class2RecordSize
|
||||
|
||||
result.classDef1 = parseClassDef(buf, offset + result.classDef1Offset.int)
|
||||
result.classDef2 = parseClassDef(buf, offset + result.classDef2Offset.int)
|
||||
|
||||
result.coverage = parseCoverage(buf, offset + result.coverageOffset.int)
|
||||
else:
|
||||
failUnsupported()
|
||||
|
||||
|
@ -1244,12 +1360,21 @@ proc getGlyphPath*(opentype: OpenType, rune: Rune): Path =
|
|||
opentype.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||
opentype.glyphPaths[rune]
|
||||
|
||||
proc getGlyphAdvance*(opentype: OpenType, rune: Rune): float32 =
|
||||
proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 =
|
||||
let glyphId = opentype.getGlyphId(rune).int
|
||||
if glyphId < opentype.hmtx.hMetrics.len:
|
||||
opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
||||
result = opentype.hmtx.hMetrics[glyphId].leftSideBearing.float32
|
||||
else:
|
||||
opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||
let index = glyphId - opentype.hmtx.hMetrics.len
|
||||
if index > 0 and index < opentype.hmtx.leftSideBearings.len:
|
||||
result = opentype.hmtx.leftSideBearings[index].float32
|
||||
|
||||
proc getAdvance*(opentype: OpenType, rune: Rune): float32 =
|
||||
let glyphId = opentype.getGlyphId(rune).int
|
||||
if glyphId < opentype.hmtx.hMetrics.len:
|
||||
result = opentype.hmtx.hMetrics[glyphId].advanceWidth.float32
|
||||
else:
|
||||
result = opentype.hmtx.hMetrics[^1].advanceWidth.float32
|
||||
|
||||
proc getKerningAdjustment*(opentype: OpenType, left, right: Rune): float32 =
|
||||
let pair = (left, right)
|
||||
|
@ -1328,21 +1453,93 @@ proc parseOpenType*(buf: string): OpenType =
|
|||
value += result.kerningPairs[key]
|
||||
result.kerningPairs[key] = value
|
||||
|
||||
if "GPOS" in result.tableRecords:
|
||||
result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int)
|
||||
# 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()
|
||||
# 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()
|
||||
|
||||
for pairSet in result.gpos.lookupList.pairPos.pairSets:
|
||||
for pairValue in pairSet.pairValueRecords:
|
||||
discard
|
||||
# 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):
|
||||
{.pop.}
|
||||
|
|
|
@ -2,7 +2,7 @@ import pixie/common, pixie/paths, strutils, tables, unicode, vmath, xmlparser, x
|
|||
|
||||
type SvgFont* = ref object
|
||||
unitsPerEm*, ascent*, descent*: float32
|
||||
glyphAdvances: Table[Rune, float32]
|
||||
advances: Table[Rune, float32]
|
||||
glyphPaths: Table[Rune, Path]
|
||||
kerningPairs: Table[(Rune, Rune), float32]
|
||||
missingGlyphAdvance: float32
|
||||
|
@ -14,9 +14,9 @@ proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path =
|
|||
else:
|
||||
svgFont.missingGlyphPath
|
||||
|
||||
proc getGlyphAdvance*(svgFont: SvgFont, rune: Rune): float32 =
|
||||
if rune in svgFont.glyphAdvances:
|
||||
svgFont.glyphAdvances[rune]
|
||||
proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 =
|
||||
if rune in svgFont.advances:
|
||||
svgFont.advances[rune]
|
||||
else:
|
||||
svgFont.missingGlyphAdvance
|
||||
|
||||
|
@ -74,7 +74,7 @@ proc parseSvgFont*(buf: string): SvgFont =
|
|||
var advance = defaultAdvance
|
||||
if node.attr("horiz-adv-x").len > 0:
|
||||
advance = node.parseFloat("horiz-adv-x")
|
||||
result.glyphAdvances[rune] = advance
|
||||
result.advances[rune] = advance
|
||||
result.glyphPaths[rune] = parsePath(node.attr("d"))
|
||||
result.glyphPaths[rune].transform(scale(vec2(1, -1)))
|
||||
else:
|
||||
|
|
|
@ -4,13 +4,16 @@ import pixie/fontformats/opentype, pixie/fontformats/svgfont, pixie/paths,
|
|||
const AutoLineHeight* = -1.float32 ## Use default line height for the font size
|
||||
|
||||
type
|
||||
Font* = ref object
|
||||
Typeface = ref object
|
||||
opentype: OpenType
|
||||
svgFont: SvgFont
|
||||
|
||||
Font* = object
|
||||
typeface*: Typeface
|
||||
size*: float32 ## Font size in pixels.
|
||||
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
|
||||
|
||||
TypesetText* = ref object
|
||||
Typesetting* = ref object
|
||||
runes*: seq[Rune]
|
||||
positions*: seq[Vec2]
|
||||
|
||||
|
@ -32,56 +35,59 @@ type
|
|||
# tcSmallCaps
|
||||
# tcSmallCapsForced
|
||||
|
||||
proc ascent(font: Font): float32 {.inline.} =
|
||||
proc ascent(typeface: Typeface): float32 {.inline.} =
|
||||
## The font ascender value in font units.
|
||||
if font.opentype != nil:
|
||||
font.opentype.hhea.ascender.float32
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.ascender.float32
|
||||
else:
|
||||
font.svgFont.ascent
|
||||
typeface.svgFont.ascent
|
||||
|
||||
proc descent(font: Font): float32 {.inline.} =
|
||||
proc descent(typeface: Typeface): float32 {.inline.} =
|
||||
## The font descender value in font units.
|
||||
if font.opentype != nil:
|
||||
font.opentype.hhea.descender.float32
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.hhea.descender.float32
|
||||
else:
|
||||
font.svgFont.descent
|
||||
typeface.svgFont.descent
|
||||
|
||||
proc lineGap(font: Font): float32 {.inline.} =
|
||||
proc lineGap(typeface: Typeface): float32 {.inline.} =
|
||||
## The font line gap value in font units.
|
||||
if font.opentype != nil:
|
||||
result = font.opentype.hhea.lineGap.float32
|
||||
if typeface.opentype != nil:
|
||||
result = typeface.opentype.hhea.lineGap.float32
|
||||
|
||||
proc getGlyphPath*(font: Font, rune: Rune): Path {.inline.} =
|
||||
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
|
||||
## The glyph path for the rune.
|
||||
if font.opentype != nil:
|
||||
font.opentype.getGlyphPath(rune)
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getGlyphPath(rune)
|
||||
else:
|
||||
font.svgFont.getGlyphPath(rune)
|
||||
typeface.svgFont.getGlyphPath(rune)
|
||||
|
||||
proc getGlyphAdvance(font: Font, rune: Rune): float32 {.inline.} =
|
||||
proc getAdvance(typeface: Typeface, rune: Rune): float32 {.inline.} =
|
||||
## The advance for the rune in pixels.
|
||||
if font.opentype != nil:
|
||||
font.opentype.getGlyphAdvance(rune)
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getAdvance(rune)
|
||||
else:
|
||||
font.svgFont.getGlyphAdvance(rune)
|
||||
typeface.svgFont.getAdvance(rune)
|
||||
|
||||
proc getKerningAdjustment(font: Font, left, right: Rune): float32 {.inline.} =
|
||||
proc getKerningAdjustment(
|
||||
typeface: Typeface, left, right: Rune
|
||||
): float32 {.inline.} =
|
||||
## The kerning adjustment for the rune pair, in pixels.
|
||||
if font.opentype != nil:
|
||||
font.opentype.getKerningAdjustment(left, right)
|
||||
if typeface.opentype != nil:
|
||||
typeface.opentype.getKerningAdjustment(left, right)
|
||||
else:
|
||||
font.svgfont.getKerningAdjustment(left, right)
|
||||
typeface.svgfont.getKerningAdjustment(left, right)
|
||||
|
||||
proc scale*(font: Font): float32 {.inline.} =
|
||||
## The scale factor to transform font units into pixels.
|
||||
if font.opentype != nil:
|
||||
font.size / font.opentype.head.unitsPerEm.float32
|
||||
if font.typeface.opentype != nil:
|
||||
font.size / font.typeface.opentype.head.unitsPerEm.float32
|
||||
else:
|
||||
font.size / font.svgFont.unitsPerEm
|
||||
font.size / font.typeface.svgFont.unitsPerEm
|
||||
|
||||
proc defaultLineHeight*(font: Font): float32 {.inline.} =
|
||||
## The default line height in pixels for the current font size.
|
||||
let fontUnits = (font.ascent + abs(font.descent) + font.lineGap)
|
||||
let fontUnits =
|
||||
font.typeface.ascent - font.typeface.descent + font.typeface.lineGap
|
||||
round(fontUnits * font.scale)
|
||||
|
||||
proc convertTextCase(runes: var seq[Rune], textCase: TextCase) =
|
||||
|
@ -107,12 +113,13 @@ proc typeset*(
|
|||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal
|
||||
): TypesetText =
|
||||
result = TypesetText()
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
): Typesetting =
|
||||
result = Typesetting()
|
||||
result.runes = toRunes(text)
|
||||
result.runes.convertTextCase(textCase)
|
||||
|
||||
result.positions.setLen(result.runes.len)
|
||||
|
||||
let lineHeight =
|
||||
|
@ -121,22 +128,24 @@ proc typeset*(
|
|||
else:
|
||||
font.defaultLineHeight
|
||||
|
||||
proc glyphAdvance(runes: seq[Rune], font: Font, i: int): float32 {.inline.} =
|
||||
if i + 1 < runes.len:
|
||||
result += font.getKerningAdjustment(runes[i], runes[i + 1])
|
||||
result += font.getGlyphAdvance(runes[i])
|
||||
proc glyphAdvance(
|
||||
font: Font, runes: seq[Rune], i: int, kerning: bool
|
||||
): float32 {.inline.} =
|
||||
if kerning and i + 1 < runes.len:
|
||||
result += font.typeface.getKerningAdjustment(runes[i], runes[i + 1])
|
||||
result += font.typeface.getAdvance(runes[i])
|
||||
result *= font.scale
|
||||
|
||||
var
|
||||
at: Vec2
|
||||
prevCanWrap: int
|
||||
at.y = round(font.ascent * font.scale)
|
||||
at.y = round(font.typeface.ascent * font.scale)
|
||||
at.y += (lineheight - font.defaultLineHeight) / 2
|
||||
for i, rune in result.runes:
|
||||
if rune.canWrap():
|
||||
prevCanWrap = i
|
||||
|
||||
let advance = glyphAdvance(result.runes, font, i)
|
||||
let advance = glyphAdvance(font, result.runes, i, kerning)
|
||||
if rune != Rune(32) and bounds.x > 0 and at.x + advance > bounds.x:
|
||||
# Wrap to new line
|
||||
at.x = 0
|
||||
|
@ -146,14 +155,40 @@ proc typeset*(
|
|||
if prevCanWrap > 0 and prevCanWrap != i:
|
||||
for j in prevCanWrap + 1 ..< i:
|
||||
result.positions[j] = at
|
||||
at.x += glyphAdvance(result.runes, font, j)
|
||||
at.x += glyphAdvance(font, result.runes, j, kerning)
|
||||
|
||||
result.positions[i] = at
|
||||
at.x += advance
|
||||
|
||||
iterator typesetPaths*(
|
||||
font: Font,
|
||||
text: string,
|
||||
bounds = vec2(0, 0),
|
||||
hAlign = haLeft,
|
||||
vAlign = vaTop,
|
||||
textCase = tcNormal,
|
||||
wrap = true,
|
||||
kerning = true
|
||||
): Path =
|
||||
let typesetText = font.typeset(
|
||||
text,
|
||||
bounds,
|
||||
hAlign,
|
||||
vAlign,
|
||||
textCase,
|
||||
wrap,
|
||||
kerning
|
||||
)
|
||||
for i in 0 ..< typesetText.runes.len:
|
||||
var path = font.typeface.getGlyphPath(typesetText.runes[i])
|
||||
path.transform(
|
||||
translate(typesetText.positions[i]) * scale(vec2(font.scale))
|
||||
)
|
||||
yield path
|
||||
|
||||
proc parseOtf*(buf: string): Font =
|
||||
result = Font()
|
||||
result.opentype = parseOpenType(buf)
|
||||
result.typeface = Typeface()
|
||||
result.typeface.opentype = parseOpenType(buf)
|
||||
result.size = 12
|
||||
result.lineHeight = AutoLineHeight
|
||||
|
||||
|
@ -161,7 +196,7 @@ proc parseTtf*(buf: string): Font =
|
|||
parseOtf(buf)
|
||||
|
||||
proc parseSvgFont*(buf: string): Font =
|
||||
result = Font()
|
||||
result.svgFont = svgfont.parseSvgFont(buf)
|
||||
result.typeface = Typeface()
|
||||
result.typeface.svgFont = svgfont.parseSvgFont(buf)
|
||||
result.size = 12
|
||||
result.lineHeight = AutoLineHeight
|
||||
|
|
|
@ -2,7 +2,7 @@ import benchy, pixie
|
|||
|
||||
const text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis in quam in nulla bibendum luctus. Integer dui lectus, ultricies commodo enim quis, laoreet lacinia erat. Vivamus ultrices maximus risus, non aliquam quam sagittis quis. Ut nec diam vitae tortor interdum ullamcorper in aliquet velit. Ut sed lobortis mi. Nulla venenatis lectus varius justo lacinia, quis sollicitudin nunc ultrices. Donec a suscipit arcu, id egestas neque. Nullam commodo pharetra est. Nullam gravida nibh eget quam venenatis lacinia. Vestibulum et libero arcu. Sed dignissim enim eros. Nullam eleifend luctus erat sed luctus. Nunc tincidunt, mi nec tincidunt tristique, ex nulla lobortis sem, sit amet finibus purus justo non massa."
|
||||
|
||||
let font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
var font = readFont("tests/fonts/Roboto-Regular.ttf")
|
||||
font.size = 16
|
||||
|
||||
timeIt "typeset":
|
||||
|
@ -13,7 +13,7 @@ let
|
|||
mask = newMask(500, 300)
|
||||
|
||||
timeIt "rasterize":
|
||||
# image.fill(rgba(255, 255, 255, 255))
|
||||
# image.fillText(font, text, rgba(0, 0, 0, 255), bounds = image.wh)
|
||||
mask.fill(0)
|
||||
mask.fillText(font, text, bounds = mask.wh)
|
||||
image.fill(rgba(255, 255, 255, 255))
|
||||
image.fillText(font, text, rgba(0, 0, 0, 255), bounds = image.wh)
|
||||
# mask.fill(0)
|
||||
# mask.fillText(font, text, bounds = mask.wh)
|
||||
|
|
BIN
tests/fonts/IBMPlexSans-Regular.ttf
Normal file
BIN
tests/fonts/NotoSans-Regular.ttf
Normal file
BIN
tests/fonts/Pacifico-Regular.ttf
Normal file
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 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.4 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4 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.6 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 80 KiB |
BIN
tests/fonts/diffs/paragraph1.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
tests/fonts/diffs/paragraph1_2.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph1_3.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph1_nokern.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
tests/fonts/diffs/paragraph1_nokern_2.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
tests/fonts/diffs/paragraph1_nokern_3.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
tests/fonts/diffs/paragraph2.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
tests/fonts/diffs/paragraph2_2.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph2_3.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
tests/fonts/diffs/paragraph2_nokern.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
tests/fonts/diffs/paragraph2_nokern_2.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph2_nokern_3.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph3.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tests/fonts/diffs/paragraph3_2.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
tests/fonts/diffs/paragraph3_3.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
tests/fonts/diffs/paragraph3_nokern.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
tests/fonts/diffs/paragraph3_nokern_2.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph3_nokern_3.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
tests/fonts/diffs/paragraph4.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tests/fonts/diffs/paragraph4_2.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
tests/fonts/diffs/paragraph4_3.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
tests/fonts/diffs/paragraph4_nokern.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tests/fonts/diffs/paragraph4_nokern_2.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
tests/fonts/diffs/paragraph4_nokern_3.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
tests/fonts/diffs/paragraph5.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
tests/fonts/diffs/paragraph5_2.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
tests/fonts/diffs/paragraph5_3.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
tests/fonts/diffs/paragraph5_nokern.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
tests/fonts/diffs/paragraph5_nokern_2.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
tests/fonts/diffs/paragraph5_nokern_3.png
Normal file
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: 7.1 KiB |
Before Width: | Height: | Size: 711 B After Width: | Height: | Size: 709 B |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 31 KiB |
BIN
tests/fonts/masters/paragraph1.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/fonts/masters/paragraph1_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph1_3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph1_nokern.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
tests/fonts/masters/paragraph1_nokern_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph1_nokern_3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph2.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
tests/fonts/masters/paragraph2_2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/paragraph2_3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph2_nokern.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/fonts/masters/paragraph2_nokern_2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/paragraph2_nokern_3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph3.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
tests/fonts/masters/paragraph3_2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/paragraph3_3.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/paragraph3_nokern.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
tests/fonts/masters/paragraph3_nokern_2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
tests/fonts/masters/paragraph3_nokern_3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph4.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
tests/fonts/masters/paragraph4_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph4_3.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/fonts/masters/paragraph4_nokern.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
tests/fonts/masters/paragraph4_nokern_2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
tests/fonts/masters/paragraph4_nokern_3.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/fonts/masters/paragraph5.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
tests/fonts/masters/paragraph5_2.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
tests/fonts/masters/paragraph5_3.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
tests/fonts/masters/paragraph5_nokern.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
tests/fonts/masters/paragraph5_nokern_2.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
tests/fonts/masters/paragraph5_nokern_3.png
Normal file
After Width: | Height: | Size: 32 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.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 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 KiB |
Before Width: | Height: | Size: 76 KiB |
BIN
tests/fonts/rendered/paragraph1.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
tests/fonts/rendered/paragraph1_1.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
tests/fonts/rendered/paragraph1_2.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
tests/fonts/rendered/paragraph1_3.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
tests/fonts/rendered/paragraph1_nokern.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
tests/fonts/rendered/paragraph1_nokern_2.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
tests/fonts/rendered/paragraph1_nokern_3.png
Normal file
After Width: | Height: | Size: 68 KiB |