diff --git a/README.md b/README.md index ce3a080..e6117a6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This library is being actively developed and we'd be happy for you to use it. `nimble install pixie` Features: +* Typesetting and rasterizing text, including styled rich text via spans. * Drawing paths, shapes and curves with even-odd and non-zero windings. * Pixel-perfect AA quality. * Supported file formats are PNG, BMP, JPG, SVG + more in development. @@ -16,13 +17,13 @@ Features: * Shadows, glows and blurs. * Complex masking: Subtract, Intersect, Exclude. * Complex blends: Darken, Multiply, Color Dodge, Hue, Luminosity... etc. -* Many operations are SIMD accelerated where possible. +* Many operations are SIMD accelerated. ### Documentation API reference: https://treeform.github.io/pixie/pixie.html -### File formats +### Image file formats Format | Read | Write | ------------- | ------------- | ------------- | @@ -32,6 +33,14 @@ BMP | ✅ | ✅ | GIF | ✅ | | SVG | ✅ | | +### Font file formats + +Format | Read +------------- | ------------- +TTF | ✅ +OTF | ✅ +SVG | ✅ + ### Joins and caps Supported Caps: @@ -91,6 +100,40 @@ z | ✅ | close path | ## Examples +### Text +[examples/text.nim](examples/text.nim) +```nim +var font = readFont("tests/fonts/Roboto-Regular_1.ttf") +font.size = 20 + +let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias." + +image.fillText(font.typeset(text, bounds = vec2(180, 180)), vec2(10, 10)) +``` +![example output](examples/text.png) + +### Text spans +[examples/text_spans.nim](examples/text_spans.nim) +```nim +let font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + +proc style(font: Font, size: float32, color: ColorRGBA): Font = + result = font + result.size = size + result.paint.color = color + +let spans = @[ + newSpan("verb [with object] ", font.style(12, rgba(200, 200, 200, 255))), + newSpan("strallow\n", font.style(36, rgba(0, 0, 0, 255))), + newSpan("\nstral·low\n", font.style(13, rgba(0, 127, 244, 255))), + newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ", + font.style(14, rgba(80, 80, 80, 255))) +] + +image.fillText(typeset(spans, bounds = vec2(180, 180)), vec2(10, 10)) +``` +![example output](examples/text_spans.png) + ### Square [examples/square.nim](examples/square.nim) ```nim diff --git a/examples/blur.png b/examples/blur.png index a56cd1b..04340b4 100644 Binary files a/examples/blur.png and b/examples/blur.png differ diff --git a/examples/gradient.png b/examples/gradient.png index 9e76abb..ae1ba1b 100644 Binary files a/examples/gradient.png and b/examples/gradient.png differ diff --git a/examples/heart.png b/examples/heart.png index 968e975..7082326 100644 Binary files a/examples/heart.png and b/examples/heart.png differ diff --git a/examples/image_tiled.png b/examples/image_tiled.png index a892e15..897b8da 100644 Binary files a/examples/image_tiled.png and b/examples/image_tiled.png differ diff --git a/examples/line.png b/examples/line.png index 7bcf2f6..0791cdd 100644 Binary files a/examples/line.png and b/examples/line.png differ diff --git a/examples/masking.png b/examples/masking.png index c95e421..19d6f33 100644 Binary files a/examples/masking.png and b/examples/masking.png differ diff --git a/examples/rounded_rectangle.png b/examples/rounded_rectangle.png index f8596ec..a52a9a8 100644 Binary files a/examples/rounded_rectangle.png and b/examples/rounded_rectangle.png differ diff --git a/examples/shadow.png b/examples/shadow.png index 4bcbae3..d476390 100644 Binary files a/examples/shadow.png and b/examples/shadow.png differ diff --git a/examples/square.png b/examples/square.png index 1f0a431..e828ae2 100644 Binary files a/examples/square.png and b/examples/square.png differ diff --git a/examples/text.nim b/examples/text.nim new file mode 100644 index 0000000..0048b79 --- /dev/null +++ b/examples/text.nim @@ -0,0 +1,12 @@ +import pixie + +let image = newImage(200, 200) +image.fill(rgba(255, 255, 255, 255)) + +var font = readFont("tests/fonts/Roboto-Regular_1.ttf") +font.size = 20 + +let text = "Typesetting is the arrangement and composition of text in graphic design and publishing in both digital and traditional medias." + +image.fillText(font.typeset(text, bounds = vec2(180, 180)), vec2(10, 10)) +image.writeFile("examples/text.png") diff --git a/examples/text.png b/examples/text.png new file mode 100644 index 0000000..7483842 Binary files /dev/null and b/examples/text.png differ diff --git a/examples/text_spans.nim b/examples/text_spans.nim new file mode 100644 index 0000000..fdd0894 --- /dev/null +++ b/examples/text_spans.nim @@ -0,0 +1,22 @@ +import pixie + +let image = newImage(200, 200) +image.fill(rgba(255, 255, 255, 255)) + +let font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + +proc style(font: Font, size: float32, color: ColorRGBA): Font = + result = font + result.size = size + result.paint.color = color + +let spans = @[ + newSpan("verb [with object] ", font.style(12, rgba(200, 200, 200, 255))), + newSpan("strallow\n", font.style(36, rgba(0, 0, 0, 255))), + newSpan("\nstral·low\n", font.style(13, rgba(0, 127, 244, 255))), + newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ", + font.style(14, rgba(80, 80, 80, 255))) +] + +image.fillText(typeset(spans, bounds = vec2(180, 180)), vec2(10, 10)) +image.writeFile("examples/text_spans.png") diff --git a/examples/text_spans.png b/examples/text_spans.png new file mode 100644 index 0000000..41bf361 Binary files /dev/null and b/examples/text_spans.png differ diff --git a/examples/tiger.png b/examples/tiger.png index 1a77f38..6dc2d67 100644 Binary files a/examples/tiger.png and b/examples/tiger.png differ diff --git a/pixie.nimble b/pixie.nimble index fd7bae7..b490a5f 100644 --- a/pixie.nimble +++ b/pixie.nimble @@ -6,7 +6,7 @@ license = "MIT" srcDir = "src" requires "nim >= 1.2.6" -requires "vmath >= 1.0.3" +requires "vmath >= 1.0.4" requires "chroma >= 0.2.5" requires "zippy >= 0.3.5" requires "flatty >= 0.1.3" diff --git a/src/pixie.nim b/src/pixie.nim index fbdc2e9..cb6f3f6 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -1,14 +1,28 @@ import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common, pixie/fileformats/bmp, pixie/fileformats/gif, pixie/fileformats/jpg, - pixie/fileformats/png, pixie/fileformats/svg, pixie/images, pixie/masks, - pixie/paints, pixie/paths, vmath + pixie/fileformats/png, pixie/fileformats/svg, pixie/fonts, pixie/images, + pixie/masks, pixie/paints, pixie/paths, strutils, vmath -export blends, bumpy, chroma, common, images, masks, paints, paths, vmath +export blends, bumpy, chroma, common, fonts, images, masks, paints, paths, vmath type FileFormat* = enum ffPng, ffBmp, ffJpg, ffGif +proc readFont*(filePath: string): Font = + ## Loads a font from a file. + result = + case splitFile(filePath).ext.toLowerAscii(): + of ".ttf": + parseTtf(readFile(filePath)) + of ".otf": + parseOtf(readFile(filePath)) + of ".svg": + parseSvgFont(readFile(filePath)) + else: + raise newException(PixieError, "Unsupported font format") + result.typeface.filePath = filePath + converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline.} = ## Convert a paremultiplied alpha RGBA to a straight alpha RGBA. c.rgba() @@ -55,7 +69,7 @@ proc writeFile*(image: Image, filePath: string, fileFormat: FileFormat) = proc writeFile*(image: Image, filePath: string) = ## Writes an image to a file. - let fileFormat = case splitFile(filePath).ext: + let fileFormat = case splitFile(filePath).ext.toLowerAscii(): of ".png": ffPng of ".bmp": ffBmp of ".jpg", ".jpeg": ffJpg @@ -313,3 +327,80 @@ proc strokePolygon*( var path: Path path.polygon(pos, size, sides) mask.strokePath(path, strokeWidth) + +proc fillText*( + target: Image | Mask, + arrangement: Arrangement, + transform: Vec2 | Mat3 = vec2(0, 0) +) = + ## Fills the text arrangement. + for spanIndex, (start, stop) in arrangement.spans: + let font = arrangement.fonts[spanIndex] + for runeIndex in start .. stop: + var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) + path.transform( + translate(arrangement.positions[runeIndex]) * + scale(vec2(font.scale)) + ) + when type(target) is Image: + target.fillPath(path, font.paint, transform) + else: # target is Mask + target.fillPath(path, transform) + +proc fillText*( + target: Image | Mask, + font: Font, + text: string, + transform: Vec2 | Mat3 = vec2(0, 0), + bounds = vec2(0, 0), + hAlign = haLeft, + vAlign = vaTop +) {.inline.} = + ## Typesets and fills the text. Optional parameters: + ## transform: translation or matrix to apply + ## bounds: width determines wrapping and hAlign, height for vAlign + ## hAlign: horizontal alignment of the text + ## vAlign: vertical alignment of the text + fillText(target, font.typeset(text, bounds, hAlign, vAlign), transform) + +proc strokeText*( + target: Image | Mask, + arrangement: Arrangement, + transform: Vec2 | Mat3 = vec2(0, 0), + strokeWidth = 1.0 +) = + ## Strokes the text arrangement. + for spanIndex, (start, stop) in arrangement.spans: + let font = arrangement.fonts[spanIndex] + for runeIndex in start .. stop: + var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) + path.transform( + translate(arrangement.positions[runeIndex]) * + scale(vec2(font.scale)) + ) + when type(target) is Image: + target.strokePath(path, font.paint, transform, strokeWidth) + else: # target is Mask + target.strokePath(path, transform, strokeWidth) + +proc strokeText*( + target: Image | Mask, + font: Font, + text: string, + transform: Vec2 | Mat3 = vec2(0, 0), + strokeWidth = 1.0, + bounds = vec2(0, 0), + hAlign = haLeft, + vAlign = vaTop +) {.inline.} = + ## Typesets and strokes the text. Optional parameters: + ## transform: translation or matrix to apply + ## bounds: width determines wrapping and hAlign, height for vAlign + ## hAlign: horizontal alignment of the text + ## vAlign: vertical alignment of the text + strokeText( + target, + font.typeset(text, bounds, hAlign, vAlign), + transform, + strokeWidth + ) diff --git a/src/pixie/demo.nim b/src/pixie/demo.nim index 58b4f4e..fae1dbb 100644 --- a/src/pixie/demo.nim +++ b/src/pixie/demo.nim @@ -1,7 +1,6 @@ -import staticglfw except Image -import opengl, pixie -export pixie -export staticglfw except Image +import opengl, pixie, staticglfw except Image + +export pixie, staticglfw except Image var screen* = newImage(800, 600) diff --git a/src/pixie/fileformats/png.nim b/src/pixie/fileformats/png.nim index d44b3ec..939f5fb 100644 --- a/src/pixie/fileformats/png.nim +++ b/src/pixie/fileformats/png.nim @@ -104,9 +104,11 @@ proc unfilter( let filterType = uncompressed[uncompressedIdx(0, y)] case filterType: of 0: # None - for x in 0 ..< rowBytes: - var value = uncompressed[uncompressedIdx(x + 1, y)] - result[unfiteredIdx(x, y)] = value + copyMem( + result[unfiteredIdx(0, y)].addr, + uncompressed[uncompressedIdx(1, y)].unsafeAddr, + rowBytes + ) of 1: # Sub for x in 0 ..< rowBytes: var value = uncompressed[uncompressedIdx(x + 1, y)] @@ -413,7 +415,7 @@ proc decodePng*(data: seq[uint8]): Image = prevChunkType = chunkType - if pos == data.len: + if pos == data.len or prevChunkType == "IEND": break if prevChunkType != "IEND": diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim new file mode 100644 index 0000000..bb6353d --- /dev/null +++ b/src/pixie/fontformats/opentype.nim @@ -0,0 +1,1575 @@ +import bitops, flatty/binny, math, pixie/common, pixie/paths, sets, tables, + unicode, vmath + +## See https://docs.microsoft.com/en-us/typography/opentype/spec/ + +export tables + +type + EncodingRecord* = object + platformID*: uint16 + encodingID*: uint16 + offset*: uint32 + + CmapTable* = ref object + version*: uint16 + numTables*: uint16 + encodingRecords*: seq[EncodingRecord] + runeToGlyphId*: Table[Rune, uint16] + glyphIdToRune*: Table[uint16, Rune] + + HeadTable* = ref object + majorVersion*: uint16 + minorVersion*: uint16 + fontRevision*: float32 + checkSumAdjustment*: uint32 + magicNumber*: uint32 + flags*: uint16 + unitsPerEm*: uint16 + created*: float64 + modified*: float64 + xMin*: int16 + yMin*: int16 + xMax*: int16 + yMax*: int16 + macStyle*: uint16 + lowestRecPPEM*: uint16 + fontDirectionHint*: int16 + indexToLocFormat*: int16 + glyphDataFormat*: int16 + + HheaTable* = ref object + majorVersion*: uint16 + minorVersion*: uint16 + ascender*: int16 + descender*: int16 + lineGap*: int16 + advanceWidthMax*: uint16 + minLeftSideBearing*: int16 + minRightSideBearing*: int16 + xMaxExtent*: int16 + caretSlopeRise*: int16 + caretSlopeRun*: int16 + caretOffset*: int16 + metricDataFormat*: int16 + numberOfHMetrics*: uint16 + + MaxpTable* = ref object + version*: float32 + numGlyphs*: uint16 + maxPoints*: uint16 + maxContours*: uint16 + maxCompositePoints*: uint16 + maxCompositeContours*: uint16 + maxZones*: uint16 + maxTwilightPoints*: uint16 + maxStorage*: uint16 + maxFunctionDefs*: uint16 + maxInstructionDefs*: uint16 + maxStackElements*: uint16 + maxSizeOfInstructions*: uint16 + maxComponentElements*: uint16 + maxComponentDepth*: uint16 + + LongHorMetricRecord* = object + advanceWidth*: uint16 + leftSideBearing*: int16 + + HmtxTable* = ref object + hMetrics*: seq[LongHorMetricRecord] + leftSideBearings*: seq[int16] + + NameRecord* = object + platformID*: uint16 + encodingID*: uint16 + languageID*: uint16 + nameID*: uint16 + length*: uint16 + offset*: uint16 + + NameTable* = ref object + format*: uint16 + count*: uint16 + stringOffset*: uint16 + nameRecords*: seq[NameRecord] + + OS2Table* = ref object + version*: uint16 + xAvgCharWidth*: int16 + usWeightClass*: uint16 + usWidthClass*: uint16 + fsType*: uint16 + ySubscriptXSize*: int16 + ySubscriptYSize*: int16 + ySubscriptXOffset*: int16 + ySubscriptYOffset*: int16 + ySuperscriptXSize*: int16 + ySuperscriptYSize*: int16 + ySuperscriptXOffset*: int16 + ySuperscriptYOffset*: int16 + yStrikeoutSize*: int16 + yStrikeoutPosition*: int16 + sFamilyClass*: int16 + panose*: array[10, uint8] + ulUnicodeRange1*: uint32 + ulUnicodeRange2*: uint32 + ulUnicodeRange3*: uint32 + ulUnicodeRange4*: uint32 + achVendID*: string + fsSelection*: uint16 + usFirstCharIndex*: uint16 + usLastCharIndex*: uint16 + sTypoAscender*: int16 + sTypoDescender*: int16 + sTypoLineGap*: int16 + usWinAscent*: uint16 + usWinDescent*: uint16 + ulCodePageRange1*: uint32 + ulCodePageRange2*: uint32 + sxHeight*: int16 + sCapHeight*: int16 + usDefaultChar*: uint16 + usBreakChar*: uint16 + usMaxContext*: uint16 + usLowerOpticalPointSize*: uint16 + usUpperOpticalPointSize*: uint16 + + LocaTable* = ref object + offsets*: seq[uint32] + + GlyfTable* = ref object + offsets*: seq[uint32] + + KernPair* = object + left*: uint16 + right*: uint16 + value*: int16 + + KernSubTable* = object + version*: uint16 + length*: uint16 + coverage*: uint16 + nPairs*: uint16 + searchRange*: uint16 + entrySelector*: uint16 + rangeShift*: uint16 + kernPairs*: seq[KernPair] + + KernTable* = ref object + version*: uint16 + nTables*: uint16 + subTables*: seq[KernSubTable] + kerningPairs: Table[(uint16, uint16), float32] + + TableRecord* = object + tag*: string + checksum*: uint32 + offset*: uint32 + length*: uint32 + + # LangSys = object + # lookupOrderOffset: uint16 + # requiredFeatureIndex: uint16 + # featureIndexCount: uint16 + # featureIndices: seq[uint16] + + # LangSysRecord = object + # langSysTag: string + # langSysOffset: uint16 + # langSys: LangSys + + # Script = object + # defaultLangSysOffset: uint16 + # langSysCount: uint16 + # langSysRecords: seq[LangSysRecord] + + # ScriptRecord = object + # scriptTag: string + # scriptOffset: uint16 + # script: Script + + # ScriptList = object + # scriptCount: uint16 + # scriptRecords: seq[ScriptRecord] + + # Feature = object + # featureParamsOffset: uint16 + # lookupIndexCount: uint16 + # lookupListIndices: seq[uint16] + + # FeatureRecord = object + # featureTag: string + # featureOffset: uint16 + # feature: Feature + + # FeatureList = object + # featureCount: uint16 + # featureRecords: seq[FeatureRecord] + + RangeRecord = object + startGlyphID: uint16 + endGlyphID: uint16 + startCoverageIndex: uint16 + + Coverage = object + coverageFormat: uint16 + glyphCount: uint16 + glyphArray: seq[uint16] + rangeCount: uint16 + rangeRecords: seq[RangeRecord] + coveredGlyphs: HashSet[uint16] + + ValueRecord = object + xPlacement: int16 + yPlacement: int16 + xAdvance: int16 + yAdvance: int16 + xPlaDeviceOffset: uint16 + yPlaDeviceOffset: uint16 + xAdvDeviceOffset: uint16 + yAdvDeviceOffset: uint16 + + PairValueRecord = object + secondGlyph: uint16 + valueRecord1: ValueRecord + valueRecord2: ValueRecord + + PairSet = object + pairValueCount: uint16 + pairValueRecords: seq[PairValueRecord] + + 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 + valueFormat1: uint16 + valueFormat2: uint16 + 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 + glyphIdToClass1: Table[uint16, uint16] + glyphIdToClass2: Table[uint16, uint16] + classPairAdjustments: Table[(uint16, uint16), int16] + glyphPairAdjustments: Table[(uint16, uint16), int16] + + Lookup = object + lookupType: uint16 + lookupFlag: uint16 + subTableCount: uint16 + subTableOffsets: seq[uint16] + markFilteringSet: uint16 + + LookupList = object + lookupCount: uint16 + lookupOffsets: seq[uint16] + lookups: seq[Lookup] + pairPosTables: seq[PairPos] + + GposTable = ref object + majorVersion: uint16 + minorVersion: uint16 + scriptListOffset: uint16 + featureListOffset: uint16 + lookupListOffset: uint16 + featureVariationsOffset: uint32 + # scriptList: ScriptList + # featureList: FeatureList + lookupList: LookupList + + OpenType* = ref object + buf*: string + version*: uint32 + numTables*: uint16 + searchRange*: uint16 + entrySelector*: uint16 + rangeShift*: uint16 + tableRecords*: Table[string, TableRecord] + cmap*: CmapTable + head*: HeadTable + hhea*: HheaTable + maxp*: MaxpTable + hmtx*: HmtxTable + name*: NameTable + os2*: OS2Table + loca*: LocaTable + glyf*: GlyfTable + kern*: KernTable + gpos*: GposTable + glyphPaths: Table[Rune, Path] + +when defined(release): + {.push checks: off.} + +proc eofCheck(buf: string, readTo: int) {.inline.} = + if readTo > buf.len: + raise newException(PixieError, "Unexpected error reading font data, EOF") + +proc failUnsupported() = + raise newException(PixieError, "Unsupported font data") + +proc readUint16Seq(buf: string, offset, len: int): seq[uint16] = + result = newSeq[uint16](len) + for i in 0 ..< len: + result[i] = buf.readUint16(offset + i * 2).swap() + +proc readFixed32(buf: string, offset: int): float32 = + ## Packed 32-bit value with major and minor version numbers. + ceil(buf.readInt32(offset).swap().float32 / 65536.0 * 100000.0) / 100000.0 + +proc readFixed16(buf: string, offset: int): float32 = + ## Reads 16-bit signed fixed number with the low 14 bits of fraction (2.14). + buf.readInt16(offset).swap().float32 / 16384.0 + +proc readLongDateTime(buf: string, offset: int): float64 = + ## Date and time represented in number of seconds since 12:00 midnight, + ## January 1, 1904, UTC. + buf.readInt64(offset).swap().float64 - 2082844800 + +proc parseCmapTable(buf: string, offset: int): CmapTable = + var i = offset + buf.eofCheck(i + 4) + + result = CmapTable() + result.version = buf.readUint16(i + 0).swap() + result.numTables = buf.readUint16(i + 2).swap() + i += 4 + + for j in 0 ..< result.numTables.int: + buf.eofCheck(i + 8) + + var encodingRecord: EncodingRecord + encodingRecord.platformID = buf.readUint16(i + 0).swap() + encodingRecord.encodingID = buf.readUint16(i + 2).swap() + encodingRecord.offset = buf.readUint32(i + 4).swap() + i += 8 + + if encodingRecord.platformID == 3: + # Windows + var i = offset + encodingRecord.offset.int + buf.eofCheck(i + 2) + + let format = buf.readUint16(i + 0).swap() + if format == 4: + type Format4 = object + format: uint16 + length: uint16 + language: uint16 + segCountX2: uint16 + searchRange: uint16 + entrySelector: uint16 + rangeShift: uint16 + endCodes: seq[uint16] + reservedPad: uint16 + startCodes: seq[uint16] + idDeltas: seq[uint16] + idRangeOffsets: seq[uint16] + + buf.eofCheck(i + 14) + + var subTable: Format4 + subTable.format = format + subTable.length = buf.readUint16(i + 2).swap() + subTable.language = buf.readUint16(i + 4).swap() + subTable.segCountX2 = buf.readUint16(i + 6).swap() + let segCount = (subtable.segCountX2 div 2).int + subTable.searchRange = buf.readUint16(i + 8).swap() + subTable.entrySelector = buf.readUint16(i + 10).swap() + subTable.rangeShift = buf.readUint16(i + 12).swap() + i += 14 + + buf.eofCheck(i + 2 + 4 * segCount * 2) + + subTable.endCodes = buf.readUint16Seq(i, segCount) + i += segCount * 2 + subTable.reservedPad = buf.readUint16(i + 0).swap() + i += 2 + subTable.startCodes = buf.readUint16Seq(i, segCount) + i += segCount * 2 + subTable.idDeltas = buf.readUint16Seq(i, segCount) + i += segCount * 2 + let idRangeOffsetPos = i + subTable.idRangeOffsets = buf.readUint16Seq(i, segCount) + i += segCount * 2 + + for k in 0 ..< segCount: + let + endCode = subTable.endCodes[k] + startCode = subTable.startCodes[k] + idDelta = subTable.idDeltas[k].int + idRangeOffset = subTable.idRangeOffsets[k].int + for c in startCode .. endCode: + var glyphId: int + if idRangeOffset != 0: + var glyphIdOffset = idRangeOffsetPos + k * 2 + glyphIdOffset += idRangeOffset + glyphIdOffset += (c - startCode).int * 2 + buf.eofCheck(glyphIdOffset + 2) + glyphId = buf.readUint16(glyphIdOffset).swap().int + if glyphId != 0: + glyphId = (glyphId + idDelta) and 0xFFFF + else: + glyphId = (c.int + idDelta) and 0xFFFF + + if c != 65535: + result.runeToGlyphId[Rune(c)] = glyphId.uint16 + result.glyphIdToRune[glyphId.uint16] = Rune(c) + else: + # TODO implement other Windows encodingIDs + discard + else: + # TODO implement other cmap platformIDs + discard + +proc parseHeadTable(buf: string, offset: int): HeadTable = + buf.eofCheck(offset + 54) + + result = HeadTable() + result.majorVersion = buf.readUint16(offset + 0).swap() + if result.majorVersion != 1: + failUnsupported() + result.minorVersion = buf.readUint16(offset + 2).swap() + if result.minorVersion != 0: + failUnsupported() + result.fontRevision = buf.readFixed32(offset + 4) + result.checkSumAdjustment = buf.readUint32(offset + 8).swap() + result.magicNumber = buf.readUint32(offset + 12).swap() + result.flags = buf.readUint16(offset + 16).swap() + result.unitsPerEm = buf.readUint16(offset + 18).swap() + result.created = buf.readLongDateTime(offset + 20) + result.modified = buf.readLongDateTime(offset + 28) + result.xMin = buf.readInt16(offset + 36).swap() + result.yMin = buf.readInt16(offset + 38).swap() + result.xMax = buf.readInt16(offset + 40).swap() + result.yMax = buf.readInt16(offset + 42).swap() + result.macStyle = buf.readUint16(offset + 44).swap() + result.lowestRecPPEM = buf.readUint16(offset + 46).swap() + result.fontDirectionHint = buf.readInt16(offset + 48).swap() + result.indexToLocFormat = buf.readInt16(offset + 50).swap() + result.glyphDataFormat = buf.readInt16(offset + 52).swap() + if result.glyphDataFormat != 0: + failUnsupported() + +proc parseHheaTable(buf: string, offset: int): HheaTable = + buf.eofCheck(offset + 36) + + result = HheaTable() + result.majorVersion = buf.readUint16(offset + 0).swap() + if result.majorVersion != 1: + failUnsupported() + result.minorVersion = buf.readUint16(offset + 2).swap() + if result.minorVersion != 0: + failUnsupported() + result.ascender = buf.readInt16(offset + 4).swap() + result.descender = buf.readInt16(offset + 6).swap() + result.lineGap = buf.readInt16(offset + 8).swap() + result.advanceWidthMax = buf.readUint16(offset + 10).swap() + result.minLeftSideBearing = buf.readInt16(offset + 12).swap() + result.minRightSideBearing = buf.readInt16(offset + 14).swap() + result.xMaxExtent = buf.readInt16(offset + 16).swap() + result.caretSlopeRise = buf.readInt16(offset + 18).swap() + result.caretSlopeRun = buf.readInt16(offset + 20).swap() + result.caretOffset = buf.readInt16(offset + 22).swap() + # discard buf.readUint16(offset + 24).swap() # Reserved + # discard buf.readUint16(offset + 26).swap() # Reserved + # discard buf.readUint16(offset + 28).swap() # Reserved + # discard buf.readUint16(offset + 30).swap() # Reserved + result.metricDataFormat = buf.readInt16(offset + 32).swap() + if result.metricDataFormat != 0: + failUnsupported() + result.numberOfHMetrics = buf.readUint16(offset + 34).swap() + +proc parseMaxpTable(buf: string, offset: int): MaxpTable = + buf.eofCheck(offset + 32) + + result = MaxpTable() + result.version = buf.readFixed32(offset + 0) + if result.version != 1.0: + failUnsupported() + result.numGlyphs = buf.readUint16(offset + 4).swap() + result.maxPoints = buf.readUint16(offset + 6).swap() + result.maxContours = buf.readUint16(offset + 8).swap() + result.maxCompositePoints = buf.readUint16(offset + 10).swap() + result.maxCompositeContours = buf.readUint16(offset + 12).swap() + result.maxZones = buf.readUint16(offset + 14).swap() + result.maxTwilightPoints = buf.readUint16(offset + 16).swap() + result.maxStorage = buf.readUint16(offset + 18).swap() + result.maxFunctionDefs = buf.readUint16(offset + 20).swap() + result.maxInstructionDefs = buf.readUint16(offset + 22).swap() + result.maxStackElements = buf.readUint16(offset + 24).swap() + result.maxSizeOfInstructions = buf.readUint16(offset + 26).swap() + result.maxComponentElements = buf.readUint16(offset + 28).swap() + result.maxComponentDepth = buf.readUint16(offset + 30).swap() + +proc parseHmtxTable( + buf: string, offset: int, hhea: HheaTable, maxp: MaxpTable +): HmtxTable = + var i = offset + + let + hMetricsSize = hhea.numberOfHMetrics.int * 4 + leftSideBearingsSize = (maxp.numGlyphs - hhea.numberOfHMetrics).int * 2 + + buf.eofCheck(i + hMetricsSize + leftSideBearingsSize) + + result = HmtxTable() + for glyph in 0 ..< maxp.numGlyphs.int: + if glyph < hhea.numberOfHMetrics.int: + var record = LongHorMetricRecord() + record.advanceWidth = buf.readUint16(i + 0).swap() + record.leftSideBearing = buf.readInt16(i + 2).swap() + result.hMetrics.add(record) + i += 4 + else: + result.leftSideBearings.add(buf.readInt16(i).swap()) + i += 2 + +proc parseNameTable(buf: string, offset: int): NameTable = + var i = offset + + buf.eofCheck(i + 6) + + result = NameTable() + result.format = buf.readUint16(i + 0).swap() + if result.format != 0: + failUnsupported() + result.count = buf.readUint16(i + 2).swap() + result.stringOffset = buf.readUint16(i + 4).swap() + + i += 6 + + buf.eofCheck(i + result.count.int * 12) + + for j in 0 ..< result.count.int: + var record: NameRecord + record.platformID = buf.readUint16(i + 0).swap() + record.encodingID = buf.readUint16(i + 2).swap() + record.languageID = buf.readUint16(i + 4).swap() + record.nameID = buf.readUint16(i + 6).swap() + record.length = buf.readUint16(i + 8).swap() + record.offset = buf.readUint16(i + 10).swap() + i += 12 + +proc parseOS2Table(buf: string, offset: int): OS2Table = + var i = offset + + buf.eofCheck(i + 78) + + result = OS2Table() + result.version = buf.readUint16(i + 0).swap() + result.xAvgCharWidth = buf.readInt16(i + 2).swap() + result.usWeightClass = buf.readUint16(i + 4).swap() + result.usWidthClass = buf.readUint16(i + 6).swap() + result.fsType = buf.readUint16(i + 8).swap() + result.ySubscriptXSize = buf.readInt16(i + 10).swap() + result.ySubscriptYSize = buf.readInt16(i + 12).swap() + result.ySubscriptXOffset = buf.readInt16(i + 14).swap() + result.ySubscriptYOffset = buf.readInt16(i + 16).swap() + result.ySuperscriptXSize = buf.readInt16(i + 18).swap() + result.ySuperscriptYSize = buf.readInt16(i + 20).swap() + result.ySuperscriptXOffset = buf.readInt16(i + 22).swap() + result.ySuperscriptYOffset = buf.readInt16(i + 24).swap() + result.yStrikeoutSize = buf.readInt16(i + 26).swap() + result.yStrikeoutPosition = buf.readInt16(i + 28).swap() + result.sFamilyClass = buf.readInt16(i + 30).swap() + i += 32 + for i in 0 ..< 10: + result.panose[i] = buf.readUint8(i + i) + i += 10 + result.ulUnicodeRange1 = buf.readUint32(i + 0).swap() + result.ulUnicodeRange2 = buf.readUint32(i + 4).swap() + result.ulUnicodeRange3 = buf.readUint32(i + 8).swap() + result.ulUnicodeRange4 = buf.readUint32(i + 12).swap() + result.achVendID = buf.readStr(i + 16, 4) + result.fsSelection = buf.readUint16(i + 20).swap() + result.usFirstCharIndex = buf.readUint16(i + 22).swap() + result.usLastCharIndex = buf.readUint16(i + 24).swap() + result.sTypoAscender = buf.readInt16(i + 26).swap() + result.sTypoDescender = buf.readInt16(i + 28).swap() + result.sTypoLineGap = buf.readInt16(i + 30).swap() + result.usWinAscent = buf.readUint16(i + 32).swap() + result.usWinDescent = buf.readUint16(i + 34).swap() + i += 36 + + if result.version >= 1.uint16: + buf.eofCheck(i + 8) + result.ulCodePageRange1 = buf.readUint32(i + 0).swap() + result.ulCodePageRange2 = buf.readUint32(i + 4).swap() + i += 8 + + if result.version >= 2.uint16: + buf.eofCheck(i + 10) + result.sxHeight = buf.readInt16(i + 0).swap() + result.sCapHeight = buf.readInt16(i + 2).swap() + result.usDefaultChar = buf.readUint16(i + 4).swap() + result.usBreakChar = buf.readUint16(i + 6).swap() + result.usMaxContext = buf.readUint16(i + 8).swap() + i += 10 + + if result.version >= 5.uint16: + buf.eofCheck(i + 4) + result.usLowerOpticalPointSize = buf.readUint16(i + 0).swap() + result.usUpperOpticalPointSize = buf.readUint16(i + 2).swap() + i += 4 + +proc parseLocaTable( + buf: string, offset: int, head: HeadTable, maxp: MaxpTable +): LocaTable = + var i = offset + + result = LocaTable() + if head.indexToLocFormat == 0: + # uint16 + buf.eofCheck(i + maxp.numGlyphs.int * 2) + for _ in 0 ..< maxp.numGlyphs.int: + result.offsets.add(buf.readUint16(i).swap().uint32 * 2) + i += 2 + else: + # uint32 + buf.eofCheck(i + maxp.numGlyphs.int * 4) + for _ in 0 ..< maxp.numGlyphs.int: + result.offsets.add(buf.readUint32(i).swap()) + i += 4 + +proc parseGlyfTable(buf: string, offset: int, loca: LocaTable): GlyfTable = + result = GlyfTable() + result.offsets.setLen(loca.offsets.len) + for glyphId in 0 ..< loca.offsets.len: + result.offsets[glyphId] = offset.uint32 + loca.offsets[glyphId] + +proc parseKernTable(buf: string, offset: int): KernTable = + var i = offset + + buf.eofCheck(i + 2) + + let version = buf.readUint16(i + 0).swap() + i += 2 + + if version == 0: + buf.eofCheck(i + 2) + + result = KernTable() + result.version = version + result.nTables = buf.readUint16(i + 0).swap() + i += 2 + + for _ in 0 ..< result.nTables.int: + buf.eofCheck(i + 14) + + var subTable: KernSubTable + subtable.version = buf.readUint16(i + 0).swap() + if subTable.version != 0: + failUnsupported() + subTable.length = buf.readUint16(i + 2).swap() + subTable.coverage = buf.readUint16(i + 4).swap() + if subTable.coverage shr 8 != 0: + failUnsupported() + subTable.nPairs = buf.readUint16(i + 6).swap() + subTable.searchRange = buf.readUint16(i + 8).swap() + subTable.entrySelector = buf.readUint16(i + 10).swap() + subTable.rangeShift = buf.readUint16(i + 12).swap() + i += 14 + + for _ in 0 ..< subTable.nPairs.int: + buf.eofCheck(i + 6) + + var pair: KernPair + pair.left = buf.readUint16(i + 0).swap() + pair.right = buf.readUint16(i + 2).swap() + pair.value = buf.readInt16(i + 4).swap() + subTable.kernPairs.add(pair) + 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: + 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: + failUnsupported() + +# proc parseLangSys(buf: string, offset: int): LangSys = +# var i = offset + +# buf.eofCheck(i + 6) + +# result.lookupOrderOffset = buf.readUint16(i + 0).swap() +# result.requiredFeatureIndex = buf.readUint16(i + 2).swap() +# result.featureIndexCount = buf.readUint16(i + 4).swap() +# i += 6 + +# buf.eofCheck(i + 2 * result.featureIndexCount.int) + +# result.featureIndices = buf.readUint16Seq(i, result.featureIndexCount.int) + +# proc parseScript(buf: string, offset: int): Script = +# var i = offset + +# buf.eofCheck(i + 4) + +# result.defaultLangSysOffset = buf.readUint16(i + 0).swap() +# result.langSysCount = buf.readUint16(i + 2).swap() +# i += 4 + +# buf.eofCheck(i + result.langSysCount.int * 6) + +# for _ in 0 ..< result.langSysCount.int: +# var langSysRecord: LangSysRecord +# langSysRecord.langSysTag = buf.readStr(i + 0, 4) +# langSysRecord.langSysOffset = buf.readUint16(i + 4).swap() +# langSysRecord.langSys = parseLangSys( +# buf, offset + langSysRecord.langSysOffset.int +# ) +# result.langSysRecords.add(langSysRecord) +# i += 6 + +# proc parseScriptList(buf: string, offset: int): ScriptList = +# var i = offset + +# buf.eofCheck(i + 2) + +# result.scriptCount = buf.readUint16(i + 0).swap() +# i += 2 + +# buf.eofCheck(i + 6 * result.scriptCount.int) + +# for _ in 0 ..< result.scriptCount.int: +# var scriptRecord: ScriptRecord +# scriptRecord.scriptTag = buf.readStr(i + 0, 4) +# scriptRecord.scriptOffset = buf.readUint16(i + 4).swap() +# scriptRecord.script = parseScript( +# buf, offset + scriptRecord.scriptOffset.int +# ) +# result.scriptRecords.add(scriptRecord) +# i += 6 + +# proc parseFeature(buf: string, offset: int): Feature = +# var i = offset + +# buf.eofCheck(i + 4) + +# result.featureParamsOffset = buf.readUint16(i + 0).swap() +# result.lookupIndexCount = buf.readUint16(i + 2).swap() +# i += 4 + +# buf.eofCheck(i + 2 * result.lookupIndexCount.int) + +# result.lookupListIndices = buf.readUint16Seq(i, result.lookupIndexCount.int) + +# proc parseFeatureList(buf: string, offset: int): FeatureList = +# var i = offset + +# buf.eofCheck(i + 2) + +# result.featureCount = buf.readUint16(i + 0).swap() +# i += 2 + +# buf.eofCheck(i + 6 * result.featureCount.int) + +# for _ in 0 ..< result.featureCount.int: +# var featureRecord: FeatureRecord +# featureRecord.featureTag = buf.readStr(i + 0, 4) +# featureRecord.featureOffset = buf.readUint16(i + 4).swap() +# featureRecord.feature = parseFeature( +# buf, offset + featureRecord.featureOffset.int +# ) +# result.featureRecords.add(featureRecord) +# i += 6 + +proc parseRangeRecord(buf: string, offset: int): RangeRecord = + buf.eofCheck(offset + 6) + + result.startGlyphID = buf.readUint16(offset + 0).swap() + result.endGlyphID = buf.readUint16(offset + 2).swap() + result.startCoverageIndex = buf.readUint16(offset + 4).swap() + +proc parseCoverage(buf: string, offset: int): Coverage = + var i = offset + + buf.eofCheck(i + 4) + + result.coverageFormat = buf.readUint16(i + 0).swap() + i += 2 + + case result.coverageFormat: + of 1: + result.glyphCount = buf.readUint16(i + 0).swap() + i += 2 + + buf.eofCheck(i + result.glyphCount.int * 2) + + result.glyphArray = buf.readUint16Seq(i, result.glyphCount.int) + + for ci, glyphId in result.glyphArray: + result.coveredGlyphs.incl(glyphId) + + of 2: + result.rangeCount = buf.readUint16(i + 0).swap() + i += 2 + + result.rangeRecords.setLen(result.rangeCount.int) + for j in 0 ..< result.rangeCount.int: + result.rangeRecords[j] = parseRangeRecord(buf, i) + i += 6 + + 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() + +proc valueFormatSize(valueFormat: uint16): int {.inline.} = + countSetBits(valueFormat) * 2 + +proc parseValueRecord( + buf: string, offset: int, valueFormat: uint16 +): ValueRecord = + buf.eofCheck(offset + valueFormatSize(valueFormat)) + + var i = offset + if (valueFormat and 0b1) != 0: + result.xPlacement = buf.readInt16(i).swap() + i += 2 + if (valueFormat and 0b10) != 0: + result.yPlacement = buf.readInt16(i).swap() + i += 2 + if (valueFormat and 0b100) != 0: + result.xAdvance = buf.readInt16(i).swap() + i += 2 + if (valueFormat and 0b1000) != 0: + result.yAdvance = buf.readInt16(i).swap() + i += 2 + if (valueFormat and 0b10000) != 0: + result.xPlaDeviceOffset = buf.readUint16(i).swap() + i += 2 + if (valueFormat and 0b100000) != 0: + result.yPlaDeviceOffset = buf.readUint16(i).swap() + i += 2 + if (valueFormat and 0b1000000) != 0: + result.xAdvDeviceOffset = buf.readUint16(i).swap() + i += 2 + if (valueFormat and 0b10000000) != 0: + result.yAdvDeviceOffset = buf.readUint16(i).swap() + i += 2 + +proc parsePairValueRecord( + buf: string, offset: int, valueFormat1, valueFormat2: uint16 +): PairValueRecord = + var i = offset + + buf.eofCheck(i + 2) + + result.secondGlyph = buf.readUint16(i + 0).swap() + i += 2 + + result.valueRecord1 = parseValueRecord(buf, i, valueFormat1) + i += valueFormatSize(valueFormat1) + result.valueRecord2 = parseValueRecord(buf, i, valueFormat2) + +proc parsePairSet( + buf: string, offset: int, valueFormat1, valueFormat2: uint16 +): PairSet = + var i = offset + + buf.eofCheck(i + 2) + + result.pairValueCount = buf.readUint16(i + 0).swap() + i += 2 + + let pairValueRecordSize = + 2 + valueFormatSize(valueFormat1) + valueFormatSize(valueFormat2) + + result.pairValueRecords.setLen(result.pairValueCount.int) + for j in 0 ..< result.pairValueCount.int: + result.pairValueRecords[j] = + parsePairValueRecord(buf, i, valueFormat1, valueFormat2) + i += pairValueRecordSize + +proc parseClass2Record( + buf: string, offset: int, valueFormat1, valueFormat2: uint16 +): Class2Record = + var i = offset + + buf.eofCheck( + i + valueFormatSize(valueFormat1) + valueFormatSize(valueFormat2) + ) + + result.valueRecord1 = parseValueRecord(buf, i, valueFormat1) + i += valueFormatSize(valueFormat1) + result.valueRecord2 = parseValueRecord(buf, i, valueFormat2) + +proc parseClass1Record( + buf: string, offset: int, valueFormat1, valueFormat2, class2Count: uint16 +): Class1Record = + var i = offset + + result.class2Records.setLen(class2Count.int) + for j in 0 ..< class2Count.int: + result.class2Records[j] = + parseClass2Record(buf, i, valueFormat1, valueFormat2) + i += valueFormatSize(valueFormat1) + valueFormatSize(valueFormat2) + +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 += 6 + else: + failUnsupported() + +proc parsePairPos(buf: string, offset: int): PairPos = + var i = offset + + buf.eofCheck(i + 4) + + result = PairPos() + result.posFormat = buf.readUint16(i + 0).swap() + i += 2 + + case result.posFormat: + of 1: # Glyph ID pairs + buf.eofCheck(i + 8) + + result.coverageOffset = buf.readUint16(i + 0).swap() + result.valueFormat1 = buf.readUint16(i + 2).swap() + result.valueFormat2 = buf.readUint16(i + 4).swap() + result.pairSetCount = buf.readUint16(i + 6).swap() + i += 8 + + buf.eofCheck(i + 2 * result.pairSetCount.int) + + let pairSetOffsets = buf.readUint16Seq(i + 0, result.pairSetCount.int) + i += 2 * result.pairSetCount.int + + result.pairSets.setLen(result.pairSetCount.int) + for j in 0 ..< result.pairSetCount.int: + result.pairSets[j] = parsePairSet( + buf, + offset + pairSetOffsets[j].int, + result.valueFormat1, + result.valueFormat2 + ) + + result.coverage = parseCoverage(buf, offset + result.coverageOffset.int) + + 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: # Class pairs + 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 = + valueFormatSize(result.valueFormat1) + + valueFormatSize(result.valueFormat2) + + 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.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() + +proc parseLookup(buf: string, offset: int, gpos: GposTable): Lookup = + var i = offset + + buf.eofCheck(i + 6) + + result.lookupType = buf.readUint16(i + 0).swap() + result.lookupFlag = buf.readUint16(i + 2).swap() + result.subTableCount = buf.readUint16(i + 4).swap() + i += 6 + + buf.eofCheck(i + 2 * result.subTableCount.int) + + result.subTableOffsets = buf.readUint16Seq(i, result.subTableCount.int) + i += 2 * result.subTableCount.int + + if (result.lookupFlag and 0x0010) != 0: # USE_MARK_FILTERING_SET + buf.eofCheck(i + 2) + result.markFilteringSet = buf.readUint16(i).swap() + + for subTableOffset in result.subTableOffsets: + if result.lookupType == 2: + 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 + + buf.eofCheck(i + 2) + + result.lookupCount = buf.readUint16(i + 0).swap() + i += 2 + + buf.eofCheck(i + 2 * result.lookupCount.int) + + result.lookupOffsets = buf.readUint16Seq(i, result.lookupCount.int) + + for lookupOffset in result.lookupoffsets: + result.lookups.add(parseLookup(buf, offset + lookupOffset.int, gpos)) + +proc parseGposTable(buf: string, offset: int): GPOSTable = + var i = offset + + buf.eofCheck(i + 10) + + result = GPOSTable() + result.majorVersion = buf.readUint16(i + 0).swap() + result.minorVersion = buf.readUint16(i + 2).swap() + result.scriptListOffset = buf.readUint16(i + 4).swap() + result.featureListOffset = buf.readUint16(i + 6).swap() + result.lookupListOffset = buf.readUint16(i + 8).swap() + i += 10 + + if result.majorVersion != 1: + failUnsupported() + + if result.minorVersion == 0: + discard + elif result.minorVersion == 1: + buf.eofCheck(i + 4) + result.featureVariationsOffset = buf.readUint32(i + 0).swap() + i += 4 + else: + failUnsupported() + + # result.scriptList = parseScriptList(buf, offset + result.scriptListOffset.int) + # result.featureList = + # parseFeatureList(buf, offset + result.featureListOffset.int) + result.lookupList = + parseLookupList(buf, offset + result.lookupListOffset.int, result) + +proc getGlyphId(opentype: OpenType, rune: Rune): uint16 {.inline.} = + if rune in opentype.cmap.runeToGlyphId: + result = opentype.cmap.runeToGlyphId[rune] + else: + discard # Index 0 is the "missing character" glyph + +proc parseGlyph(opentype: OpenType, glyphId: uint16): Path + +proc parseGlyphPath(buf: string, offset, numberOfContours: int): Path = + if numberOfContours < 0: + raise newException(PixieError, "Glyph numberOfContours must be >= 0") + if numberOfContours == 0: + return + + var i = offset + + buf.eofCheck(i + 2 * numberOfContours + 2) + + let endPtsOfContours = buf.readUint16Seq(i, numberOfContours) + i += 2 * numberOfContours + + let instructionLength = buf.readUint16(i + 0).swap().int + i += 2 + + buf.eofCheck(instructionLength) + + # let instructions = buf.readUint8Seq(i, instructionLength) + i += instructionLength + + let + numPoints = endPtsOfContours[^1].int + 1 + flags = block: + var + flags: seq[uint8] + point = 0 + while point < numPoints: + buf.eofCheck(i + 1) + let flag = buf.readUint8(i) + flags.add(flag) + i += 1 + point += 1 + + if (flag and 0b1000) != 0: # REPEAT_FLAG + buf.eofCheck(i + 1) + let repeatCount = buf.readUint8(i).int + i += 1 + for j in 0 ..< repeatCount: + flags.add(flag) + point += 1 + flags + + type TtfCoordinate = object + x*: float32 + y*: float32 + isOnCurve*: bool + + var points = newSeq[TtfCoordinate](numPoints) + + var prevX = 0 + for point, flag in flags: + var x: int + if (flag and 0b10) != 0: + buf.eofCheck(i + 1) + x = buf.readUint8(i).int + i += 1 + if (flag and 0b10000) == 0: + x = -x + else: + if (flag and 0b10000) != 0: + x = 0 + else: + buf.eofCheck(i + 2) + x = buf.readInt16(i).swap().int + i += 2 + prevX += x + if point >= points.len: + failUnsupported() + points[point].x = prevX.float32 + points[point].isOnCurve = (flag and 1) != 0 + + var prevY = 0 + for point, flag in flags: + var y: int + if (flag and 0b100) != 0: + buf.eofCheck(i + 1) + y = buf.readUint8(i).int + i += 1 + if (flag and 0b100000) == 0: + y = -y + else: + if (flag and 0b100000) != 0: + y = 0 + else: + buf.eofCheck(i + 2) + y = buf.readInt16(i).swap().int + i += 2 + prevY += y + points[point].y = prevY.float32 + + var + contours: seq[seq[TtfCoordinate]] + startIdx = 0 + for endIdx in endPtsOfContours: + contours.add(points[startIdx .. endIdx.int]) + startIdx = endIdx.int + 1 + + for contour in contours: + var prev, curr, next: TtfCoordinate + curr = contour[^1] + next = contour[0] + + if curr.isOnCurve: + result.moveTo(curr.x, curr.y) + else: + if next.isOnCurve: + result.moveTo(next.x, next.y) + else: + result.moveTo((curr.x + next.x) / 2, (curr.y + next.y) / 2) + + for point in 0 ..< contour.len: + prev = curr + curr = next + next = contour[(point + 1) mod contour.len] + + if curr.isOnCurve: + result.lineTo(curr.x, curr.y) + else: + var next2 = next + if not next.isOnCurve: + next2 = TtfCoordinate( + x: (curr.x + next.x) / 2, + y: (curr.y + next.y) / 2 + ) + + result.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y) + + result.closePath() + +proc parseCompositeGlyph(opentype: OpenType, offset: int): Path = + var + i = offset + moreComponents = true + while moreComponents: + opentype.buf.eofCheck(i + 4) + + let flags = opentype.buf.readUint16(i + 0).swap() + + i += 2 + + type TtfComponent = object + glyphId: uint16 + xScale: float32 + scale01: float32 + scale10: float32 + yScale: float32 + dx: float32 + dy: float32 + matchedPoints: array[2, int] + + var component = TtfComponent() + component.glyphId = opentype.buf.readUint16(i + 0).swap() + component.xScale = 1 + component.yScale = 1 + + i += 2 + + if (flags and 1) != 0: # The arguments are uint16 + opentype.buf.eofCheck(i + 4) + if (flags and 0b10) != 0: # The arguments are offets + component.dx = opentype.buf.readInt16(i + 0).swap().float32 + component.dy = opentype.buf.readInt16(i + 2).swap().float32 + else: # The arguments are matched points + component.matchedPoints = [ + opentype.buf.readUint16(i + 0).swap().int, + opentype.buf.readUint16(i + 2).swap().int + ] + i += 4 + else: # The arguments are uint8 + opentype.buf.eofCheck(i + 2) + if (flags and 0b10) != 0: # Arguments are offsets + component.dx = opentype.buf.readInt8(i + 0).float32 + component.dy = opentype.buf.readInt8(i + 1).float32 + else: # The arguments are matched points + component.matchedPoints = [ + opentype.buf.readInt8(i + 0).int, + opentype.buf.readInt8(i + 1).int + ] + i += 2 + + # TODO: ROUND_XY_TO_GRID + + if (flags and 0b1000) != 0: # WE_HAVE_A_SCALE + opentype.buf.eofCheck(i + 2) + component.xScale = opentype.buf.readFixed16(i + 0) + component.yScale = component.xScale + i += 2 + elif (flags and 0b1000000) != 0: # WE_HAVE_AN_X_AND_Y_SCALE + opentype.buf.eofCheck(i + 4) + component.xScale = opentype.buf.readFixed16(i + 0) + component.yScale = opentype.buf.readFixed16(i + 2) + i += 4 + elif (flags and 0b10000000) != 0: # WE_HAVE_A_TWO_BY_TWO + opentype.buf.eofCheck(i + 8) + component.xScale = opentype.buf.readFixed16(i + 0) + component.scale10 = opentype.buf.readFixed16(i + 2) + component.scale01 = opentype.buf.readFixed16(i + 4) + component.yScale = opentype.buf.readFixed16(i + 6) + i += 8 + + # if (flags and 0b100000000) != 0: # WE_HAVE_INSTRUCTIONS + # discard + # elif (flags and 0b1000000000) != 0: # USE_MY_METRICS + # discard + # elif (flags and 0b10000000000) != 0: # OVERLAP_COMPOUND + # discard + # elif (flags and 0b100000000000) != 0: # SCALED_COMPONENT_OFFSET + # discard + # elif (flags and 0b1000000000000) != 0: # UNSCALED_COMPONENT_OFFSET + # discard + + var subPath = opentype.parseGlyph(component.glyphId) + subPath.transform(mat3( + component.xScale, component.scale10, 0.0, + component.scale01, component.yScale, 0.0, + component.dx, component.dy, 1.0 + )) + + result.commands.add(subPath.commands) + + moreComponents = (flags and 0b100000) != 0 + +proc parseGlyph(opentype: OpenType, glyphId: uint16): Path = + if glyphId.int >= opentype.glyf.offsets.len: + raise newException(PixieError, "Invalid glyph ID " & $glyphId) + + let glyphOffset = opentype.glyf.offsets[glyphId] + + if glyphId.int + 1 < opentype.glyf.offsets.len and + glyphOffset == opentype.glyf.offsets[glyphId + 1]: + # Empty glyph + return + + var i = glyphOffset.int + opentype.buf.eofCheck(i + 10) + + let + numberOfContours = opentype.buf.readInt16(i + 0).swap().int + # xMin = opentype.buf.readInt16(i + 2).swap() + # yMin = opentype.buf.readInt16(i + 4).swap() + # xMax = opentype.buf.readInt16(i + 6).swap() + # yMax = opentype.buf.readInt16(i + 8).swap() + + i += 10 + + if numberOfContours < 0: + opentype.parseCompositeGlyph(i) + else: + parseGlyphPath(opentype.buf, i, numberOfContours) + +proc parseGlyph(opentype: OpenType, rune: Rune): Path {.inline.} = + opentype.parseGlyph(opentype.getGlyphId(rune)) + +proc getGlyphPath*(opentype: OpenType, rune: Rune): Path = + if rune notin opentype.glyphPaths: + opentype.glyphPaths[rune] = opentype.parseGlyph(rune) + opentype.glyphPaths[rune].transform(scale(vec2(1, -1))) + opentype.glyphPaths[rune] + +proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 = + let glyphId = opentype.getGlyphId(rune).int + if glyphId < opentype.hmtx.hMetrics.len: + result = opentype.hmtx.hMetrics[glyphId].leftSideBearing.float32 + else: + 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 = + if left notin opentype.cmap.runeToGlyphId or + right notin opentype.cmap.runeToGlyphId: + return + + 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() + result.buf = buf + + var i: int + + buf.eofCheck(i + 12) + + result.version = buf.readUint32(i + 0).swap() + result.numTables = buf.readUint16(i + 4).swap() + result.searchRange = buf.readUint16(i + 6).swap() + result.entrySelector = buf.readUint16(i + 8).swap() + result.rangeShift = buf.readUint16(i + 10).swap() + + i += 12 + + buf.eofCheck(i + result.numTables.int * 16) + + for j in 0 ..< result.numTables.int: + var tableRecord: TableRecord + tableRecord.tag = buf.readStr(i + 0, 4) + tableRecord.checksum = buf.readUint32(i + 4).swap() + tableRecord.offset = buf.readUint32(i + 8).swap() + tableRecord.length = buf.readUint32(i + 12).swap() + result.tableRecords[tableRecord.tag] = tableRecord + i += 16 + + const requiredTables = [ + "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "loca", "glyf" + ] + for table in requiredTables: + if table notin result.tableRecords: + raise newException(PixieError, "Missing required font table " & table) + + result.cmap = parseCmapTable(buf, result.tableRecords["cmap"].offset.int) + result.head = parseHeadTable(buf, result.tableRecords["head"].offset.int) + result.hhea = parseHheaTable(buf, result.tableRecords["hhea"].offset.int) + result.maxp = parseMaxpTable(buf, result.tableRecords["maxp"].offset.int) + result.hmtx = parseHmtxTable( + buf, result.tableRecords["hmtx"].offset.int, result.hhea, result.maxp + ) + result.name = parseNameTable(buf, result.tableRecords["name"].offset.int) + result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int) + result.loca = parseLocaTable( + buf, result.tableRecords["loca"].offset.int, result.head, result.maxp + ) + 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) + + if "GPOS" in result.tableRecords: + result.gpos = parseGposTable(buf, result.tableRecords["GPOS"].offset.int) + +when defined(release): + {.pop.} diff --git a/src/pixie/fontformats/svgfont.nim b/src/pixie/fontformats/svgfont.nim new file mode 100644 index 0000000..62a73b8 --- /dev/null +++ b/src/pixie/fontformats/svgfont.nim @@ -0,0 +1,106 @@ +import pixie/common, pixie/paths, strutils, tables, unicode, vmath, xmlparser, xmltree + +type SvgFont* = ref object + unitsPerEm*, ascent*, descent*: float32 + advances: Table[Rune, float32] + glyphPaths: Table[Rune, Path] + kerningPairs: Table[(Rune, Rune), float32] + missingGlyphAdvance: float32 + missingGlyphPath: Path + +proc getGlyphPath*(svgFont: SvgFont, rune: Rune): Path = + if rune in svgFont.glyphPaths: + svgFont.glyphPaths[rune] + else: + svgFont.missingGlyphPath + +proc getAdvance*(svgFont: SvgFont, rune: Rune): float32 = + if rune in svgFont.advances: + svgFont.advances[rune] + else: + svgFont.missingGlyphAdvance + +proc getKerningAdjustment*(svgFont: SvgFont, left, right: Rune): float32 = + let pair = (left, right) + if pair in svgFont.kerningPairs: + result = svgFont.kerningPairs[pair] + +proc failInvalid() = + raise newException(PixieError, "Invalid SVG font data") + +proc parseFloat(node: XmlNode, attr: string): float32 = + let value = node.attr(attr) + if value.len == 0: + raise newException(PixieError, "SVG font missing attr " & attr) + try: + result = parseFloat(value) + except: + failInvalid() + +proc parseSvgFont*(buf: string): SvgFont = + result = SvgFont() + + let + root = parseXml(buf) + defs = root.child("defs") + if defs == nil: + failInvalid() + + let font = defs.child("font") + if font == nil: + failInvalid() + + let defaultAdvance = font.parseFloat("horiz-adv-x") + + for node in font.items: + case node.tag: + of "font-face": + result.unitsPerEm = node.parseFloat("units-per-em") + result.ascent = node.parseFloat("ascent") + result.descent = node.parseFloat("descent") + of "glyph": + let + name = node.attr("glyph-name") + unicode = node.attr("unicode") + if unicode.len > 0 or name == "space": + var + i: int + rune: Rune + if name == "space": + rune = Rune(32) + else: + fastRuneAt(unicode, i, rune, true) + if i == unicode.len: + var advance = defaultAdvance + if node.attr("horiz-adv-x").len > 0: + advance = node.parseFloat("horiz-adv-x") + result.advances[rune] = advance + result.glyphPaths[rune] = parsePath(node.attr("d")) + result.glyphPaths[rune].transform(scale(vec2(1, -1))) + else: + discard # Multi-rune unicode? + of "hkern": + # TODO "g" kerning + let + u1 = node.attr("u1") + u2 = node.attr("u2") + if u1.len > 0 and u2.len > 0: + var + i1, i2: int + left, right: Rune + fastRuneAt(u1, i1, left, true) + fastRuneAt(u2, i2, right, true) + if i1 == u1.len and i2 == u2.len: + let adjustment = -node.parseFloat("k") + result.kerningPairs[(left, right)] = adjustment + else: + discard # Multi-rune unicode? + of "missing-glyph": + var advance = defaultAdvance + if node.attr("horiz-adv-x").len > 0: + advance = node.parseFloat("horiz-adv-x") + result.missingGlyphAdvance = advance + result.missingGlyphPath = parsePath(node.attr("d")) + result.missingGlyphPath.transform(scale(vec2(1, -1))) + else: + discard # Unrecognized font node child diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim new file mode 100644 index 0000000..64747ed --- /dev/null +++ b/src/pixie/fonts.nim @@ -0,0 +1,402 @@ +import bumpy, chroma, pixie/fontformats/opentype, pixie/fontformats/svgfont, + pixie/paints, pixie/paths, unicode, vmath + +const + AutoLineHeight* = -1.float32 ## Use default line height for the font size + LF = Rune(10) + SP = Rune(32) + +type + Typeface* = ref object + opentype: OpenType + svgFont: SvgFont + filePath*: string + + 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. + paint*: Paint + textCase*: TextCase + noKerningAdjustments*: bool ## Optionally disable kerning pair adjustments + + Span* = ref object + text*: string + font*: Font + + Arrangement* = ref object + spans*: seq[(int, int)] + fonts*: seq[Font] + runes*: seq[Rune] + positions*: seq[Vec2] + selectionRects*: seq[Rect] + + HAlignMode* = enum + haLeft + haCenter + haRight + + VAlignMode* = enum + vaTop + vaMiddle + vaBottom + + TextCase* = enum + tcNormal + tcUpper + tcLower + tcTitle + # tcSmallCaps + # tcSmallCapsForced + +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.} = + ## 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.} = + ## The font line gap value in font units. + if typeface.opentype != nil: + result = typeface.opentype.hhea.lineGap.float32 + +proc lineHeight*(typeface: Typeface): float32 {.inline.} = + ## The default line height in font units. + typeface.ascent - typeface.descent + typeface.lineGap + +proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} = + ## The glyph path for the rune. + if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu) + if typeface.opentype != nil: + result = typeface.opentype.getGlyphPath(rune) + else: + result = typeface.svgFont.getGlyphPath(rune) + +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*( + typeface: Typeface, left, right: Rune +): float32 {.inline.} = + ## The kerning adjustment for the rune pair, in pixels. + if typeface.opentype != nil: + typeface.opentype.getKerningAdjustment(left, right) + else: + typeface.svgfont.getKerningAdjustment(left, right) + +proc scale*(font: Font): float32 {.inline.} = + ## The scale factor to transform font units into pixels. + if font.typeface.opentype != nil: + font.size / font.typeface.opentype.head.unitsPerEm.float32 + else: + 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.typeface.ascent - font.typeface.descent + font.typeface.lineGap + round(fontUnits * font.scale) + +proc newSpan*(text: string, font: Font): Span = + result = Span() + result.text = text + result.font = font + +proc convertTextCase(runes: var seq[Rune], textCase: TextCase) = + case textCase: + of tcNormal: + discard + of tcUpper: + for rune in runes.mitems: + rune = rune.toUpper() + of tcLower: + for rune in runes.mitems: + rune = rune.toLower() + of tcTitle: + var prevRune = SP + for rune in runes.mitems: + if prevRune.isWhiteSpace: + rune = rune.toUpper() + prevRune = rune + +proc canWrap(rune: Rune): bool {.inline.} = + rune == Rune(32) or rune.isWhiteSpace() + +proc typeset*( + spans: seq[Span], + bounds = vec2(0, 0), + hAlign = haLeft, + vAlign = vaTop, + wrap = true +): Arrangement = + ## Lays out the character glyphs and returns the arrangement. + ## Optional parameters: + ## bounds: width determines wrapping and hAlign, height for vAlign + ## hAlign: horizontal alignment of the text + ## vAlign: vertical alignment of the text + ## wrap: enable/disable text wrapping + + result = Arrangement() + + block: # Walk and filter the spans + var start: int + for span in spans: + var + i = 0 + rune: Rune + runes: seq[Rune] + while i < span.text.len: + fastRuneAt(span.text, i, rune, true) + # Ignore control runes (0 - 31) except LF for now + if rune.uint32 >= SP.uint32 or rune.uint32 == LF.uint32: + runes.add(rune) + + if runes.len > 0: + runes.convertTextCase(span.font.textCase) + result.runes.add(runes) + result.spans.add((start, start + runes.len - 1)) + result.fonts.add(span.font) + start += runes.len + + if result.runes.len == 0: + return + + result.positions.setLen(result.runes.len) + result.selectionRects.setLen(result.runes.len) + + var lines = @[(0, 0)] # (start, stop) + + block: # Arrange the glyphs horizontally first (handling line breaks) + proc advance(font: Font, runes: seq[Rune], i: int): float32 {.inline.} = + if not font.noKerningAdjustments 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 + for spanIndex, (start, stop) in result.spans: + let font = result.fonts[spanIndex] + for runeIndex in start .. stop: + let rune = result.runes[runeIndex] + if rune == LF: + let advance = font.typeface.getAdvance(SP) * font.scale + result.positions[runeIndex] = at + result.selectionRects[runeIndex] = rect(at.x, at.y, advance, 0) + at.x = 0 + at.y += 1 + prevCanWrap = 0 + lines[^1][1] = runeIndex + # Start a new line if we are not at the end + if runeIndex + 1 < result.runes.len: + lines.add((runeIndex + 1, 0)) + else: + let advance = advance(font, result.runes, runeIndex) + if wrap and rune != SP and bounds.x > 0 and at.x + advance > bounds.x: + # Wrap to new line + at.x = 0 + at.y += 1 + + var lineStart = runeIndex + + # Go back and wrap glyphs after the wrap index down to the next line + if prevCanWrap > 0 and prevCanWrap != runeIndex: + for i in prevCanWrap + 1 ..< runeIndex: + result.positions[i] = at + result.selectionRects[i].xy = vec2(at.x, at.y) + at.x += advance(font, result.runes, i) + dec lineStart + + lines[^1][1] = lineStart - 1 + lines.add((lineStart, 0)) + + if rune.canWrap(): + prevCanWrap = runeIndex + + result.positions[runeIndex] = at + result.selectionRects[runeIndex] = rect(at.x, at.y, advance, 0) + at.x += advance + + lines[^1][1] = result.runes.len - 1 + + if hAlign != haLeft: + # Since horizontal alignment adjustments are different for each line, + # find the start and stop of each line of text. + for (start, stop) in lines: + var furthestX: float32 + for i in countdown(stop, start): + if result.runes[i] != SP and result.runes[i] != LF: + furthestX = result.selectionRects[i].x + result.selectionRects[i].w + break + + var xAdjustment: float32 + case hAlign: + of haLeft: + discard + of haCenter: + xAdjustment = (bounds.x - furthestX) / 2 + of haRight: + xAdjustment = bounds.x - furthestX + + if xAdjustment != 0: + for i in start .. stop: + result.positions[i].x += xAdjustment + result.selectionRects[i].x += xAdjustment + + block: # Nudge selection rects to pixel grid + var at = result.selectionRects[0] + at.x = round(at.x) + for rect in result.selectionRects.mitems: + if rect.y == at.y: + rect.x = at.x + rect.w = round(rect.w) + at.x = rect.x + rect.w + else: + rect.w = round(rect.w) + at.x = rect.w + at.y = rect.y + + block: # Arrange the lines vertically + let initialY = block: + var maxInitialY: float32 + block outer: + for spanIndex, (start, stop) in result.spans: + let + font = result.fonts[spanIndex] + lineHeight = + if font.lineheight >= 0: + font.lineheight + else: + font.defaultLineHeight + var fontUnitInitialY = font.typeface.ascent + font.typeface.lineGap / 2 + if lineHeight != font.defaultLineHeight: + fontUnitInitialY += ( + (lineHeight / font.scale) - font.typeface.lineHeight + ) / 2 + maxInitialY = max(maxInitialY, round(fontUnitInitialY * font.scale)) + for runeIndex in start .. stop: + if runeIndex == lines[0][1]: + break outer + maxInitialY + + var lineHeights = newSeq[float32](lines.len) + block: # Compute each line's line height + var line: int + for spanIndex, (start, stop) in result.spans: + let + font = result.fonts[spanIndex] + fontLineHeight = + if font.lineheight >= 0: + font.lineheight + else: + font.defaultLineHeight + lineHeights[line] = max(lineHeights[line], fontLineHeight) + for runeIndex in start .. stop: + if line + 1 < lines.len and runeIndex == lines[line + 1][0]: + inc line + lineHeights[line] = max(lineHeights[line], fontLineHeight) + # Handle when span and line endings coincide + if line + 1 < lines.len and stop == lines[line][1]: + inc line + + block: # Vertically position the glyphs + var + line: int + baseline = initialY + for spanIndex, (start, stop) in result.spans: + let + font = result.fonts[spanIndex] + lineHeight = + if font.lineheight >= 0: + font.lineheight + else: + font.defaultLineHeight + for runeIndex in start .. stop: + if line + 1 < lines.len and runeIndex == lines[line + 1][0]: + inc line + baseline += lineHeights[line] + result.positions[runeIndex].y = baseline + result.selectionRects[runeIndex].y = + baseline - round(font.typeface.ascent * font.scale) + result.selectionRects[runeIndex].h = lineHeight + + if vAlign != vaTop: + let + finalSelectionRect = result.selectionRects[^1] + furthestY = finalSelectionRect.y + finalSelectionRect.h + + var yAdjustment: float32 + case vAlign: + of vaTop: + discard + of vaMiddle: + yAdjustment = round((bounds.y - furthestY) / 2) + of vaBottom: + yAdjustment = bounds.y - furthestY + + if yAdjustment != 0: + for i in 0 ..< result.positions.len: + result.positions[i].y += yAdjustment + result.selectionRects[i].y += yAdjustment + +proc typeset*( + font: Font, + text: string, + bounds = vec2(0, 0), + hAlign = haLeft, + vAlign = vaTop, + wrap = true +): Arrangement {.inline.} = + ## Lays out the character glyphs and returns the arrangement. + ## Optional parameters: + ## bounds: width determines wrapping and hAlign, height for vAlign + ## hAlign: horizontal alignment of the text + ## vAlign: vertical alignment of the text + ## wrap: enable/disable text wrapping + typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap) + +proc computeBounds*(arrangement: Arrangement): Vec2 = + if arrangement.runes.len > 0: + for i in 0 ..< arrangement.runes.len: + if arrangement.runes[i] != LF: + let rect = arrangement.selectionRects[i] + result.x = max(result.x, rect.x + rect.w) + let finalRect = arrangement.selectionRects[^1] + result.y = finalRect.y + finalRect.h + +proc computeBounds*(font: Font, text: string): Vec2 {.inline.} = + ## Computes the width and height of the text in pixels. + font.typeset(text).computeBounds() + +proc computeBounds*(spans: seq[Span]): Vec2 {.inline.} = + typeset(spans).computeBounds() + +proc parseOtf*(buf: string): Font = + result.typeface = Typeface() + result.typeface.opentype = parseOpenType(buf) + result.size = 12 + result.lineHeight = AutoLineHeight + result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) + +proc parseTtf*(buf: string): Font = + parseOtf(buf) + +proc parseSvgFont*(buf: string): Font = + result.typeface = Typeface() + result.typeface.svgFont = svgfont.parseSvgFont(buf) + result.size = 12 + result.lineHeight = AutoLineHeight + result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255)) diff --git a/src/pixie/paints.nim b/src/pixie/paints.nim index 36212a9..2449b5b 100644 --- a/src/pixie/paints.nim +++ b/src/pixie/paints.nim @@ -1,4 +1,4 @@ -import blends, chroma, images, vmath +import blends, chroma, common, images, vmath type PaintKind* = enum @@ -9,7 +9,7 @@ type pkGradientRadial pkGradientAngular - Paint* = ref object + Paint* = object ## Paint used to fill paths. case kind*: PaintKind of pkSolid: @@ -27,7 +27,7 @@ type color*: ColorRGBX ## Color of the stop position*: float32 ## Gradient Stop position 0..1. -proc toLineSpace(at, to, point: Vec2): float32 = +proc toLineSpace(at, to, point: Vec2): float32 {.inline.} = ## Convert position on to where it would fall on a line between at and to. let d = to - at @@ -60,25 +60,44 @@ proc gradientPut(image: Image, x, y: int, a: float32, stops: seq[ColorStop]) = ) image.setRgbaUnsafe(x, y, color.rgba.rgbx()) -proc fillLinearGradient*( - image: Image, - at, to: Vec2, - stops: seq[ColorStop] -) = +proc fillGradientLinear*(image: Image, paint: Paint) = ## Fills a linear gradient. + + if paint.kind != pkGradientLinear: + raise newException(PixieError, "Paint kind must be " & $pkGradientLinear) + + if paint.gradientHandlePositions.len != 2: + raise newException(PixieError, "Linear gradient requires 2 handles") + + if paint.gradientStops.len == 0: + raise newException(PixieError, "Gradient must have at least 1 color stop") + + let + at = paint.gradientHandlePositions[0] + to = paint.gradientHandlePositions[1] for y in 0 ..< image.height: for x in 0 ..< image.width: - let xy = vec2(x.float32, y.float32) - let a = toLineSpace(at, to, xy) - image.gradientPut(x, y, a, stops) + let + xy = vec2(x.float32, y.float32) + a = toLineSpace(at, to, xy) + image.gradientPut(x, y, a, paint.gradientStops) -proc fillRadialGradient*( - image: Image, - center, edge, skew: Vec2, - stops: seq[ColorStop] -) = +proc fillGradientRadial*(image: Image, paint: Paint) = ## Fills a radial gradient. + + if paint.kind != pkGradientRadial: + raise newException(PixieError, "Paint kind must be " & $pkGradientRadial) + + if paint.gradientHandlePositions.len != 3: + raise newException(PixieError, "Radial gradient requires 3 handles") + + if paint.gradientStops.len == 0: + raise newException(PixieError, "Gradient must have at least 1 color stop") + let + center = paint.gradientHandlePositions[0] + edge = paint.gradientHandlePositions[1] + skew = paint.gradientHandlePositions[2] distanceX = dist(center, edge) distanceY = dist(center, skew) gradientAngle = normalize(center - edge).angle().fixAngle() @@ -89,23 +108,32 @@ proc fillRadialGradient*( ).inverse() for y in 0 ..< image.height: for x in 0 ..< image.width: - let xy = vec2(x.float32, y.float32) - let b = (mat * xy).length() - image.gradientPut(x, y, b, stops) + let + xy = vec2(x.float32, y.float32) + b = (mat * xy).length() + image.gradientPut(x, y, b, paint.gradientStops) + +proc fillGradientAngular*(image: Image, paint: Paint) = + ## Fills an angular gradient. + + if paint.kind != pkGradientAngular: + raise newException(PixieError, "Paint kind must be " & $pkGradientAngular) + + if paint.gradientHandlePositions.len != 3: + raise newException(PixieError, "Angular gradient requires 2 handles") + + if paint.gradientStops.len == 0: + raise newException(PixieError, "Gradient must have at least 1 color stop") -proc fillAngularGradient*( - image: Image, - center, edge, skew: Vec2, - stops: seq[ColorStop] -) = - ## Angular gradient. - # TODO: make edge between start and end anti-aliased. let - gradientAngle = normalize(edge - center).angle().fixAngle() + center = paint.gradientHandlePositions[0] + edge = paint.gradientHandlePositions[1] + # TODO: make edge between start and end anti-aliased. + let gradientAngle = normalize(edge - center).angle().fixAngle() for y in 0 ..< image.height: for x in 0 ..< image.width: let xy = vec2(x.float32, y.float32) angle = normalize(xy - center).angle() a = (angle + gradientAngle + PI/2).fixAngle() / 2 / PI + 0.5 - image.gradientPut(x, y, a, stops) + image.gradientPut(x, y, a, paint.gradientStops) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 49fb2ca..618a2c0 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -48,6 +48,11 @@ proc maxScale(m: Mat3): float32 = vec2(m[1, 0], m[1, 1]).length ) +proc isRelative(kind: PathCommandKind): bool = + kind in { + RMove, RLine, TQuad, RTQuad, RHLine, RVLine, RCubic, RSCubic, RQuad, RArc + } + proc parameterCount(kind: PathCommandKind): int = ## Returns number of parameters a path command has. case kind: @@ -231,7 +236,17 @@ proc parsePath*(path: string): Path = proc transform*(path: var Path, mat: Mat3) = ## Apply a matrix transform to a path. + if mat == mat3(): + return + + if path.commands.len > 0 and path.commands[0].kind == RMove: + path.commands[0].kind = Move + for command in path.commands.mitems: + var mat = mat + if command.kind.isRelative(): + mat.pos = vec2(0) + case command.kind: of Close: discard @@ -984,7 +999,7 @@ proc partitionSegments( numPartitions = min(maxPartitions, max(1, segmentCount div 10).uint32) partitionHeight = (height.uint32 div numPartitions) - var partitions = newSeq[seq[(Segment, int16)]](numPartitions) + result.setLen(numPartitions) for shape in shapes: for segment in shape.segments: if segment.at.y == segment.to.y: # Skip horizontal @@ -997,17 +1012,15 @@ proc partitionSegments( winding = -1 if partitionHeight == 0: - partitions[0].add((segment, winding)) + result[0].add((segment, winding)) else: var atPartition = max(0, segment.at.y).uint32 div partitionHeight toPartition = max(0, ceil(segment.to.y)).uint32 div partitionHeight - atPartition = clamp(atPartition, 0, partitions.high.uint32) - toPartition = clamp(toPartition, 0, partitions.high.uint32) + atPartition = clamp(atPartition, 0, result.high.uint32) + toPartition = clamp(toPartition, 0, result.high.uint32) for i in atPartition .. toPartition: - partitions[i].add((segment, winding)) - - partitions + result[i].add((segment, winding)) template computeCoverages( coverages: var seq[uint8], @@ -1415,6 +1428,18 @@ proc parseSomePath( elif type(path) is seq[seq[Vec2]]: path +proc transform(shapes: var seq[seq[Vec2]], transform: Vec2 | Mat3) = + when type(transform) is Vec2: + if transform != vec2(0, 0): + for shape in shapes.mitems: + for segment in shape.mitems: + segment += transform + else: + if transform != mat3(): + for shape in shapes.mitems: + for segment in shape.mitems: + segment = transform * segment + proc fillPath*( image: Image, path: SomePath, @@ -1439,12 +1464,7 @@ proc fillPath*( else: let pixelScale = 1.0 var shapes = parseSomePath(path, pixelScale) - for shape in shapes.mitems: - for segment in shape.mitems: - when type(transform) is Vec2: - segment += transform - else: - segment = transform * segment + shapes.transform(transform) image.fillShapes(shapes, color, windingRule, blendMode) proc fillPath*( @@ -1467,30 +1487,26 @@ proc fillPath*( else: let pixelScale = 1.0 var shapes = parseSomePath(path, pixelScale) - for shape in shapes.mitems: - for segment in shape.mitems: - when type(transform) is Vec2: - segment += transform - else: - segment = transform * segment + shapes.transform(transform) mask.fillShapes(shapes, windingRule) proc fillPath*( image: Image, path: SomePath, paint: Paint, - windingRule = wrNonZero, + transform: Vec2 | Mat3 = vec2(), + windingRule = wrNonZero ) = ## Fills a path. if paint.kind == pkSolid: - image.fillPath(path, paint.color) + image.fillPath(path, paint.color, transform) return let mask = newMask(image.width, image.height) fill = newImage(image.width, image.height) - mask.fillPath(parseSomePath(path), windingRule) + mask.fillPath(parseSomePath(path), transform, windingRule) case paint.kind: of pkSolid: @@ -1500,25 +1516,11 @@ proc fillPath*( of pkImageTiled: fill.drawTiled(paint.image, paint.imageMat) of pkGradientLinear: - fill.fillLinearGradient( - paint.gradientHandlePositions[0], - paint.gradientHandlePositions[1], - paint.gradientStops - ) + fill.fillGradientLinear(paint) of pkGradientRadial: - fill.fillRadialGradient( - paint.gradientHandlePositions[0], - paint.gradientHandlePositions[1], - paint.gradientHandlePositions[2], - paint.gradientStops - ) + fill.fillGradientRadial(paint) of pkGradientAngular: - fill.fillAngularGradient( - paint.gradientHandlePositions[0], - paint.gradientHandlePositions[1], - paint.gradientHandlePositions[2], - paint.gradientStops - ) + fill.fillGradientAngular(paint) fill.draw(mask) image.draw(fill, blendMode = paint.blendMode) @@ -1556,12 +1558,7 @@ proc strokePath*( var strokeShapes = strokeShapes( parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin ) - for shape in strokeShapes.mitems: - for segment in shape.mitems: - when type(transform) is Vec2: - segment += transform - else: - segment = transform * segment + strokeShapes.transform(transform) image.fillShapes(strokeShapes, color, wrNonZero, blendMode) proc strokePath*( @@ -1593,13 +1590,47 @@ proc strokePath*( var strokeShapes = strokeShapes( parseSomePath(path, pixelScale), strokeWidth, lineCap, lineJoin ) - for shape in strokeShapes.mitems: - for segment in shape.mitems: - when type(transform) is Vec2: - segment += transform - else: - segment = transform * segment + strokeShapes.transform(transform) mask.fillShapes(strokeShapes, wrNonZero) +proc strokePath*( + image: Image, + path: SomePath, + paint: Paint, + transform: Vec2 | Mat3 = vec2(), + strokeWidth = 1.0, + lineCap = lcButt, + lineJoin = ljMiter +) = + ## Fills a path. + if paint.kind == pkSolid: + image.strokePath( + path, paint.color, transform, strokeWidth, lineCap, lineJoin + ) + return + + let + mask = newMask(image.width, image.height) + fill = newImage(image.width, image.height) + + mask.strokePath(parseSomePath(path), transform) + + case paint.kind: + of pkSolid: + discard # Handled above + of pkImage: + fill.draw(paint.image, paint.imageMat) + of pkImageTiled: + fill.drawTiled(paint.image, paint.imageMat) + of pkGradientLinear: + fill.fillGradientLinear(paint) + of pkGradientRadial: + fill.fillGradientRadial(paint) + of pkGradientAngular: + fill.fillGradientAngular(paint) + + fill.draw(mask) + image.draw(fill, blendMode = paint.blendMode) + when defined(release): {.pop.} diff --git a/tests/benchmark_fonts.nim b/tests/benchmark_fonts.nim new file mode 100644 index 0000000..301b3b8 --- /dev/null +++ b/tests/benchmark_fonts.nim @@ -0,0 +1,19 @@ +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." + +var font = readFont("tests/fonts/Roboto-Regular_1.ttf") +font.size = 16 + +let + image = newImage(500, 300) + mask = newMask(500, 300) + +timeIt "typeset": + discard font.typeset(text, bounds = image.wh) + +timeIt "rasterize": + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, text, bounds = image.wh) + # mask.fill(0) + # mask.fillText(font, text, bounds = mask.wh) diff --git a/tests/common.nim b/tests/common.nim new file mode 100644 index 0000000..697c081 --- /dev/null +++ b/tests/common.nim @@ -0,0 +1,7 @@ +import algorithm, os + +proc findAllFonts*(rootPath: string): seq[string] = + for fontPath in walkDirRec(rootPath): + if splitFile(fontPath).ext in [".ttf", ".otf"]: + result.add(fontPath) + result.sort() diff --git a/tests/fonts/Aclonica-Regular_1.ttf b/tests/fonts/Aclonica-Regular_1.ttf new file mode 100644 index 0000000..bbe191d Binary files /dev/null and b/tests/fonts/Aclonica-Regular_1.ttf differ diff --git a/tests/fonts/Changa-Bold.svg b/tests/fonts/Changa-Bold.svg new file mode 100644 index 0000000..ca4cefa --- /dev/null +++ b/tests/fonts/Changa-Bold.svg @@ -0,0 +1,2420 @@ + + + + +Created by FontForge 20200306 at Thu Oct 17 17:37:15 2019 + By convertio +Copyright 2011 The Changa Project Authors (https://github.com/etunni/Changa) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fonts/DejaVuSans.svg b/tests/fonts/DejaVuSans.svg new file mode 100644 index 0000000..d703ac0 --- /dev/null +++ b/tests/fonts/DejaVuSans.svg @@ -0,0 +1,18479 @@ + + + + +Created by FontForge 20170910 at Sun May 17 10:12:36 2015 + By Jimmy Wärting +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. +DejaVu changes are in public domain + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fonts/IBMPlexSans-Regular.svg b/tests/fonts/IBMPlexSans-Regular.svg new file mode 100644 index 0000000..5c37f3e --- /dev/null +++ b/tests/fonts/IBMPlexSans-Regular.svg @@ -0,0 +1,22584 @@ + + + + +Created by FontForge 20170924 at Wed Nov 21 14:06:34 2018 + By www-data +Copyright 2018 IBM Corp. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fonts/IBMPlexSans-Regular_2.ttf b/tests/fonts/IBMPlexSans-Regular_2.ttf new file mode 100644 index 0000000..705fdf6 Binary files /dev/null and b/tests/fonts/IBMPlexSans-Regular_2.ttf differ diff --git a/tests/fonts/Moon-Bold.svg b/tests/fonts/Moon-Bold.svg new file mode 100644 index 0000000..2b5002b --- /dev/null +++ b/tests/fonts/Moon-Bold.svg @@ -0,0 +1,616 @@ + + + + +Created by FontForge 20170910 at Sat Feb 7 18:47:58 2015 + By Jimmy Wärting +Moon (c) Jack Harvatt 2015. All Rights Reserved + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fonts/NotoSans-Regular_4.ttf b/tests/fonts/NotoSans-Regular_4.ttf new file mode 100644 index 0000000..170c15c Binary files /dev/null and b/tests/fonts/NotoSans-Regular_4.ttf differ diff --git a/tests/fonts/Pacifico-Regular_4.ttf b/tests/fonts/Pacifico-Regular_4.ttf new file mode 100644 index 0000000..27765d0 Binary files /dev/null and b/tests/fonts/Pacifico-Regular_4.ttf differ diff --git a/tests/fonts/Roboto-Regular_1.ttf b/tests/fonts/Roboto-Regular_1.ttf new file mode 100644 index 0000000..2c97eea Binary files /dev/null and b/tests/fonts/Roboto-Regular_1.ttf differ diff --git a/tests/fonts/Ubuntu-Regular_1.ttf b/tests/fonts/Ubuntu-Regular_1.ttf new file mode 100644 index 0000000..2001d6e Binary files /dev/null and b/tests/fonts/Ubuntu-Regular_1.ttf differ diff --git a/tests/fonts/Ubuntu.svg b/tests/fonts/Ubuntu.svg new file mode 100644 index 0000000..ddb765e --- /dev/null +++ b/tests/fonts/Ubuntu.svg @@ -0,0 +1,19406 @@ + + + + +Created by FontForge 20170910 at Wed Jul 8 18:49:55 2015 + By Jimmy Wärting +Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fonts/diffs/alignments.png b/tests/fonts/diffs/alignments.png new file mode 100644 index 0000000..016cd6e Binary files /dev/null and b/tests/fonts/diffs/alignments.png differ diff --git a/tests/fonts/diffs/basic1.png b/tests/fonts/diffs/basic1.png new file mode 100644 index 0000000..cb654e8 Binary files /dev/null and b/tests/fonts/diffs/basic1.png differ diff --git a/tests/fonts/diffs/basic2.png b/tests/fonts/diffs/basic2.png new file mode 100644 index 0000000..b933717 Binary files /dev/null and b/tests/fonts/diffs/basic2.png differ diff --git a/tests/fonts/diffs/basic3.png b/tests/fonts/diffs/basic3.png new file mode 100644 index 0000000..c179aaf Binary files /dev/null and b/tests/fonts/diffs/basic3.png differ diff --git a/tests/fonts/diffs/basic4.png b/tests/fonts/diffs/basic4.png new file mode 100644 index 0000000..8058ed1 Binary files /dev/null and b/tests/fonts/diffs/basic4.png differ diff --git a/tests/fonts/diffs/basic5.png b/tests/fonts/diffs/basic5.png new file mode 100644 index 0000000..3dcbe4e Binary files /dev/null and b/tests/fonts/diffs/basic5.png differ diff --git a/tests/fonts/diffs/basic6.png b/tests/fonts/diffs/basic6.png new file mode 100644 index 0000000..8abcfb7 Binary files /dev/null and b/tests/fonts/diffs/basic6.png differ diff --git a/tests/fonts/diffs/basic7.png b/tests/fonts/diffs/basic7.png new file mode 100644 index 0000000..7cae15b Binary files /dev/null and b/tests/fonts/diffs/basic7.png differ diff --git a/tests/fonts/diffs/basic8.png b/tests/fonts/diffs/basic8.png new file mode 100644 index 0000000..1bbbae0 Binary files /dev/null and b/tests/fonts/diffs/basic8.png differ diff --git a/tests/fonts/diffs/basic9.png b/tests/fonts/diffs/basic9.png new file mode 100644 index 0000000..1cac49a Binary files /dev/null and b/tests/fonts/diffs/basic9.png differ diff --git a/tests/fonts/diffs/huge1.png b/tests/fonts/diffs/huge1.png new file mode 100644 index 0000000..4a543a2 Binary files /dev/null and b/tests/fonts/diffs/huge1.png differ diff --git a/tests/fonts/diffs/huge1_nokern.png b/tests/fonts/diffs/huge1_nokern.png new file mode 100644 index 0000000..6bfeb09 Binary files /dev/null and b/tests/fonts/diffs/huge1_nokern.png differ diff --git a/tests/fonts/diffs/huge2.png b/tests/fonts/diffs/huge2.png new file mode 100644 index 0000000..8bcfbe9 Binary files /dev/null and b/tests/fonts/diffs/huge2.png differ diff --git a/tests/fonts/diffs/huge2_nokern.png b/tests/fonts/diffs/huge2_nokern.png new file mode 100644 index 0000000..a1d635e Binary files /dev/null and b/tests/fonts/diffs/huge2_nokern.png differ diff --git a/tests/fonts/diffs/huge3.png b/tests/fonts/diffs/huge3.png new file mode 100644 index 0000000..0b05db3 Binary files /dev/null and b/tests/fonts/diffs/huge3.png differ diff --git a/tests/fonts/diffs/huge3_nokern.png b/tests/fonts/diffs/huge3_nokern.png new file mode 100644 index 0000000..f22cb70 Binary files /dev/null and b/tests/fonts/diffs/huge3_nokern.png differ diff --git a/tests/fonts/diffs/lines1.png b/tests/fonts/diffs/lines1.png new file mode 100644 index 0000000..6adf135 Binary files /dev/null and b/tests/fonts/diffs/lines1.png differ diff --git a/tests/fonts/diffs/lines2.png b/tests/fonts/diffs/lines2.png new file mode 100644 index 0000000..bc3a8f3 Binary files /dev/null and b/tests/fonts/diffs/lines2.png differ diff --git a/tests/fonts/diffs/pairs1.png b/tests/fonts/diffs/pairs1.png new file mode 100644 index 0000000..3040a43 Binary files /dev/null and b/tests/fonts/diffs/pairs1.png differ diff --git a/tests/fonts/diffs/pairs2.png b/tests/fonts/diffs/pairs2.png new file mode 100644 index 0000000..9c0df3e Binary files /dev/null and b/tests/fonts/diffs/pairs2.png differ diff --git a/tests/fonts/diffs/pairs3.png b/tests/fonts/diffs/pairs3.png new file mode 100644 index 0000000..8d1eed3 Binary files /dev/null and b/tests/fonts/diffs/pairs3.png differ diff --git a/tests/fonts/diffs/paragraph1.png b/tests/fonts/diffs/paragraph1.png new file mode 100644 index 0000000..d774c46 Binary files /dev/null and b/tests/fonts/diffs/paragraph1.png differ diff --git a/tests/fonts/diffs/paragraph1_2.png b/tests/fonts/diffs/paragraph1_2.png new file mode 100644 index 0000000..323d9a5 Binary files /dev/null and b/tests/fonts/diffs/paragraph1_2.png differ diff --git a/tests/fonts/diffs/paragraph1_3.png b/tests/fonts/diffs/paragraph1_3.png new file mode 100644 index 0000000..30bdd17 Binary files /dev/null and b/tests/fonts/diffs/paragraph1_3.png differ diff --git a/tests/fonts/diffs/paragraph1_nokern.png b/tests/fonts/diffs/paragraph1_nokern.png new file mode 100644 index 0000000..e5e4952 Binary files /dev/null and b/tests/fonts/diffs/paragraph1_nokern.png differ diff --git a/tests/fonts/diffs/paragraph1_nokern_2.png b/tests/fonts/diffs/paragraph1_nokern_2.png new file mode 100644 index 0000000..dbc6e04 Binary files /dev/null and b/tests/fonts/diffs/paragraph1_nokern_2.png differ diff --git a/tests/fonts/diffs/paragraph1_nokern_3.png b/tests/fonts/diffs/paragraph1_nokern_3.png new file mode 100644 index 0000000..96a410d Binary files /dev/null and b/tests/fonts/diffs/paragraph1_nokern_3.png differ diff --git a/tests/fonts/diffs/paragraph2.png b/tests/fonts/diffs/paragraph2.png new file mode 100644 index 0000000..40d8b94 Binary files /dev/null and b/tests/fonts/diffs/paragraph2.png differ diff --git a/tests/fonts/diffs/paragraph2_2.png b/tests/fonts/diffs/paragraph2_2.png new file mode 100644 index 0000000..a71d81d Binary files /dev/null and b/tests/fonts/diffs/paragraph2_2.png differ diff --git a/tests/fonts/diffs/paragraph2_3.png b/tests/fonts/diffs/paragraph2_3.png new file mode 100644 index 0000000..6e9f6e9 Binary files /dev/null and b/tests/fonts/diffs/paragraph2_3.png differ diff --git a/tests/fonts/diffs/paragraph2_nokern.png b/tests/fonts/diffs/paragraph2_nokern.png new file mode 100644 index 0000000..11da7fb Binary files /dev/null and b/tests/fonts/diffs/paragraph2_nokern.png differ diff --git a/tests/fonts/diffs/paragraph2_nokern_2.png b/tests/fonts/diffs/paragraph2_nokern_2.png new file mode 100644 index 0000000..d95bc93 Binary files /dev/null and b/tests/fonts/diffs/paragraph2_nokern_2.png differ diff --git a/tests/fonts/diffs/paragraph2_nokern_3.png b/tests/fonts/diffs/paragraph2_nokern_3.png new file mode 100644 index 0000000..5c7d38b Binary files /dev/null and b/tests/fonts/diffs/paragraph2_nokern_3.png differ diff --git a/tests/fonts/diffs/paragraph3.png b/tests/fonts/diffs/paragraph3.png new file mode 100644 index 0000000..bc51484 Binary files /dev/null and b/tests/fonts/diffs/paragraph3.png differ diff --git a/tests/fonts/diffs/paragraph3_2.png b/tests/fonts/diffs/paragraph3_2.png new file mode 100644 index 0000000..8100141 Binary files /dev/null and b/tests/fonts/diffs/paragraph3_2.png differ diff --git a/tests/fonts/diffs/paragraph3_3.png b/tests/fonts/diffs/paragraph3_3.png new file mode 100644 index 0000000..c9c2e7b Binary files /dev/null and b/tests/fonts/diffs/paragraph3_3.png differ diff --git a/tests/fonts/diffs/paragraph3_nokern.png b/tests/fonts/diffs/paragraph3_nokern.png new file mode 100644 index 0000000..25c02cb Binary files /dev/null and b/tests/fonts/diffs/paragraph3_nokern.png differ diff --git a/tests/fonts/diffs/paragraph3_nokern_2.png b/tests/fonts/diffs/paragraph3_nokern_2.png new file mode 100644 index 0000000..4c6b49a Binary files /dev/null and b/tests/fonts/diffs/paragraph3_nokern_2.png differ diff --git a/tests/fonts/diffs/paragraph3_nokern_3.png b/tests/fonts/diffs/paragraph3_nokern_3.png new file mode 100644 index 0000000..f748d82 Binary files /dev/null and b/tests/fonts/diffs/paragraph3_nokern_3.png differ diff --git a/tests/fonts/diffs/paragraph4.png b/tests/fonts/diffs/paragraph4.png new file mode 100644 index 0000000..8696251 Binary files /dev/null and b/tests/fonts/diffs/paragraph4.png differ diff --git a/tests/fonts/diffs/paragraph4_2.png b/tests/fonts/diffs/paragraph4_2.png new file mode 100644 index 0000000..fde9201 Binary files /dev/null and b/tests/fonts/diffs/paragraph4_2.png differ diff --git a/tests/fonts/diffs/paragraph4_3.png b/tests/fonts/diffs/paragraph4_3.png new file mode 100644 index 0000000..2ecbcd1 Binary files /dev/null and b/tests/fonts/diffs/paragraph4_3.png differ diff --git a/tests/fonts/diffs/paragraph4_nokern.png b/tests/fonts/diffs/paragraph4_nokern.png new file mode 100644 index 0000000..bfd42d2 Binary files /dev/null and b/tests/fonts/diffs/paragraph4_nokern.png differ diff --git a/tests/fonts/diffs/paragraph4_nokern_2.png b/tests/fonts/diffs/paragraph4_nokern_2.png new file mode 100644 index 0000000..51cf884 Binary files /dev/null and b/tests/fonts/diffs/paragraph4_nokern_2.png differ diff --git a/tests/fonts/diffs/paragraph4_nokern_3.png b/tests/fonts/diffs/paragraph4_nokern_3.png new file mode 100644 index 0000000..e2cd98e Binary files /dev/null and b/tests/fonts/diffs/paragraph4_nokern_3.png differ diff --git a/tests/fonts/diffs/paragraph5.png b/tests/fonts/diffs/paragraph5.png new file mode 100644 index 0000000..6c54306 Binary files /dev/null and b/tests/fonts/diffs/paragraph5.png differ diff --git a/tests/fonts/diffs/paragraph5_2.png b/tests/fonts/diffs/paragraph5_2.png new file mode 100644 index 0000000..f84f601 Binary files /dev/null and b/tests/fonts/diffs/paragraph5_2.png differ diff --git a/tests/fonts/diffs/paragraph5_3.png b/tests/fonts/diffs/paragraph5_3.png new file mode 100644 index 0000000..af155ee Binary files /dev/null and b/tests/fonts/diffs/paragraph5_3.png differ diff --git a/tests/fonts/diffs/paragraph5_nokern.png b/tests/fonts/diffs/paragraph5_nokern.png new file mode 100644 index 0000000..479da65 Binary files /dev/null and b/tests/fonts/diffs/paragraph5_nokern.png differ diff --git a/tests/fonts/diffs/paragraph5_nokern_2.png b/tests/fonts/diffs/paragraph5_nokern_2.png new file mode 100644 index 0000000..7cfeff7 Binary files /dev/null and b/tests/fonts/diffs/paragraph5_nokern_2.png differ diff --git a/tests/fonts/diffs/paragraph5_nokern_3.png b/tests/fonts/diffs/paragraph5_nokern_3.png new file mode 100644 index 0000000..5540d68 Binary files /dev/null and b/tests/fonts/diffs/paragraph5_nokern_3.png differ diff --git a/tests/fonts/diffs/selection_rects1.png b/tests/fonts/diffs/selection_rects1.png new file mode 100644 index 0000000..bffddd6 Binary files /dev/null and b/tests/fonts/diffs/selection_rects1.png differ diff --git a/tests/fonts/diffs/selection_rects2.png b/tests/fonts/diffs/selection_rects2.png new file mode 100644 index 0000000..28aad88 Binary files /dev/null and b/tests/fonts/diffs/selection_rects2.png differ diff --git a/tests/fonts/diffs/selection_rects3.png b/tests/fonts/diffs/selection_rects3.png new file mode 100644 index 0000000..8349b78 Binary files /dev/null and b/tests/fonts/diffs/selection_rects3.png differ diff --git a/tests/fonts/diffs/spans1.png b/tests/fonts/diffs/spans1.png new file mode 100644 index 0000000..ab20d31 Binary files /dev/null and b/tests/fonts/diffs/spans1.png differ diff --git a/tests/fonts/diffs/spans2.png b/tests/fonts/diffs/spans2.png new file mode 100644 index 0000000..87ef007 Binary files /dev/null and b/tests/fonts/diffs/spans2.png differ diff --git a/tests/fonts/diffs/spans4.png b/tests/fonts/diffs/spans4.png new file mode 100644 index 0000000..ea0cd1f Binary files /dev/null and b/tests/fonts/diffs/spans4.png differ diff --git a/tests/fonts/diffs/spans5.png b/tests/fonts/diffs/spans5.png new file mode 100644 index 0000000..754ece4 Binary files /dev/null and b/tests/fonts/diffs/spans5.png differ diff --git a/tests/fonts/image_fill.png b/tests/fonts/image_fill.png new file mode 100644 index 0000000..74ca38d Binary files /dev/null and b/tests/fonts/image_fill.png differ diff --git a/tests/fonts/image_paint_fill.png b/tests/fonts/image_paint_fill.png new file mode 100644 index 0000000..76693da Binary files /dev/null and b/tests/fonts/image_paint_fill.png differ diff --git a/tests/fonts/image_stroke.png b/tests/fonts/image_stroke.png new file mode 100644 index 0000000..ee6ea66 Binary files /dev/null and b/tests/fonts/image_stroke.png differ diff --git a/tests/fonts/mask_fill.png b/tests/fonts/mask_fill.png new file mode 100644 index 0000000..73183c4 Binary files /dev/null and b/tests/fonts/mask_fill.png differ diff --git a/tests/fonts/mask_stroke.png b/tests/fonts/mask_stroke.png new file mode 100644 index 0000000..f3d82d5 Binary files /dev/null and b/tests/fonts/mask_stroke.png differ diff --git a/tests/fonts/masters/alignments.png b/tests/fonts/masters/alignments.png new file mode 100644 index 0000000..9e267e4 Binary files /dev/null and b/tests/fonts/masters/alignments.png differ diff --git a/tests/fonts/masters/basic1.png b/tests/fonts/masters/basic1.png new file mode 100644 index 0000000..b5a34a7 Binary files /dev/null and b/tests/fonts/masters/basic1.png differ diff --git a/tests/fonts/masters/basic2.png b/tests/fonts/masters/basic2.png new file mode 100644 index 0000000..2510d23 Binary files /dev/null and b/tests/fonts/masters/basic2.png differ diff --git a/tests/fonts/masters/basic3.png b/tests/fonts/masters/basic3.png new file mode 100644 index 0000000..db8492d Binary files /dev/null and b/tests/fonts/masters/basic3.png differ diff --git a/tests/fonts/masters/basic4.png b/tests/fonts/masters/basic4.png new file mode 100644 index 0000000..c1111f7 Binary files /dev/null and b/tests/fonts/masters/basic4.png differ diff --git a/tests/fonts/masters/basic5.png b/tests/fonts/masters/basic5.png new file mode 100644 index 0000000..0b3a14a Binary files /dev/null and b/tests/fonts/masters/basic5.png differ diff --git a/tests/fonts/masters/basic6.png b/tests/fonts/masters/basic6.png new file mode 100644 index 0000000..9337c98 Binary files /dev/null and b/tests/fonts/masters/basic6.png differ diff --git a/tests/fonts/masters/basic7.png b/tests/fonts/masters/basic7.png new file mode 100644 index 0000000..c652d44 Binary files /dev/null and b/tests/fonts/masters/basic7.png differ diff --git a/tests/fonts/masters/basic8.png b/tests/fonts/masters/basic8.png new file mode 100644 index 0000000..3823985 Binary files /dev/null and b/tests/fonts/masters/basic8.png differ diff --git a/tests/fonts/masters/basic9.png b/tests/fonts/masters/basic9.png new file mode 100644 index 0000000..eb8b15b Binary files /dev/null and b/tests/fonts/masters/basic9.png differ diff --git a/tests/fonts/masters/huge1.png b/tests/fonts/masters/huge1.png new file mode 100644 index 0000000..fb83009 Binary files /dev/null and b/tests/fonts/masters/huge1.png differ diff --git a/tests/fonts/masters/huge1_nokern.png b/tests/fonts/masters/huge1_nokern.png new file mode 100644 index 0000000..a6200f5 Binary files /dev/null and b/tests/fonts/masters/huge1_nokern.png differ diff --git a/tests/fonts/masters/huge2.png b/tests/fonts/masters/huge2.png new file mode 100644 index 0000000..5f5ae91 Binary files /dev/null and b/tests/fonts/masters/huge2.png differ diff --git a/tests/fonts/masters/huge2_nokern.png b/tests/fonts/masters/huge2_nokern.png new file mode 100644 index 0000000..aa15e3b Binary files /dev/null and b/tests/fonts/masters/huge2_nokern.png differ diff --git a/tests/fonts/masters/huge3.png b/tests/fonts/masters/huge3.png new file mode 100644 index 0000000..a646cae Binary files /dev/null and b/tests/fonts/masters/huge3.png differ diff --git a/tests/fonts/masters/huge3_nokern.png b/tests/fonts/masters/huge3_nokern.png new file mode 100644 index 0000000..3eb6d2a Binary files /dev/null and b/tests/fonts/masters/huge3_nokern.png differ diff --git a/tests/fonts/masters/lines1.png b/tests/fonts/masters/lines1.png new file mode 100644 index 0000000..1d1ac0d Binary files /dev/null and b/tests/fonts/masters/lines1.png differ diff --git a/tests/fonts/masters/lines2.png b/tests/fonts/masters/lines2.png new file mode 100644 index 0000000..0b1308c Binary files /dev/null and b/tests/fonts/masters/lines2.png differ diff --git a/tests/fonts/masters/pairs1.png b/tests/fonts/masters/pairs1.png new file mode 100644 index 0000000..c8bcde9 Binary files /dev/null and b/tests/fonts/masters/pairs1.png differ diff --git a/tests/fonts/masters/pairs2.png b/tests/fonts/masters/pairs2.png new file mode 100644 index 0000000..71bc2e5 Binary files /dev/null and b/tests/fonts/masters/pairs2.png differ diff --git a/tests/fonts/masters/pairs3.png b/tests/fonts/masters/pairs3.png new file mode 100644 index 0000000..680ba3f Binary files /dev/null and b/tests/fonts/masters/pairs3.png differ diff --git a/tests/fonts/masters/paragraph1.png b/tests/fonts/masters/paragraph1.png new file mode 100644 index 0000000..30db82d Binary files /dev/null and b/tests/fonts/masters/paragraph1.png differ diff --git a/tests/fonts/masters/paragraph1_2.png b/tests/fonts/masters/paragraph1_2.png new file mode 100644 index 0000000..2a789dc Binary files /dev/null and b/tests/fonts/masters/paragraph1_2.png differ diff --git a/tests/fonts/masters/paragraph1_3.png b/tests/fonts/masters/paragraph1_3.png new file mode 100644 index 0000000..7cecdcd Binary files /dev/null and b/tests/fonts/masters/paragraph1_3.png differ diff --git a/tests/fonts/masters/paragraph1_nokern.png b/tests/fonts/masters/paragraph1_nokern.png new file mode 100644 index 0000000..472f5ec Binary files /dev/null and b/tests/fonts/masters/paragraph1_nokern.png differ diff --git a/tests/fonts/masters/paragraph1_nokern_2.png b/tests/fonts/masters/paragraph1_nokern_2.png new file mode 100644 index 0000000..bf782ab Binary files /dev/null and b/tests/fonts/masters/paragraph1_nokern_2.png differ diff --git a/tests/fonts/masters/paragraph1_nokern_3.png b/tests/fonts/masters/paragraph1_nokern_3.png new file mode 100644 index 0000000..d9f5d3a Binary files /dev/null and b/tests/fonts/masters/paragraph1_nokern_3.png differ diff --git a/tests/fonts/masters/paragraph2.png b/tests/fonts/masters/paragraph2.png new file mode 100644 index 0000000..59d9583 Binary files /dev/null and b/tests/fonts/masters/paragraph2.png differ diff --git a/tests/fonts/masters/paragraph2_2.png b/tests/fonts/masters/paragraph2_2.png new file mode 100644 index 0000000..11e0f5c Binary files /dev/null and b/tests/fonts/masters/paragraph2_2.png differ diff --git a/tests/fonts/masters/paragraph2_3.png b/tests/fonts/masters/paragraph2_3.png new file mode 100644 index 0000000..92de359 Binary files /dev/null and b/tests/fonts/masters/paragraph2_3.png differ diff --git a/tests/fonts/masters/paragraph2_nokern.png b/tests/fonts/masters/paragraph2_nokern.png new file mode 100644 index 0000000..1f5583e Binary files /dev/null and b/tests/fonts/masters/paragraph2_nokern.png differ diff --git a/tests/fonts/masters/paragraph2_nokern_2.png b/tests/fonts/masters/paragraph2_nokern_2.png new file mode 100644 index 0000000..29fcf52 Binary files /dev/null and b/tests/fonts/masters/paragraph2_nokern_2.png differ diff --git a/tests/fonts/masters/paragraph2_nokern_3.png b/tests/fonts/masters/paragraph2_nokern_3.png new file mode 100644 index 0000000..aef7850 Binary files /dev/null and b/tests/fonts/masters/paragraph2_nokern_3.png differ diff --git a/tests/fonts/masters/paragraph3.png b/tests/fonts/masters/paragraph3.png new file mode 100644 index 0000000..12e8996 Binary files /dev/null and b/tests/fonts/masters/paragraph3.png differ diff --git a/tests/fonts/masters/paragraph3_2.png b/tests/fonts/masters/paragraph3_2.png new file mode 100644 index 0000000..6b1dcd1 Binary files /dev/null and b/tests/fonts/masters/paragraph3_2.png differ diff --git a/tests/fonts/masters/paragraph3_3.png b/tests/fonts/masters/paragraph3_3.png new file mode 100644 index 0000000..847962c Binary files /dev/null and b/tests/fonts/masters/paragraph3_3.png differ diff --git a/tests/fonts/masters/paragraph3_nokern.png b/tests/fonts/masters/paragraph3_nokern.png new file mode 100644 index 0000000..daabb7c Binary files /dev/null and b/tests/fonts/masters/paragraph3_nokern.png differ diff --git a/tests/fonts/masters/paragraph3_nokern_2.png b/tests/fonts/masters/paragraph3_nokern_2.png new file mode 100644 index 0000000..740df9f Binary files /dev/null and b/tests/fonts/masters/paragraph3_nokern_2.png differ diff --git a/tests/fonts/masters/paragraph3_nokern_3.png b/tests/fonts/masters/paragraph3_nokern_3.png new file mode 100644 index 0000000..ab5ee84 Binary files /dev/null and b/tests/fonts/masters/paragraph3_nokern_3.png differ diff --git a/tests/fonts/masters/paragraph4.png b/tests/fonts/masters/paragraph4.png new file mode 100644 index 0000000..a24f2a6 Binary files /dev/null and b/tests/fonts/masters/paragraph4.png differ diff --git a/tests/fonts/masters/paragraph4_2.png b/tests/fonts/masters/paragraph4_2.png new file mode 100644 index 0000000..aae6d57 Binary files /dev/null and b/tests/fonts/masters/paragraph4_2.png differ diff --git a/tests/fonts/masters/paragraph4_3.png b/tests/fonts/masters/paragraph4_3.png new file mode 100644 index 0000000..20b0af4 Binary files /dev/null and b/tests/fonts/masters/paragraph4_3.png differ diff --git a/tests/fonts/masters/paragraph4_nokern.png b/tests/fonts/masters/paragraph4_nokern.png new file mode 100644 index 0000000..05b41cb Binary files /dev/null and b/tests/fonts/masters/paragraph4_nokern.png differ diff --git a/tests/fonts/masters/paragraph4_nokern_2.png b/tests/fonts/masters/paragraph4_nokern_2.png new file mode 100644 index 0000000..e932025 Binary files /dev/null and b/tests/fonts/masters/paragraph4_nokern_2.png differ diff --git a/tests/fonts/masters/paragraph4_nokern_3.png b/tests/fonts/masters/paragraph4_nokern_3.png new file mode 100644 index 0000000..a7711b3 Binary files /dev/null and b/tests/fonts/masters/paragraph4_nokern_3.png differ diff --git a/tests/fonts/masters/paragraph5.png b/tests/fonts/masters/paragraph5.png new file mode 100644 index 0000000..ab683c6 Binary files /dev/null and b/tests/fonts/masters/paragraph5.png differ diff --git a/tests/fonts/masters/paragraph5_2.png b/tests/fonts/masters/paragraph5_2.png new file mode 100644 index 0000000..23a4470 Binary files /dev/null and b/tests/fonts/masters/paragraph5_2.png differ diff --git a/tests/fonts/masters/paragraph5_3.png b/tests/fonts/masters/paragraph5_3.png new file mode 100644 index 0000000..7192f1b Binary files /dev/null and b/tests/fonts/masters/paragraph5_3.png differ diff --git a/tests/fonts/masters/paragraph5_nokern.png b/tests/fonts/masters/paragraph5_nokern.png new file mode 100644 index 0000000..b353821 Binary files /dev/null and b/tests/fonts/masters/paragraph5_nokern.png differ diff --git a/tests/fonts/masters/paragraph5_nokern_2.png b/tests/fonts/masters/paragraph5_nokern_2.png new file mode 100644 index 0000000..83713ea Binary files /dev/null and b/tests/fonts/masters/paragraph5_nokern_2.png differ diff --git a/tests/fonts/masters/paragraph5_nokern_3.png b/tests/fonts/masters/paragraph5_nokern_3.png new file mode 100644 index 0000000..8b5d847 Binary files /dev/null and b/tests/fonts/masters/paragraph5_nokern_3.png differ diff --git a/tests/fonts/masters/selection_rects1.png b/tests/fonts/masters/selection_rects1.png new file mode 100644 index 0000000..a5fdfef Binary files /dev/null and b/tests/fonts/masters/selection_rects1.png differ diff --git a/tests/fonts/masters/selection_rects2.png b/tests/fonts/masters/selection_rects2.png new file mode 100644 index 0000000..7ff88fd Binary files /dev/null and b/tests/fonts/masters/selection_rects2.png differ diff --git a/tests/fonts/masters/selection_rects3.png b/tests/fonts/masters/selection_rects3.png new file mode 100644 index 0000000..ed80e33 Binary files /dev/null and b/tests/fonts/masters/selection_rects3.png differ diff --git a/tests/fonts/masters/spans1.png b/tests/fonts/masters/spans1.png new file mode 100644 index 0000000..659eb30 Binary files /dev/null and b/tests/fonts/masters/spans1.png differ diff --git a/tests/fonts/masters/spans2.png b/tests/fonts/masters/spans2.png new file mode 100644 index 0000000..d792d55 Binary files /dev/null and b/tests/fonts/masters/spans2.png differ diff --git a/tests/fonts/masters/spans4.png b/tests/fonts/masters/spans4.png new file mode 100644 index 0000000..763903d Binary files /dev/null and b/tests/fonts/masters/spans4.png differ diff --git a/tests/fonts/masters/spans5.png b/tests/fonts/masters/spans5.png new file mode 100644 index 0000000..615908b Binary files /dev/null and b/tests/fonts/masters/spans5.png differ diff --git a/tests/fonts/rendered/alignments.png b/tests/fonts/rendered/alignments.png new file mode 100644 index 0000000..fda3688 Binary files /dev/null and b/tests/fonts/rendered/alignments.png differ diff --git a/tests/fonts/rendered/basic1.png b/tests/fonts/rendered/basic1.png new file mode 100644 index 0000000..07ecae7 Binary files /dev/null and b/tests/fonts/rendered/basic1.png differ diff --git a/tests/fonts/rendered/basic2.png b/tests/fonts/rendered/basic2.png new file mode 100644 index 0000000..8b2ed35 Binary files /dev/null and b/tests/fonts/rendered/basic2.png differ diff --git a/tests/fonts/rendered/basic3.png b/tests/fonts/rendered/basic3.png new file mode 100644 index 0000000..b6729aa Binary files /dev/null and b/tests/fonts/rendered/basic3.png differ diff --git a/tests/fonts/rendered/basic4.png b/tests/fonts/rendered/basic4.png new file mode 100644 index 0000000..7409f47 Binary files /dev/null and b/tests/fonts/rendered/basic4.png differ diff --git a/tests/fonts/rendered/basic5.png b/tests/fonts/rendered/basic5.png new file mode 100644 index 0000000..2e048f5 Binary files /dev/null and b/tests/fonts/rendered/basic5.png differ diff --git a/tests/fonts/rendered/basic6.png b/tests/fonts/rendered/basic6.png new file mode 100644 index 0000000..abb7064 Binary files /dev/null and b/tests/fonts/rendered/basic6.png differ diff --git a/tests/fonts/rendered/basic7.png b/tests/fonts/rendered/basic7.png new file mode 100644 index 0000000..ddf03e2 Binary files /dev/null and b/tests/fonts/rendered/basic7.png differ diff --git a/tests/fonts/rendered/basic8.png b/tests/fonts/rendered/basic8.png new file mode 100644 index 0000000..5e66e06 Binary files /dev/null and b/tests/fonts/rendered/basic8.png differ diff --git a/tests/fonts/rendered/basic9.png b/tests/fonts/rendered/basic9.png new file mode 100644 index 0000000..4b747ff Binary files /dev/null and b/tests/fonts/rendered/basic9.png differ diff --git a/tests/fonts/rendered/huge1.png b/tests/fonts/rendered/huge1.png new file mode 100644 index 0000000..661ce69 Binary files /dev/null and b/tests/fonts/rendered/huge1.png differ diff --git a/tests/fonts/rendered/huge1_nokern.png b/tests/fonts/rendered/huge1_nokern.png new file mode 100644 index 0000000..3c9e80d Binary files /dev/null and b/tests/fonts/rendered/huge1_nokern.png differ diff --git a/tests/fonts/rendered/huge2.png b/tests/fonts/rendered/huge2.png new file mode 100644 index 0000000..cfa5ccd Binary files /dev/null and b/tests/fonts/rendered/huge2.png differ diff --git a/tests/fonts/rendered/huge2_nokern.png b/tests/fonts/rendered/huge2_nokern.png new file mode 100644 index 0000000..5753c16 Binary files /dev/null and b/tests/fonts/rendered/huge2_nokern.png differ diff --git a/tests/fonts/rendered/huge3.png b/tests/fonts/rendered/huge3.png new file mode 100644 index 0000000..2a7c8ca Binary files /dev/null and b/tests/fonts/rendered/huge3.png differ diff --git a/tests/fonts/rendered/huge3_nokern.png b/tests/fonts/rendered/huge3_nokern.png new file mode 100644 index 0000000..0df1b96 Binary files /dev/null and b/tests/fonts/rendered/huge3_nokern.png differ diff --git a/tests/fonts/rendered/lines1.png b/tests/fonts/rendered/lines1.png new file mode 100644 index 0000000..8332af3 Binary files /dev/null and b/tests/fonts/rendered/lines1.png differ diff --git a/tests/fonts/rendered/lines2.png b/tests/fonts/rendered/lines2.png new file mode 100644 index 0000000..5450444 Binary files /dev/null and b/tests/fonts/rendered/lines2.png differ diff --git a/tests/fonts/rendered/pairs1.png b/tests/fonts/rendered/pairs1.png new file mode 100644 index 0000000..6367f97 Binary files /dev/null and b/tests/fonts/rendered/pairs1.png differ diff --git a/tests/fonts/rendered/pairs2.png b/tests/fonts/rendered/pairs2.png new file mode 100644 index 0000000..0dfca9a Binary files /dev/null and b/tests/fonts/rendered/pairs2.png differ diff --git a/tests/fonts/rendered/pairs3.png b/tests/fonts/rendered/pairs3.png new file mode 100644 index 0000000..cf0493c Binary files /dev/null and b/tests/fonts/rendered/pairs3.png differ diff --git a/tests/fonts/rendered/paragraph1.png b/tests/fonts/rendered/paragraph1.png new file mode 100644 index 0000000..b51f2d5 Binary files /dev/null and b/tests/fonts/rendered/paragraph1.png differ diff --git a/tests/fonts/rendered/paragraph1_2.png b/tests/fonts/rendered/paragraph1_2.png new file mode 100644 index 0000000..359dd30 Binary files /dev/null and b/tests/fonts/rendered/paragraph1_2.png differ diff --git a/tests/fonts/rendered/paragraph1_3.png b/tests/fonts/rendered/paragraph1_3.png new file mode 100644 index 0000000..fabe147 Binary files /dev/null and b/tests/fonts/rendered/paragraph1_3.png differ diff --git a/tests/fonts/rendered/paragraph1_nokern.png b/tests/fonts/rendered/paragraph1_nokern.png new file mode 100644 index 0000000..1d22360 Binary files /dev/null and b/tests/fonts/rendered/paragraph1_nokern.png differ diff --git a/tests/fonts/rendered/paragraph1_nokern_2.png b/tests/fonts/rendered/paragraph1_nokern_2.png new file mode 100644 index 0000000..dc0e255 Binary files /dev/null and b/tests/fonts/rendered/paragraph1_nokern_2.png differ diff --git a/tests/fonts/rendered/paragraph1_nokern_3.png b/tests/fonts/rendered/paragraph1_nokern_3.png new file mode 100644 index 0000000..404c13d Binary files /dev/null and b/tests/fonts/rendered/paragraph1_nokern_3.png differ diff --git a/tests/fonts/rendered/paragraph2.png b/tests/fonts/rendered/paragraph2.png new file mode 100644 index 0000000..45ee6f3 Binary files /dev/null and b/tests/fonts/rendered/paragraph2.png differ diff --git a/tests/fonts/rendered/paragraph2_2.png b/tests/fonts/rendered/paragraph2_2.png new file mode 100644 index 0000000..253c396 Binary files /dev/null and b/tests/fonts/rendered/paragraph2_2.png differ diff --git a/tests/fonts/rendered/paragraph2_3.png b/tests/fonts/rendered/paragraph2_3.png new file mode 100644 index 0000000..9bdb327 Binary files /dev/null and b/tests/fonts/rendered/paragraph2_3.png differ diff --git a/tests/fonts/rendered/paragraph2_nokern.png b/tests/fonts/rendered/paragraph2_nokern.png new file mode 100644 index 0000000..d63cf94 Binary files /dev/null and b/tests/fonts/rendered/paragraph2_nokern.png differ diff --git a/tests/fonts/rendered/paragraph2_nokern_2.png b/tests/fonts/rendered/paragraph2_nokern_2.png new file mode 100644 index 0000000..992aa19 Binary files /dev/null and b/tests/fonts/rendered/paragraph2_nokern_2.png differ diff --git a/tests/fonts/rendered/paragraph2_nokern_3.png b/tests/fonts/rendered/paragraph2_nokern_3.png new file mode 100644 index 0000000..412f442 Binary files /dev/null and b/tests/fonts/rendered/paragraph2_nokern_3.png differ diff --git a/tests/fonts/rendered/paragraph3.png b/tests/fonts/rendered/paragraph3.png new file mode 100644 index 0000000..d4aa2a8 Binary files /dev/null and b/tests/fonts/rendered/paragraph3.png differ diff --git a/tests/fonts/rendered/paragraph3_2.png b/tests/fonts/rendered/paragraph3_2.png new file mode 100644 index 0000000..ff50720 Binary files /dev/null and b/tests/fonts/rendered/paragraph3_2.png differ diff --git a/tests/fonts/rendered/paragraph3_3.png b/tests/fonts/rendered/paragraph3_3.png new file mode 100644 index 0000000..6a622e6 Binary files /dev/null and b/tests/fonts/rendered/paragraph3_3.png differ diff --git a/tests/fonts/rendered/paragraph3_nokern.png b/tests/fonts/rendered/paragraph3_nokern.png new file mode 100644 index 0000000..aab3e48 Binary files /dev/null and b/tests/fonts/rendered/paragraph3_nokern.png differ diff --git a/tests/fonts/rendered/paragraph3_nokern_2.png b/tests/fonts/rendered/paragraph3_nokern_2.png new file mode 100644 index 0000000..5d380cb Binary files /dev/null and b/tests/fonts/rendered/paragraph3_nokern_2.png differ diff --git a/tests/fonts/rendered/paragraph3_nokern_3.png b/tests/fonts/rendered/paragraph3_nokern_3.png new file mode 100644 index 0000000..62255e2 Binary files /dev/null and b/tests/fonts/rendered/paragraph3_nokern_3.png differ diff --git a/tests/fonts/rendered/paragraph4.png b/tests/fonts/rendered/paragraph4.png new file mode 100644 index 0000000..e8b47d2 Binary files /dev/null and b/tests/fonts/rendered/paragraph4.png differ diff --git a/tests/fonts/rendered/paragraph4_2.png b/tests/fonts/rendered/paragraph4_2.png new file mode 100644 index 0000000..e6ab9c0 Binary files /dev/null and b/tests/fonts/rendered/paragraph4_2.png differ diff --git a/tests/fonts/rendered/paragraph4_3.png b/tests/fonts/rendered/paragraph4_3.png new file mode 100644 index 0000000..c439944 Binary files /dev/null and b/tests/fonts/rendered/paragraph4_3.png differ diff --git a/tests/fonts/rendered/paragraph4_nokern.png b/tests/fonts/rendered/paragraph4_nokern.png new file mode 100644 index 0000000..2524bca Binary files /dev/null and b/tests/fonts/rendered/paragraph4_nokern.png differ diff --git a/tests/fonts/rendered/paragraph4_nokern_2.png b/tests/fonts/rendered/paragraph4_nokern_2.png new file mode 100644 index 0000000..09ecd3f Binary files /dev/null and b/tests/fonts/rendered/paragraph4_nokern_2.png differ diff --git a/tests/fonts/rendered/paragraph4_nokern_3.png b/tests/fonts/rendered/paragraph4_nokern_3.png new file mode 100644 index 0000000..cceca7f Binary files /dev/null and b/tests/fonts/rendered/paragraph4_nokern_3.png differ diff --git a/tests/fonts/rendered/paragraph5.png b/tests/fonts/rendered/paragraph5.png new file mode 100644 index 0000000..637a59d Binary files /dev/null and b/tests/fonts/rendered/paragraph5.png differ diff --git a/tests/fonts/rendered/paragraph5_2.png b/tests/fonts/rendered/paragraph5_2.png new file mode 100644 index 0000000..60cb8fa Binary files /dev/null and b/tests/fonts/rendered/paragraph5_2.png differ diff --git a/tests/fonts/rendered/paragraph5_3.png b/tests/fonts/rendered/paragraph5_3.png new file mode 100644 index 0000000..f8a280c Binary files /dev/null and b/tests/fonts/rendered/paragraph5_3.png differ diff --git a/tests/fonts/rendered/paragraph5_nokern.png b/tests/fonts/rendered/paragraph5_nokern.png new file mode 100644 index 0000000..ec1d181 Binary files /dev/null and b/tests/fonts/rendered/paragraph5_nokern.png differ diff --git a/tests/fonts/rendered/paragraph5_nokern_2.png b/tests/fonts/rendered/paragraph5_nokern_2.png new file mode 100644 index 0000000..f9317e3 Binary files /dev/null and b/tests/fonts/rendered/paragraph5_nokern_2.png differ diff --git a/tests/fonts/rendered/paragraph5_nokern_3.png b/tests/fonts/rendered/paragraph5_nokern_3.png new file mode 100644 index 0000000..5967957 Binary files /dev/null and b/tests/fonts/rendered/paragraph5_nokern_3.png differ diff --git a/tests/fonts/rendered/selection_rects1.png b/tests/fonts/rendered/selection_rects1.png new file mode 100644 index 0000000..a5fdfef Binary files /dev/null and b/tests/fonts/rendered/selection_rects1.png differ diff --git a/tests/fonts/rendered/selection_rects2.png b/tests/fonts/rendered/selection_rects2.png new file mode 100644 index 0000000..7ff88fd Binary files /dev/null and b/tests/fonts/rendered/selection_rects2.png differ diff --git a/tests/fonts/rendered/selection_rects3.png b/tests/fonts/rendered/selection_rects3.png new file mode 100644 index 0000000..ed80e33 Binary files /dev/null and b/tests/fonts/rendered/selection_rects3.png differ diff --git a/tests/fonts/rendered/spans1.png b/tests/fonts/rendered/spans1.png new file mode 100644 index 0000000..5aff4e2 Binary files /dev/null and b/tests/fonts/rendered/spans1.png differ diff --git a/tests/fonts/rendered/spans2.png b/tests/fonts/rendered/spans2.png new file mode 100644 index 0000000..5060f36 Binary files /dev/null and b/tests/fonts/rendered/spans2.png differ diff --git a/tests/fonts/rendered/spans4.png b/tests/fonts/rendered/spans4.png new file mode 100644 index 0000000..0756b5f Binary files /dev/null and b/tests/fonts/rendered/spans4.png differ diff --git a/tests/fonts/rendered/spans5.png b/tests/fonts/rendered/spans5.png new file mode 100644 index 0000000..3c1ba92 Binary files /dev/null and b/tests/fonts/rendered/spans5.png differ diff --git a/tests/fonts/svg_changa.png b/tests/fonts/svg_changa.png new file mode 100644 index 0000000..4bfe9f4 Binary files /dev/null and b/tests/fonts/svg_changa.png differ diff --git a/tests/fonts/svg_dejavu.png b/tests/fonts/svg_dejavu.png new file mode 100644 index 0000000..22384d9 Binary files /dev/null and b/tests/fonts/svg_dejavu.png differ diff --git a/tests/fonts/svg_ibm.png b/tests/fonts/svg_ibm.png new file mode 100644 index 0000000..9ba89b6 Binary files /dev/null and b/tests/fonts/svg_ibm.png differ diff --git a/tests/fonts/svg_moon.png b/tests/fonts/svg_moon.png new file mode 100644 index 0000000..6b8dfd7 Binary files /dev/null and b/tests/fonts/svg_moon.png differ diff --git a/tests/fonts/svg_ubuntu.png b/tests/fonts/svg_ubuntu.png new file mode 100644 index 0000000..3509d71 Binary files /dev/null and b/tests/fonts/svg_ubuntu.png differ diff --git a/tests/fonts_megatest.nim b/tests/fonts_megatest.nim new file mode 100644 index 0000000..f76f15c --- /dev/null +++ b/tests/fonts_megatest.nim @@ -0,0 +1,12 @@ +import common, pixie + +# Clone https://github.com/treeform/fidgetfonts + +let fontPaths = findAllFonts("../fidgetfonts") + +for fontPath in fontPaths: + echo fontPath + try: + var font = readFont(fontPath) + except PixieError: + echo "ERROR: ", getCurrentExceptionMsg() diff --git a/tests/fuzz_opentype.nim b/tests/fuzz_opentype.nim new file mode 100644 index 0000000..c7d0401 --- /dev/null +++ b/tests/fuzz_opentype.nim @@ -0,0 +1,32 @@ +import common, pixie, random, strformat, unicode + +randomize() + +let fontPaths = findAllFonts("tests/fonts") + +doAssert fontPaths.len > 0 + +for i in 0 ..< 10000: + var + file = fontPaths[rand(fontPaths.len - 1)] + data = readFile(file) + pos = rand(data.len) + value = rand(255).char + data[pos] = value + echo &"{i} {file} {pos} {value.uint8}" + try: + let font = parseOtf(data) + doAssert font != nil + for i in 0.uint16 ..< uint16.high: + discard font.getGlyphPath(Rune(i.int)) + except PixieError: + discard + + data = data[0 ..< pos] + try: + let font = parseOtf(data) + doAssert font != nil + for i in 0.uint16 ..< uint16.high: + discard font.getGlyphPath(Rune(i.int)) + except PixieError: + discard diff --git a/tests/megatest.nim b/tests/icons_megatest.nim similarity index 100% rename from tests/megatest.nim rename to tests/icons_megatest.nim diff --git a/tests/images/drawEllipse.png b/tests/images/drawEllipse.png index a816ac3..f245ddf 100644 Binary files a/tests/images/drawEllipse.png and b/tests/images/drawEllipse.png differ diff --git a/tests/images/drawPolygon.png b/tests/images/drawPolygon.png index 7f2ba6c..4f50858 100644 Binary files a/tests/images/drawPolygon.png and b/tests/images/drawPolygon.png differ diff --git a/tests/images/drawRoundedRect.png b/tests/images/drawRoundedRect.png index c3050ad..2c6f3f6 100644 Binary files a/tests/images/drawRoundedRect.png and b/tests/images/drawRoundedRect.png differ diff --git a/tests/images/drawSegment.png b/tests/images/drawSegment.png index a2a446b..7ab2592 100644 Binary files a/tests/images/drawSegment.png and b/tests/images/drawSegment.png differ diff --git a/tests/images/flipped1.png b/tests/images/flipped1.png index 2fa1421..74a2974 100644 Binary files a/tests/images/flipped1.png and b/tests/images/flipped1.png differ diff --git a/tests/images/flipped2.png b/tests/images/flipped2.png index 442446e..b4a0af9 100644 Binary files a/tests/images/flipped2.png and b/tests/images/flipped2.png differ diff --git a/tests/images/flipped3.png b/tests/images/flipped3.png index 344d923..e3e4a4f 100644 Binary files a/tests/images/flipped3.png and b/tests/images/flipped3.png differ diff --git a/tests/images/gif/audrey.png b/tests/images/gif/audrey.png index a2aace5..7498f59 100644 Binary files a/tests/images/gif/audrey.png and b/tests/images/gif/audrey.png differ diff --git a/tests/images/gif/sunflower.png b/tests/images/gif/sunflower.png index e9b8c9b..cdaaa16 100644 Binary files a/tests/images/gif/sunflower.png and b/tests/images/gif/sunflower.png differ diff --git a/tests/images/imageblur20.png b/tests/images/imageblur20.png index 9704d36..978c3cd 100644 Binary files a/tests/images/imageblur20.png and b/tests/images/imageblur20.png differ diff --git a/tests/images/imageblur20oob.png b/tests/images/imageblur20oob.png index d283fc5..1934f31 100644 Binary files a/tests/images/imageblur20oob.png and b/tests/images/imageblur20oob.png differ diff --git a/tests/images/maskblur20.png b/tests/images/maskblur20.png index bab2143..3b76a26 100644 Binary files a/tests/images/maskblur20.png and b/tests/images/maskblur20.png differ diff --git a/tests/images/masks/circleMask.png b/tests/images/masks/circleMask.png index 89de309..ca9744c 100644 Binary files a/tests/images/masks/circleMask.png and b/tests/images/masks/circleMask.png differ diff --git a/tests/images/masks/circleMaskSharpened.png b/tests/images/masks/circleMaskSharpened.png index 88dc9b9..2746046 100644 Binary files a/tests/images/masks/circleMaskSharpened.png and b/tests/images/masks/circleMaskSharpened.png differ diff --git a/tests/images/masks/drawEllipse.png b/tests/images/masks/drawEllipse.png index 1bd4af3..f219dc5 100644 Binary files a/tests/images/masks/drawEllipse.png and b/tests/images/masks/drawEllipse.png differ diff --git a/tests/images/masks/drawPolygon.png b/tests/images/masks/drawPolygon.png index 089d0d3..b17c742 100644 Binary files a/tests/images/masks/drawPolygon.png and b/tests/images/masks/drawPolygon.png differ diff --git a/tests/images/masks/drawRect.png b/tests/images/masks/drawRect.png index 9afe3cc..c8039a7 100644 Binary files a/tests/images/masks/drawRect.png and b/tests/images/masks/drawRect.png differ diff --git a/tests/images/masks/drawRoundedRect.png b/tests/images/masks/drawRoundedRect.png index 13bb852..993dc2d 100644 Binary files a/tests/images/masks/drawRoundedRect.png and b/tests/images/masks/drawRoundedRect.png differ diff --git a/tests/images/masks/drawSegment.png b/tests/images/masks/drawSegment.png index fc209a6..ec89fbe 100644 Binary files a/tests/images/masks/drawSegment.png and b/tests/images/masks/drawSegment.png differ diff --git a/tests/images/masks/imageMaskedMask.png b/tests/images/masks/imageMaskedMask.png index eb13fae..e4a360f 100644 Binary files a/tests/images/masks/imageMaskedMask.png and b/tests/images/masks/imageMaskedMask.png differ diff --git a/tests/images/masks/maskMinified.png b/tests/images/masks/maskMinified.png index a6d9d58..7db6efe 100644 Binary files a/tests/images/masks/maskMinified.png and b/tests/images/masks/maskMinified.png differ diff --git a/tests/images/masks/maskedMask.png b/tests/images/masks/maskedMask.png index eb13fae..e4a360f 100644 Binary files a/tests/images/masks/maskedMask.png and b/tests/images/masks/maskedMask.png differ diff --git a/tests/images/masks/shifted.png b/tests/images/masks/shifted.png index 2beb2a4..7093b03 100644 Binary files a/tests/images/masks/shifted.png and b/tests/images/masks/shifted.png differ diff --git a/tests/images/masks/spread.png b/tests/images/masks/spread.png index 96bfceb..81b4ca4 100644 Binary files a/tests/images/masks/spread.png and b/tests/images/masks/spread.png differ diff --git a/tests/images/masks/strokeEllipse.png b/tests/images/masks/strokeEllipse.png index af1c50c..e0d06da 100644 Binary files a/tests/images/masks/strokeEllipse.png and b/tests/images/masks/strokeEllipse.png differ diff --git a/tests/images/masks/strokePolygon.png b/tests/images/masks/strokePolygon.png index 4d88470..bb78208 100644 Binary files a/tests/images/masks/strokePolygon.png and b/tests/images/masks/strokePolygon.png differ diff --git a/tests/images/masks/strokeRect.png b/tests/images/masks/strokeRect.png index b663c6e..23a86df 100644 Binary files a/tests/images/masks/strokeRect.png and b/tests/images/masks/strokeRect.png differ diff --git a/tests/images/masks/strokeRoundedRect.png b/tests/images/masks/strokeRoundedRect.png index 7f7a31c..09cf8fb 100644 Binary files a/tests/images/masks/strokeRoundedRect.png and b/tests/images/masks/strokeRoundedRect.png differ diff --git a/tests/images/minifiedBy2.png b/tests/images/minifiedBy2.png index 854cdfc..2099e3c 100644 Binary files a/tests/images/minifiedBy2.png and b/tests/images/minifiedBy2.png differ diff --git a/tests/images/minifiedBy4.png b/tests/images/minifiedBy4.png index 6e5769b..4d51d16 100644 Binary files a/tests/images/minifiedBy4.png and b/tests/images/minifiedBy4.png differ diff --git a/tests/images/paths/boxBevel.png b/tests/images/paths/boxBevel.png index 814a7e1..7551e66 100644 Binary files a/tests/images/paths/boxBevel.png and b/tests/images/paths/boxBevel.png differ diff --git a/tests/images/paths/boxMiter.png b/tests/images/paths/boxMiter.png index 69b02a3..4e92425 100644 Binary files a/tests/images/paths/boxMiter.png and b/tests/images/paths/boxMiter.png differ diff --git a/tests/images/paths/boxRound.png b/tests/images/paths/boxRound.png index 53d0e9a..c3f8ee1 100644 Binary files a/tests/images/paths/boxRound.png and b/tests/images/paths/boxRound.png differ diff --git a/tests/images/paths/gradientAngular.png b/tests/images/paths/gradientAngular.png index 260e7d5..1835cfc 100644 Binary files a/tests/images/paths/gradientAngular.png and b/tests/images/paths/gradientAngular.png differ diff --git a/tests/images/paths/gradientLinear.png b/tests/images/paths/gradientLinear.png index e1a15fc..c6145ec 100644 Binary files a/tests/images/paths/gradientLinear.png and b/tests/images/paths/gradientLinear.png differ diff --git a/tests/images/paths/gradientRadial.png b/tests/images/paths/gradientRadial.png index cb2c7ee..84fe26e 100644 Binary files a/tests/images/paths/gradientRadial.png and b/tests/images/paths/gradientRadial.png differ diff --git a/tests/images/paths/lcButt.png b/tests/images/paths/lcButt.png index f7997bc..c02645f 100644 Binary files a/tests/images/paths/lcButt.png and b/tests/images/paths/lcButt.png differ diff --git a/tests/images/paths/lcRound.png b/tests/images/paths/lcRound.png index f926b4d..5982eb2 100644 Binary files a/tests/images/paths/lcRound.png and b/tests/images/paths/lcRound.png differ diff --git a/tests/images/paths/lcSquare.png b/tests/images/paths/lcSquare.png index 5f2a5d8..c5848c7 100644 Binary files a/tests/images/paths/lcSquare.png and b/tests/images/paths/lcSquare.png differ diff --git a/tests/images/paths/paintImage.png b/tests/images/paths/paintImage.png index dc0db98..c8be7ff 100644 Binary files a/tests/images/paths/paintImage.png and b/tests/images/paths/paintImage.png differ diff --git a/tests/images/paths/paintImageTiled.png b/tests/images/paths/paintImageTiled.png index 9cac35d..47996bf 100644 Binary files a/tests/images/paths/paintImageTiled.png and b/tests/images/paths/paintImageTiled.png differ diff --git a/tests/images/paths/paintSolid.png b/tests/images/paths/paintSolid.png index 2e1e02d..d6a7820 100644 Binary files a/tests/images/paths/paintSolid.png and b/tests/images/paths/paintSolid.png differ diff --git a/tests/images/paths/pathBlackRectangle.png b/tests/images/paths/pathBlackRectangle.png index fb9d80e..5e05967 100644 Binary files a/tests/images/paths/pathBlackRectangle.png and b/tests/images/paths/pathBlackRectangle.png differ diff --git a/tests/images/paths/pathBlackRectangleZ.png b/tests/images/paths/pathBlackRectangleZ.png index fb9d80e..5e05967 100644 Binary files a/tests/images/paths/pathBlackRectangleZ.png and b/tests/images/paths/pathBlackRectangleZ.png differ diff --git a/tests/images/paths/pathBottomArc.png b/tests/images/paths/pathBottomArc.png index 8894c59..c00777c 100644 Binary files a/tests/images/paths/pathBottomArc.png and b/tests/images/paths/pathBottomArc.png differ diff --git a/tests/images/paths/pathCornerArc.png b/tests/images/paths/pathCornerArc.png index 690cc82..84a6a27 100644 Binary files a/tests/images/paths/pathCornerArc.png and b/tests/images/paths/pathCornerArc.png differ diff --git a/tests/images/paths/pathHeart.png b/tests/images/paths/pathHeart.png index 560cd66..b37ba3c 100644 Binary files a/tests/images/paths/pathHeart.png and b/tests/images/paths/pathHeart.png differ diff --git a/tests/images/paths/pathInvertedCornerArc.png b/tests/images/paths/pathInvertedCornerArc.png index 9990332..f39e9a4 100644 Binary files a/tests/images/paths/pathInvertedCornerArc.png and b/tests/images/paths/pathInvertedCornerArc.png differ diff --git a/tests/images/paths/pathRectangleMask.png b/tests/images/paths/pathRectangleMask.png index 0df19b1..8b595a5 100644 Binary files a/tests/images/paths/pathRectangleMask.png and b/tests/images/paths/pathRectangleMask.png differ diff --git a/tests/images/paths/pathRedRectangle.png b/tests/images/paths/pathRedRectangle.png index d649f68..885c1b3 100644 Binary files a/tests/images/paths/pathRedRectangle.png and b/tests/images/paths/pathRedRectangle.png differ diff --git a/tests/images/paths/pathRotatedArc.png b/tests/images/paths/pathRotatedArc.png index 4e3886a..617dd0c 100644 Binary files a/tests/images/paths/pathRotatedArc.png and b/tests/images/paths/pathRotatedArc.png differ diff --git a/tests/images/paths/pathRoundRect.png b/tests/images/paths/pathRoundRect.png index 5506d6b..7a94dd9 100644 Binary files a/tests/images/paths/pathRoundRect.png and b/tests/images/paths/pathRoundRect.png differ diff --git a/tests/images/paths/pathRoundRectMask.png b/tests/images/paths/pathRoundRectMask.png index 1205ccc..91f1833 100644 Binary files a/tests/images/paths/pathRoundRectMask.png and b/tests/images/paths/pathRoundRectMask.png differ diff --git a/tests/images/paths/pathStroke1.png b/tests/images/paths/pathStroke1.png index b738549..3245ce0 100644 Binary files a/tests/images/paths/pathStroke1.png and b/tests/images/paths/pathStroke1.png differ diff --git a/tests/images/paths/pathStroke2.png b/tests/images/paths/pathStroke2.png index 832c16e..a41899d 100644 Binary files a/tests/images/paths/pathStroke2.png and b/tests/images/paths/pathStroke2.png differ diff --git a/tests/images/paths/pathStroke3.png b/tests/images/paths/pathStroke3.png index 428b9fa..ffee6ee 100644 Binary files a/tests/images/paths/pathStroke3.png and b/tests/images/paths/pathStroke3.png differ diff --git a/tests/images/paths/pathYellowRectangle.png b/tests/images/paths/pathYellowRectangle.png index c1b1b2b..3b75c7b 100644 Binary files a/tests/images/paths/pathYellowRectangle.png and b/tests/images/paths/pathYellowRectangle.png differ diff --git a/tests/images/paths/pixelScale.png b/tests/images/paths/pixelScale.png index 0abcddc..41e8907 100644 Binary files a/tests/images/paths/pixelScale.png and b/tests/images/paths/pixelScale.png differ diff --git a/tests/images/png/trailing_data.png b/tests/images/png/trailing_data.png new file mode 100644 index 0000000..44ee3c1 Binary files /dev/null and b/tests/images/png/trailing_data.png differ diff --git a/tests/images/rotate0.png b/tests/images/rotate0.png index 7982907..2a0ecb0 100644 Binary files a/tests/images/rotate0.png and b/tests/images/rotate0.png differ diff --git a/tests/images/rotate180.png b/tests/images/rotate180.png index bfda507..a0114ee 100644 Binary files a/tests/images/rotate180.png and b/tests/images/rotate180.png differ diff --git a/tests/images/rotate270.png b/tests/images/rotate270.png index 8cd7328..58e1f37 100644 Binary files a/tests/images/rotate270.png and b/tests/images/rotate270.png differ diff --git a/tests/images/rotate360.png b/tests/images/rotate360.png index 8f9fa85..d9f0b84 100644 Binary files a/tests/images/rotate360.png and b/tests/images/rotate360.png differ diff --git a/tests/images/rotate90.png b/tests/images/rotate90.png index 4e40c7b..7f666d4 100644 Binary files a/tests/images/rotate90.png and b/tests/images/rotate90.png differ diff --git a/tests/images/strokeEllipse.png b/tests/images/strokeEllipse.png index 7c95036..9ab565c 100644 Binary files a/tests/images/strokeEllipse.png and b/tests/images/strokeEllipse.png differ diff --git a/tests/images/strokePolygon.png b/tests/images/strokePolygon.png index e124ee3..85d18e1 100644 Binary files a/tests/images/strokePolygon.png and b/tests/images/strokePolygon.png differ diff --git a/tests/images/strokeRect.png b/tests/images/strokeRect.png index fb80260..b0549e1 100644 Binary files a/tests/images/strokeRect.png and b/tests/images/strokeRect.png differ diff --git a/tests/images/superimage3.png b/tests/images/superimage3.png index 1e7232f..b68dee8 100644 Binary files a/tests/images/superimage3.png and b/tests/images/superimage3.png differ diff --git a/tests/images/superimage4.png b/tests/images/superimage4.png index 1b58d46..66a30e5 100644 Binary files a/tests/images/superimage4.png and b/tests/images/superimage4.png differ diff --git a/tests/images/superimage5.png b/tests/images/superimage5.png index 4ed0c8e..6ac6283 100644 Binary files a/tests/images/superimage5.png and b/tests/images/superimage5.png differ diff --git a/tests/images/svg/Ghostscript_Tiger.png b/tests/images/svg/Ghostscript_Tiger.png index 07b4959..e82f56a 100644 Binary files a/tests/images/svg/Ghostscript_Tiger.png and b/tests/images/svg/Ghostscript_Tiger.png differ diff --git a/tests/images/svg/circle01.png b/tests/images/svg/circle01.png index fac810b..db14d25 100644 Binary files a/tests/images/svg/circle01.png and b/tests/images/svg/circle01.png differ diff --git a/tests/images/svg/ellipse01.png b/tests/images/svg/ellipse01.png index 23d454c..26a1061 100644 Binary files a/tests/images/svg/ellipse01.png and b/tests/images/svg/ellipse01.png differ diff --git a/tests/images/svg/ionicons.png b/tests/images/svg/ionicons.png index b9209f2..2b2838a 100644 Binary files a/tests/images/svg/ionicons.png and b/tests/images/svg/ionicons.png differ diff --git a/tests/images/svg/line01.png b/tests/images/svg/line01.png index f00b96f..ac93d43 100644 Binary files a/tests/images/svg/line01.png and b/tests/images/svg/line01.png differ diff --git a/tests/images/svg/polygon01.png b/tests/images/svg/polygon01.png index c7fe070..e1a0209 100644 Binary files a/tests/images/svg/polygon01.png and b/tests/images/svg/polygon01.png differ diff --git a/tests/images/svg/polyline01.png b/tests/images/svg/polyline01.png index 77c3e57..8861b01 100644 Binary files a/tests/images/svg/polyline01.png and b/tests/images/svg/polyline01.png differ diff --git a/tests/images/svg/quad01.png b/tests/images/svg/quad01.png index 9722cce..da0526b 100644 Binary files a/tests/images/svg/quad01.png and b/tests/images/svg/quad01.png differ diff --git a/tests/images/svg/rect01.png b/tests/images/svg/rect01.png index 250f7ef..b47a8d0 100644 Binary files a/tests/images/svg/rect01.png and b/tests/images/svg/rect01.png differ diff --git a/tests/images/svg/rect02.png b/tests/images/svg/rect02.png index 90f9702..85e098a 100644 Binary files a/tests/images/svg/rect02.png and b/tests/images/svg/rect02.png differ diff --git a/tests/images/svg/simple-icons.png b/tests/images/svg/simple-icons.png index 71d5386..4b78ac7 100644 Binary files a/tests/images/svg/simple-icons.png and b/tests/images/svg/simple-icons.png differ diff --git a/tests/images/svg/tabler-icons.png b/tests/images/svg/tabler-icons.png index e1f982e..9c838ee 100644 Binary files a/tests/images/svg/tabler-icons.png and b/tests/images/svg/tabler-icons.png differ diff --git a/tests/images/svg/triangle01.png b/tests/images/svg/triangle01.png index 994bc97..954d28a 100644 Binary files a/tests/images/svg/triangle01.png and b/tests/images/svg/triangle01.png differ diff --git a/tests/images/svg/twbs-icons.png b/tests/images/svg/twbs-icons.png index 5f5ac82..8921a4d 100644 Binary files a/tests/images/svg/twbs-icons.png and b/tests/images/svg/twbs-icons.png differ diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim new file mode 100644 index 0000000..4009eb6 --- /dev/null +++ b/tests/test_fonts.nim @@ -0,0 +1,805 @@ +import pixie, pixie/fileformats/png, strformat + +proc doDiff(rendered: Image, name: string) = + rendered.writeFile(&"tests/fonts/rendered/{name}.png") + let + master = readImage(&"tests/fonts/masters/{name}.png") + (diffScore, diffImage) = diff(master, rendered) + echo &"{name} score: {diffScore}" + diffImage.writeFile(&"tests/fonts/diffs/{name}.png") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + + let bounds = font.computeBounds("Word") + doAssert bounds == vec2(57, 28) + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 64 + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "fill") + image.writeFile("tests/fonts/image_fill.png") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 64 + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.strokeText(font, "stroke") + image.writeFile("tests/fonts/image_stroke.png") + +block: + 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_1.ttf") + font.size = 64 + let mask = newMask(200, 100) + mask.strokeText(font, "stroke") + writeFile("tests/fonts/mask_stroke.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/Changa-Bold.svg") + font.size = 48 + let mask = newMask(200, 100) + mask.fillText(font, "Changa") + writeFile("tests/fonts/svg_changa.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/DejaVuSans.svg") + font.size = 48 + let mask = newMask(200, 100) + mask.fillText(font, "Deja vu ") + writeFile("tests/fonts/svg_dejavu.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/IBMPlexSans-Regular.svg") + font.size = 48 + let mask = newMask(200, 100) + mask.fillText(font, "IBM ") + writeFile("tests/fonts/svg_ibm.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/Moon-Bold.svg") + font.size = 48 + let mask = newMask(200, 100) + mask.fillText(font, "Moon ") + writeFile("tests/fonts/svg_moon.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/Ubuntu.svg") + font.size = 48 + let mask = newMask(200, 100) + mask.fillText(font, "Ubuntu ") + writeFile("tests/fonts/svg_ubuntu.png", mask.encodePng()) + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 72 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "asdf") + + doDiff(image, "basic1") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 72 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "A cow") + + doDiff(image, "basic2") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "A bit of text HERE") + + doDiff(image, "basic3") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + font.lineHeight = 100 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "Line height") + + doDiff(image, "basic4") + +block: + var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "Another font") + + doDiff(image, "basic5") + +block: + var font = readFont("tests/fonts/Aclonica-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "Different font") + + doDiff(image, "basic6") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "First line") + image.fillText(font, "Second line", vec2(0, font.defaultLineHeight)) + + doDiff(image, "basic7") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + "Wrapping text to new line", + bounds = vec2(200, 0) + ) + + doDiff(image, "basic8") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 24 + + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + "Supercalifragilisticexpialidocious", + bounds = vec2(200, 0) + ) + + doDiff(image, "basic9") + +const + paragraph = "ShehadcometotheconclusionthatyoucouldtellalotaboutapersonbytheirearsThewaytheystuckoutandthesizeoftheearlobescouldgiveyou" + paragraph_2 = "She had come to the conclusion that you could tell a lot about a person by their ears The way they stuck out and the size of the earlobes could give you wonderful insights into the person Of course she couldnt scientifically prove any of this but that didnt matter to her Before anything else she would size up the ears of the person she was talking to Shes asked the question so many times that she barely listened to the answers anymore The answers were always the same Well not exactly the same but the same in a general sense A more accurate description was the answers never surprised her" + paragraph_3 = "She had come to the conclusion that you could tell a lot about a person by their ears The way they stuck out and the size of the earlobes could give you wonderful insights into the person. Of course, she couldn't scientifically prove any of this, but that didn't matter to her. Before anything else, she would size up the ears of the person she was talking to. She's asked the question so many times that she barely listened to the answers anymore. The answers were always the same. Well, not exactly the same, but the same in a general sense. A more accurate description was the answers never surprised her." + paragraphs = [paragraph, paragraph_2, paragraph_3] + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 16 + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph1_{i + 1}" else: "paragraph1" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 16 + font.noKerningAdjustments = true + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph1_nokern_{i + 1}" else: "paragraph1_nokern" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font.size = 16 + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph2_{i + 1}" else: "paragraph2" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font.size = 16 + font.noKerningAdjustments = true + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph2_nokern_{i + 1}" else: "paragraph2_nokern" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf") + font.size = 16 + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph3_{i + 1}" else: "paragraph3" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf") + font.size = 16 + font.noKerningAdjustments = true + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph3_nokern_{i + 1}" else: "paragraph3_nokern" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/NotoSans-Regular_4.ttf") + font.size = 16 + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph4_{i + 1}" else: "paragraph4" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/NotoSans-Regular_4.ttf") + font.size = 16 + font.noKerningAdjustments = true + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph4_nokern_{i + 1}" else: "paragraph4_nokern" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/Pacifico-Regular_4.ttf") + font.size = 16 + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + let name = if i > 0: &"paragraph5_{i + 1}" else: "paragraph5" + doDiff(image, name) + +block: + var font = readFont("tests/fonts/Pacifico-Regular_4.ttf") + font.size = 16 + font.noKerningAdjustments = true + + let image = newImage(1000, 150) + + for i, text in paragraphs: + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + text, + bounds = image.wh + ) + + 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", + bounds = image.wh + ) + + doDiff(image, "huge1") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 200 + font.noKerningAdjustments = true + + let image = newImage(2800, 400) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + "Shehadcometotheconclusion", + bounds = image.wh + ) + + 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", + bounds = image.wh + ) + + doDiff(image, "huge2") + +block: + var font = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font.size = 200 + font.noKerningAdjustments = true + + let image = newImage(2800, 400) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + "Shehadcometotheconclusion", + bounds = image.wh + ) + + 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", + bounds = image.wh + ) + + doDiff(image, "huge3") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 200 + font.noKerningAdjustments = true + + let image = newImage(2800, 400) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + "Wrapping text to the next line", + bounds = image.wh + ) + + 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", + 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{", + 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", + bounds = image.wh + ) + + doDiff(image, "pairs3") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 18 + + let image = newImage(200, 150) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + """First line +Second line +Third line +Fourth line +Fifth line +Sixth line +Seventh line""", + bounds = image.wh + ) + + doDiff(image, "lines1") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 18 + font.lineHeight = 30 + + let image = newImage(200, 150) + image.fill(rgba(255, 255, 255, 255)) + image.fillText( + font, + """First line +Second line +Third line +Fourth line +Fifth line""", + bounds = image.wh + ) + + doDiff(image, "lines2") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 36 + + let image = newImage(800, 800) + image.fill(rgba(255, 255, 255, 255)) + + image.fillText( + font, + "TopLeft", + bounds = image.wh, + hAlign = haLeft, + vAlign = vaTop + ) + + image.fillText( + font, + "TopCenter", + bounds = image.wh, + hAlign = haCenter, + vAlign = vaTop + ) + + image.fillText( + font, + "TopRight", + bounds = image.wh, + hAlign = haRight, + vAlign = vaTop + ) + + image.fillText( + font, + "MiddleLeft", + bounds = image.wh, + hAlign = haLeft, + vAlign = vaMiddle + ) + + image.fillText( + font, + "MiddleCenter", + bounds = image.wh, + hAlign = haCenter, + vAlign = vaMiddle + ) + + image.fillText( + font, + "MiddleRight", + bounds = image.wh, + hAlign = haRight, + vAlign = vaMiddle + ) + + image.fillText( + font, + "BottomLeft", + bounds = image.wh, + hAlign = haLeft, + vAlign = vaBottom + ) + + image.fillText( + font, + "BottomCenter", + bounds = image.wh, + hAlign = haCenter, + vAlign = vaBottom + ) + + image.fillText( + font, + "BottomRight", + bounds = image.wh, + hAlign = haRight, + vAlign = vaBottom + ) + + doDiff(image, "alignments") + +block: + var font = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf") + font.size = 48 + font.paint = Paint( + kind: pkGradientLinear, + gradientHandlePositions: @[ + vec2(0, 50), + vec2(100, 50), + ], + gradientStops: @[ + ColorStop(color: rgba(255, 0, 0, 255), position: 0), + ColorStop(color: rgba(255, 0, 0, 40), position: 1.0), + ] + ) + + let image = newImage(100, 100) + image.fillText(font, "Text") + + image.writeFile("tests/fonts/image_paint_fill.png") + +block: + var font1 = readFont("tests/fonts/Roboto-Regular_1.ttf") + font1.size = 80 + + var font2 = readFont("tests/fonts/Aclonica-Regular_1.ttf") + font2.size = 100 + + var font3 = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font3.size = 48 + + let spans = @[ + newSpan("One span ", font1), + newSpan("Two span", font2), + newSpan(" Three span", font3) + ] + + let image = newImage(700, 250) + image.fill(rgba(255, 255, 255, 255)) + + let arrangement = typeset(spans, bounds = image.wh) + + image.fillText(arrangement) + + doDiff(image, "spans1") + + for i, rect in arrangement.selectionRects: + image.fillRect(rect, rgba(128, 128, 128, 128)) + + doDiff(image, "selection_rects1") + +block: + var font1 = readFont("tests/fonts/Roboto-Regular_1.ttf") + font1.size = 80 + + var font2 = readFont("tests/fonts/Aclonica-Regular_1.ttf") + font2.size = 100 + + var font3 = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + font3.size = 48 + + let spans = @[ + newSpan("One span ", font1), + newSpan("Two span", font2), + newSpan(" Three span", font3) + ] + + let image = newImage(475, 400) + image.fill(rgba(255, 255, 255, 255)) + + let arrangement = typeset(spans, bounds = image.wh) + + image.fillText(arrangement) + + doDiff(image, "spans2") + + for i, rect in arrangement.selectionRects: + image.fillRect(rect, rgba(128, 128, 128, 128)) + + doDiff(image, "selection_rects2") + +block: + var font = readFont("tests/fonts/Roboto-Regular_1.ttf") + font.size = 16 + + let image = newImage(75, 75) + image.fill(rgba(255, 255, 255, 255)) + + let arrangement = typeset( + font, "Wrapping text to new line", bounds = image.wh + ) + + image.fillText(arrangement) + + for i, rect in arrangement.selectionRects: + image.fillRect(rect, rgba(128, 128, 128, 128)) + + doDiff(image, "selection_rects3") + +block: + let + roboto = readFont("tests/fonts/Roboto-Regular_1.ttf") + aclonica = readFont("tests/fonts/Aclonica-Regular_1.ttf") + ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + ibm = readFont("tests/fonts/IBMPlexSans-Regular_2.ttf") + noto = readFont("tests/fonts/NotoSans-Regular_4.ttf") + + var font1 = roboto + font1.size = 64 + + var font2 = aclonica + font2.size = 80 + + var font3 = ibm + font3.size = 40 + + var font4 = ubuntu + font4.size = 56 + + var font5 = noto + font5.size = 72 + + var font6 = roboto + font6.size = 48 + + var font7 = noto + font7.size = 64 + + var font8 = ubuntu + font8.size = 54 + font8.paint.color = rgba(255, 0, 0, 255) + + var font9 = roboto + font9.size = 48 + + var font10 = aclonica + font10.size = 48 + font10.lineHeight = 120 + + let spans = @[ + newSpan("Using spans, ", font1), + newSpan("Pixie ", font2), + newSpan("can arrange and rasterize ", font3), + newSpan("very complex text layouts. ", font4), + newSpan("Spans", font5), + newSpan(" can have different ", font6), + newSpan("font sizes,", font7), + newSpan(" colors", font8), + newSpan(" and ", font9), + newSpan("line heights.", font10) + ] + + let image = newImage(600, 600) + image.fill(rgba(255, 255, 255, 255)) + + let arrangement = typeset(spans, bounds = image.wh) + + image.fillText(arrangement) + + doDiff(image, "spans4") + +block: + let ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf") + + var font1 = ubuntu + font1.size = 15 + font1.paint.color = parseHtmlColor("#CACACA").rgba() + + var font2 = ubuntu + font2.size = 84 + + var font3 = ubuntu + font3.size = 18 + font3.paint.color = parseHtmlColor("#007FF4").rgba() + + var font4 = ubuntu + font4.size = 20 + font4.paint.color = parseHtmlColor("#4F4F4F").rgba() + + let spans = @[ + newSpan("verb [with object] ", font1), + newSpan("strallow\n", font2), + newSpan("\nstral·low\n", font3), + newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ", font4) + ] + + let image = newImage(400, 400) + image.fill(rgba(255, 255, 255, 255)) + + let arrangement = typeset(spans, bounds = vec2(360, 360)) + + image.fillText(arrangement, vec2(20, 20)) + + doDiff(image, "spans5") diff --git a/tests/test_paints.nim b/tests/test_paints.nim index 8b7b439..304f9f2 100644 --- a/tests/test_paints.nim +++ b/tests/test_paints.nim @@ -9,8 +9,7 @@ const heartShape = """ """ block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( @@ -21,8 +20,7 @@ block: image.writeFile("tests/images/paths/paintSolid.png") block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( @@ -34,8 +32,7 @@ block: image.writeFile("tests/images/paths/paintImage.png") block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( @@ -47,8 +44,7 @@ block: image.writeFile("tests/images/paths/paintImageTiled.png") block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( @@ -66,8 +62,7 @@ block: image.writeFile("tests/images/paths/gradientLinear.png") block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( @@ -87,8 +82,7 @@ block: image.writeFile("tests/images/paths/gradientRadial.png") block: - let - image = newImage(100, 100) + let image = newImage(100, 100) image.fillPath( heartShape, Paint( diff --git a/tests/test_png.nim b/tests/test_png.nim index 056bdfc..57a618e 100644 --- a/tests/test_png.nim +++ b/tests/test_png.nim @@ -1,4 +1,4 @@ -import pixie/common, pixie/fileformats/png, pngsuite, strformat +import pixie, pixie/fileformats/png, pngsuite, strformat # for file in pngSuiteFiles: # let @@ -13,19 +13,23 @@ import pixie/common, pixie/fileformats/png, pngsuite, strformat # doAssert decoded.width == decoded2.width # doAssert decoded.data == decoded2.data -for channels in 1 .. 4: - var data: seq[uint8] - for x in 0 ..< 16: - for y in 0 ..< 16: - var components = newSeq[uint8](channels) - for i in 0 ..< channels: - components[i] = (x * 16).uint8 - data.add(components) - let encoded = encodePng(16, 16, channels, data[0].addr, data.len) +block: + for channels in 1 .. 4: + var data: seq[uint8] + for x in 0 ..< 16: + for y in 0 ..< 16: + var components = newSeq[uint8](channels) + for i in 0 ..< channels: + components[i] = (x * 16).uint8 + data.add(components) + let encoded = encodePng(16, 16, channels, data[0].addr, data.len) -for file in pngSuiteCorruptedFiles: - try: - discard decodePng(readFile(&"tests/images/png/pngsuite/{file}.png")) - doAssert false - except PixieError: - discard + for file in pngSuiteCorruptedFiles: + try: + discard decodePng(readFile(&"tests/images/png/pngsuite/{file}.png")) + doAssert false + except PixieError: + discard + +block: + discard readImage("tests/images/png/trailing_data.png") diff --git a/tests/validate_fonts.nim b/tests/validate_fonts.nim new file mode 100644 index 0000000..131c3e1 --- /dev/null +++ b/tests/validate_fonts.nim @@ -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')) diff --git a/tools/gen_readme.nim b/tools/gen_readme.nim index a8a5c64..57360cd 100644 --- a/tools/gen_readme.nim +++ b/tools/gen_readme.nim @@ -11,6 +11,8 @@ proc cutBetween(str, a, b: string): string = var md: seq[string] var exampleFiles = [ + "examples/text.nim", + "examples/text_spans.nim", "examples/square.nim", "examples/line.nim", "examples/rounded_rectangle.nim",