From e66119f3152654f7b9def18f0b6aa82516eed7b9 Mon Sep 17 00:00:00 2001 From: Marius Andra Date: Fri, 12 Jun 2026 00:47:43 +0200 Subject: [PATCH] Add color emoji rendering for TTF fonts Support all three OpenType color emoji formats: - COLR/CPAL layered vector glyphs (Twemoji, OpenMoji, Segoe UI Emoji) - CBDT/CBLC embedded PNG bitmaps (Noto Color Emoji) - sbix embedded PNG bitmaps (Apple Color Emoji) fillText draws color glyphs automatically, including through typeface fallbacks. Bitmap strikes are selected by font size and decoded PNGs are cached on the Typeface. New Typeface.hasColorGlyph proc. Also: - Bitmap-only fonts (no glyf/CFF, like Noto Color Emoji) now load. - Platform 0 (Unicode) cmap subtables are now parsed; Apple Color Emoji has no Windows cmap at all. - typeset skips zero-width format runes (ZWJ, variation selectors) so emoji sequences degrade into their individual emoji instead of tofu. - strokeText skips color glyphs instead of crashing on outline-less fonts. Tests use three tiny generated fonts (one per format, see generate_emoji_fonts.py) plus an 11 KB subset of the real Twemoji Mozilla COLRv0 font. Verified against full Apple Color Emoji, Noto Color Emoji and Twemoji Mozilla. Co-Authored-By: Claude Fable 5 --- src/pixie/fontformats/opentype.nim | 489 ++++++++++++++++++++++++- src/pixie/fonts.nim | 88 ++++- tests/fonts/EmojiCbdt.ttf | Bin 0 -> 3248 bytes tests/fonts/EmojiColr.ttf | Bin 0 -> 1728 bytes tests/fonts/EmojiSbix.ttf | Bin 0 -> 3248 bytes tests/fonts/TwemojiMozilla-subset.ttf | Bin 0 -> 11084 bytes tests/fonts/generate_emoji_fonts.py | 382 +++++++++++++++++++ tests/fonts/masters/emoji_cbdt.png | Bin 0 -> 2229 bytes tests/fonts/masters/emoji_colr.png | Bin 0 -> 4786 bytes tests/fonts/masters/emoji_fallback.png | Bin 0 -> 4767 bytes tests/fonts/masters/emoji_real.png | Bin 0 -> 38406 bytes tests/fonts/masters/emoji_sbix.png | Bin 0 -> 2211 bytes tests/fonts/masters/emoji_scaled.png | Bin 0 -> 6023 bytes tests/fonts/masters/emoji_ubuntu.png | Bin 0 -> 23436 bytes tests/test_fonts.nim | 92 +++++ 15 files changed, 1040 insertions(+), 11 deletions(-) create mode 100644 tests/fonts/EmojiCbdt.ttf create mode 100644 tests/fonts/EmojiColr.ttf create mode 100644 tests/fonts/EmojiSbix.ttf create mode 100644 tests/fonts/TwemojiMozilla-subset.ttf create mode 100644 tests/fonts/generate_emoji_fonts.py create mode 100644 tests/fonts/masters/emoji_cbdt.png create mode 100644 tests/fonts/masters/emoji_colr.png create mode 100644 tests/fonts/masters/emoji_fallback.png create mode 100644 tests/fonts/masters/emoji_real.png create mode 100644 tests/fonts/masters/emoji_sbix.png create mode 100644 tests/fonts/masters/emoji_scaled.png create mode 100644 tests/fonts/masters/emoji_ubuntu.png diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index 537257a6..e300400a 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -1,4 +1,4 @@ -import flatty/binny, flatty/encode, math, ../common, ../paths, sets, +import chroma, flatty/binny, flatty/encode, math, ../common, ../paths, sets, strutils, tables, unicode, vmath ## See https://docs.microsoft.com/en-us/typography/opentype/spec/ @@ -368,6 +368,60 @@ type charIndex: seq[(int, int)] isCID: bool + ColrLayer* = object + glyphId*: uint16 + paletteIndex*: uint16 + + ColrTable* = ref object + ## Layered color glyphs (COLR version 0 records). + version*: uint16 + baseGlyphs*: Table[uint16, seq[ColrLayer]] + + CpalTable* = ref object + ## Color palettes used by the COLR table. + numPaletteEntries*: uint16 + palettes*: seq[seq[ColorRGBA]] + + BitmapGlyphRecord = object + imageFormat: uint16 + dataOffset: int ## Absolute offset of the glyph data block in the buffer. + dataLen: int + hasBigMetrics: bool + width, height: int + bearingX, bearingY: int + + BitmapStrike = object + ppem: int + glyphs: Table[uint16, BitmapGlyphRecord] + + CblcTable* = ref object + ## Color bitmap (PNG) glyph index, data lives in the CBDT table. + strikes: seq[BitmapStrike] + + SbixStrike = object + ppem: int + offset: int ## Absolute offset of the strike in the buffer. + glyphDataOffsets: seq[uint32] + + SbixTable* = ref object + ## Apple-style color bitmap (PNG) glyphs. + strikes: seq[SbixStrike] + + ColorGlyphLayer* = object + ## A single layer of a COLR color glyph. + path*: Path ## Layer outline in font units, y-flipped like getGlyphPath. + color*: ColorRGBA + useTextColor*: bool ## Layer uses the text fill color instead of a palette color. + + BitmapGlyph* = object + ## An embedded color bitmap glyph (CBDT or sbix). + png*: string ## Raw PNG file data. + ppem*: float32 ## Pixels per em of the bitmap strike. + xOffset*: float32 ## Origin to bitmap left edge, in strike pixels. + yOffset*: float32 ## Baseline to bitmap top edge (positive up), in strike + ## pixels. If yOffsetIsBottom, baseline to bottom edge. + yOffsetIsBottom*: bool + OpenType* = ref object buf*: string version*: uint32 @@ -389,7 +443,12 @@ type gpos*: GposTable post*: PostTable cff*: CFFTable + colr*: ColrTable + cpal*: CpalTable + cblc*: CblcTable + sbix*: SbixTable glyphPaths: Table[Rune, Path] + colorGlyphLayers: Table[Rune, seq[ColorGlyphLayer]] when defined(release): {.push checks: off.} @@ -450,8 +509,8 @@ proc parseCmapTable(buf: string, offset: int): CmapTable = encodingRecord.offset = buf.readUint32(i + 4).swap() i += 8 - if encodingRecord.platformID == 3: - # Windows + if encodingRecord.platformID == 3 or encodingRecord.platformID == 0: + # Windows and Unicode platforms, the subtable formats are the same. var i = offset + encodingRecord.offset.int buf.eofCheck(i + 2) @@ -2175,6 +2234,228 @@ proc parsePostTable(buf: string, offset: int): PostTable = result.underlineThickness = buf.readInt16(offset + 10).swap() result.isFixedPitch = buf.readUint32(offset + 12).swap() +proc parseColrTable(buf: string, offset: int): ColrTable = + ## https://learn.microsoft.com/en-us/typography/opentype/spec/colr + ## Parses the version 0 base glyph and layer records. These are also + ## present in version 1 tables as a fallback for the v1-only features. + buf.eofCheck(offset + 14) + + result = ColrTable() + result.version = buf.readUint16(offset + 0).swap() + + let + numBaseGlyphRecords = buf.readUint16(offset + 2).swap().int + baseGlyphRecordsOffset = buf.readUint32(offset + 4).swap().int + layerRecordsOffset = buf.readUint32(offset + 8).swap().int + numLayerRecords = buf.readUint16(offset + 12).swap().int + + var i = offset + layerRecordsOffset + buf.eofCheck(i + numLayerRecords * 4) + + var layers = newSeq[ColrLayer](numLayerRecords) + for j in 0 ..< numLayerRecords: + layers[j].glyphId = buf.readUint16(i + 0).swap() + layers[j].paletteIndex = buf.readUint16(i + 2).swap() + i += 4 + + i = offset + baseGlyphRecordsOffset + buf.eofCheck(i + numBaseGlyphRecords * 6) + + for j in 0 ..< numBaseGlyphRecords: + let + glyphId = buf.readUint16(i + 0).swap() + firstLayerIndex = buf.readUint16(i + 2).swap().int + numLayers = buf.readUint16(i + 4).swap().int + if firstLayerIndex + numLayers > layers.len: + failUnsupported("COLR layer index") + if numLayers > 0: + result.baseGlyphs[glyphId] = + layers[firstLayerIndex ..< firstLayerIndex + numLayers] + i += 6 + +proc parseCpalTable(buf: string, offset: int): CpalTable = + ## https://learn.microsoft.com/en-us/typography/opentype/spec/cpal + buf.eofCheck(offset + 12) + + result = CpalTable() + result.numPaletteEntries = buf.readUint16(offset + 2).swap() + + let + numPalettes = buf.readUint16(offset + 4).swap().int + numColorRecords = buf.readUint16(offset + 6).swap().int + colorRecordsArrayOffset = buf.readUint32(offset + 8).swap().int + + buf.eofCheck(offset + 12 + numPalettes * 2) + buf.eofCheck(offset + colorRecordsArrayOffset + numColorRecords * 4) + + for j in 0 ..< numPalettes: + let firstColorIndex = buf.readUint16(offset + 12 + j * 2).swap().int + if firstColorIndex + result.numPaletteEntries.int > numColorRecords: + failUnsupported("CPAL color index") + var + palette = newSeq[ColorRGBA](result.numPaletteEntries.int) + i = offset + colorRecordsArrayOffset + firstColorIndex * 4 + for k in 0 ..< result.numPaletteEntries.int: + # Color records are stored as BGRA. + palette[k] = rgba( + buf.readUint8(i + 2), + buf.readUint8(i + 1), + buf.readUint8(i + 0), + buf.readUint8(i + 3) + ) + i += 4 + result.palettes.add(palette) + +proc readBigMetrics( + buf: string, offset: int, record: var BitmapGlyphRecord +) = + ## Reads the first half of BigGlyphMetrics (the horizontal metrics). + buf.eofCheck(offset + 8) + record.hasBigMetrics = true + record.height = buf.readUint8(offset + 0).int + record.width = buf.readUint8(offset + 1).int + record.bearingX = buf.readInt8(offset + 2).int + record.bearingY = buf.readInt8(offset + 3).int + +proc parseCbdtIndexSubTable( + buf: string, + strike: var BitmapStrike, + subTableOffset, cbdtOffset, firstGlyphIndex, lastGlyphIndex: int +) = + buf.eofCheck(subTableOffset + 8) + + let + indexFormat = buf.readUint16(subTableOffset + 0).swap() + imageFormat = buf.readUint16(subTableOffset + 2).swap() + imageDataOffset = buf.readUint32(subTableOffset + 4).swap().int + + if imageFormat notin [17.uint16, 18, 19]: + # Only the PNG image formats are supported, skip anything else. + return + + let numGlyphsInRange = lastGlyphIndex - firstGlyphIndex + 1 + + case indexFormat: + of 1, 3: # Offsets array, uint32 or uint16 + let entrySize = if indexFormat == 1: 4 else: 2 + var i = subTableOffset + 8 + buf.eofCheck(i + (numGlyphsInRange + 1) * entrySize) + for j in 0 ..< numGlyphsInRange: + let (o1, o2) = + if indexFormat == 1: + (buf.readUint32(i + j * 4).swap().int, + buf.readUint32(i + j * 4 + 4).swap().int) + else: + (buf.readUint16(i + j * 2).swap().int, + buf.readUint16(i + j * 2 + 2).swap().int) + if o2 > o1: + strike.glyphs[(firstGlyphIndex + j).uint16] = BitmapGlyphRecord( + imageFormat: imageFormat, + dataOffset: cbdtOffset + imageDataOffset + o1, + dataLen: o2 - o1 + ) + of 2: # All glyphs have the same data size and metrics + buf.eofCheck(subTableOffset + 12) + let imageSize = buf.readUint32(subTableOffset + 8).swap().int + var record = BitmapGlyphRecord(imageFormat: imageFormat, dataLen: imageSize) + buf.readBigMetrics(subTableOffset + 12, record) + for j in 0 ..< numGlyphsInRange: + record.dataOffset = cbdtOffset + imageDataOffset + j * imageSize + strike.glyphs[(firstGlyphIndex + j).uint16] = record + of 4: # Sparse glyph id and offset pairs + buf.eofCheck(subTableOffset + 12) + let numGlyphs = buf.readUint32(subTableOffset + 8).swap().int + var i = subTableOffset + 12 + buf.eofCheck(i + (numGlyphs + 1) * 4) + for j in 0 ..< numGlyphs: + let + glyphId = buf.readUint16(i + j * 4).swap() + o1 = buf.readUint16(i + j * 4 + 2).swap().int + o2 = buf.readUint16(i + j * 4 + 6).swap().int + if o2 > o1: + strike.glyphs[glyphId] = BitmapGlyphRecord( + imageFormat: imageFormat, + dataOffset: cbdtOffset + imageDataOffset + o1, + dataLen: o2 - o1 + ) + of 5: # Sparse glyph ids, same data size and metrics + buf.eofCheck(subTableOffset + 24) + let imageSize = buf.readUint32(subTableOffset + 8).swap().int + var record = BitmapGlyphRecord(imageFormat: imageFormat, dataLen: imageSize) + buf.readBigMetrics(subTableOffset + 12, record) + let numGlyphs = buf.readUint32(subTableOffset + 20).swap().int + var i = subTableOffset + 24 + buf.eofCheck(i + numGlyphs * 2) + for j in 0 ..< numGlyphs: + record.dataOffset = cbdtOffset + imageDataOffset + j * imageSize + strike.glyphs[buf.readUint16(i + j * 2).swap()] = record + else: + discard + +proc parseCblcTable(buf: string, cblcOffset, cbdtOffset: int): CblcTable = + ## https://learn.microsoft.com/en-us/typography/opentype/spec/cblc + buf.eofCheck(cblcOffset + 8) + + let numSizes = buf.readUint32(cblcOffset + 4).swap().int + + result = CblcTable() + + var sizeOffset = cblcOffset + 8 + buf.eofCheck(sizeOffset + numSizes * 48) + + for s in 0 ..< numSizes: + let + indexSubTableArrayOffset = buf.readUint32(sizeOffset + 0).swap().int + numberOfIndexSubTables = buf.readUint32(sizeOffset + 8).swap().int + ppemX = buf.readUint8(sizeOffset + 44).int + + var strike = BitmapStrike(ppem: ppemX) + + var arrayOffset = cblcOffset + indexSubTableArrayOffset + buf.eofCheck(arrayOffset + numberOfIndexSubTables * 8) + for t in 0 ..< numberOfIndexSubTables: + let + firstGlyphIndex = buf.readUint16(arrayOffset + 0).swap().int + lastGlyphIndex = buf.readUint16(arrayOffset + 2).swap().int + additionalOffset = buf.readUint32(arrayOffset + 4).swap().int + if lastGlyphIndex >= firstGlyphIndex: + buf.parseCbdtIndexSubTable( + strike, + cblcOffset + indexSubTableArrayOffset + additionalOffset, + cbdtOffset, + firstGlyphIndex, + lastGlyphIndex + ) + arrayOffset += 8 + + if strike.glyphs.len > 0: + result.strikes.add(strike) + + sizeOffset += 48 + +proc parseSbixTable(buf: string, offset, numGlyphs: int): SbixTable = + ## https://learn.microsoft.com/en-us/typography/opentype/spec/sbix + buf.eofCheck(offset + 8) + + let numStrikes = buf.readUint32(offset + 4).swap().int + + result = SbixTable() + + buf.eofCheck(offset + 8 + numStrikes * 4) + + for s in 0 ..< numStrikes: + let strikeOffset = + offset + buf.readUint32(offset + 8 + s * 4).swap().int + buf.eofCheck(strikeOffset + 4 + (numGlyphs + 1) * 4) + var strike = SbixStrike( + ppem: buf.readUint16(strikeOffset + 0).swap().int, + offset: strikeOffset + ) + strike.glyphDataOffsets = newSeq[uint32](numGlyphs + 1) + for j in 0 .. numGlyphs: + strike.glyphDataOffsets[j] = buf.readUint32(strikeOffset + 4 + j * 4).swap() + result.strikes.add(strike) + proc getGlyphId(opentype: OpenType, rune: Rune): uint16 = result = opentype.cmap.runeToGlyphId.getOrDefault(rune, 0) @@ -2456,14 +2737,20 @@ proc parseCffGlyph(opentype: OpenType, glyphId: uint16): Path = charstring = opentype.buf[a ..< b] return cff.parseCFFCharstring(charstring, glyphId.int) -proc parseGlyph(opentype: OpenType, rune: Rune): Path {.inline.} = +proc parseGlyphById(opentype: OpenType, glyphId: uint16): Path = if opentype.glyf != nil: - opentype.parseGlyfGlyph(opentype.getGlyphId(rune)) + opentype.parseGlyfGlyph(glyphId) elif opentype.cff != nil: - opentype.parseCffGlyph(opentype.getGlyphId(rune)) + opentype.parseCffGlyph(glyphId) + elif opentype.cblc != nil or opentype.sbix != nil: + # Bitmap-only fonts have no glyph outlines. + newPath() else: raise newException(PixieError, "Invalid glyph storage") +proc parseGlyph(opentype: OpenType, rune: Rune): Path {.inline.} = + opentype.parseGlyphById(opentype.getGlyphId(rune)) + proc getGlyphPath*( opentype: OpenType, rune: Rune ): Path {.raises: [PixieError].} = @@ -2473,6 +2760,177 @@ proc getGlyphPath*( opentype.glyphPaths[rune] = path opentype.glyphPaths.getOrDefault(rune, nil) # Never actually returns nil +proc getColorGlyphLayers*( + opentype: OpenType, rune: Rune +): seq[ColorGlyphLayer] {.raises: [PixieError].} = + ## The COLR color layers for the rune, or an empty seq if the rune has no + ## layered color glyph. Layer paths are in font units, y-flipped like + ## getGlyphPath. + if opentype.colr == nil or opentype.cpal == nil or + opentype.cpal.palettes.len == 0: + return + if rune in opentype.colorGlyphLayers: + return opentype.colorGlyphLayers.getOrDefault(rune, @[]) + + let glyphId = opentype.getGlyphId(rune) + if glyphId in opentype.colr.baseGlyphs: + let palette = opentype.cpal.palettes[0] + for colrLayer in opentype.colr.baseGlyphs.getOrDefault(glyphId, @[]): + var layer = ColorGlyphLayer() + if colrLayer.paletteIndex == 0xFFFF: + layer.useTextColor = true + elif colrLayer.paletteIndex.int < palette.len: + layer.color = palette[colrLayer.paletteIndex.int] + else: + continue + layer.path = opentype.parseGlyphById(colrLayer.glyphId) + layer.path.transform(scale(vec2(1, -1))) + result.add(layer) + + opentype.colorGlyphLayers[rune] = result + +proc sbixGlyph( + opentype: OpenType, + strike: SbixStrike, + glyphId: uint16, + bitmap: var BitmapGlyph, + recursionDepth = 0 +): bool = + ## Reads a glyph record from an sbix strike, following "dupe" records. + if glyphId.int + 1 >= strike.glyphDataOffsets.len: + return false + let + o1 = strike.glyphDataOffsets[glyphId].int + o2 = strike.glyphDataOffsets[glyphId + 1].int + if o2 - o1 <= 8: # No glyph data, header alone is 8 bytes + return false + + let dataOffset = strike.offset + o1 + opentype.buf.eofCheck(strike.offset + o2) + + let graphicType = opentype.buf.readStr(dataOffset + 4, 4) + if graphicType == "dupe": + if recursionDepth > 4 or o2 - o1 < 10: + return false + let dupeGlyphId = opentype.buf.readUint16(dataOffset + 8).swap() + return opentype.sbixGlyph(strike, dupeGlyphId, bitmap, recursionDepth + 1) + if graphicType != "png ": + return false + + bitmap.png = opentype.buf[dataOffset + 8 ..< strike.offset + o2] + bitmap.ppem = strike.ppem.float32 + bitmap.xOffset = opentype.buf.readInt16(dataOffset + 0).swap().float32 + bitmap.yOffset = opentype.buf.readInt16(dataOffset + 2).swap().float32 + bitmap.yOffsetIsBottom = true + true + +proc getBitmapGlyph*( + opentype: OpenType, rune: Rune, sizePx: float32, bitmap: var BitmapGlyph +): bool {.raises: [PixieError].} = + ## Looks up an embedded color bitmap (PNG) glyph for the rune, picking the + ## best strike for the target pixel size. Returns false if the rune has no + ## bitmap glyph. + if rune notin opentype.cmap.runeToGlyphId: + return false + let glyphId = opentype.getGlyphId(rune) + + if opentype.cblc != nil: + # Pick the smallest strike >= sizePx, otherwise the largest one. + var best = -1 + for i, strike in opentype.cblc.strikes: + if glyphId notin strike.glyphs: + continue + if best == -1: + best = i + continue + let bestPpem = opentype.cblc.strikes[best].ppem + if bestPpem.float32 < sizePx: + if strike.ppem > bestPpem: + best = i + elif strike.ppem.float32 >= sizePx and strike.ppem < bestPpem: + best = i + if best >= 0: + let + strike = opentype.cblc.strikes[best] + record = strike.glyphs.getOrDefault(glyphId, BitmapGlyphRecord()) + var + pngOffset: int + pngLen: int + case record.imageFormat: + of 17: # Small glyph metrics, then PNG data + opentype.buf.eofCheck(record.dataOffset + 9) + bitmap.xOffset = opentype.buf.readInt8(record.dataOffset + 2).float32 + bitmap.yOffset = opentype.buf.readInt8(record.dataOffset + 3).float32 + pngLen = opentype.buf.readUint32(record.dataOffset + 5).swap().int + pngOffset = record.dataOffset + 9 + of 18: # Big glyph metrics, then PNG data + opentype.buf.eofCheck(record.dataOffset + 12) + bitmap.xOffset = opentype.buf.readInt8(record.dataOffset + 2).float32 + bitmap.yOffset = opentype.buf.readInt8(record.dataOffset + 3).float32 + pngLen = opentype.buf.readUint32(record.dataOffset + 8).swap().int + pngOffset = record.dataOffset + 12 + of 19: # Metrics in the CBLC index subtable, only PNG data here + if not record.hasBigMetrics: + return false + opentype.buf.eofCheck(record.dataOffset + 4) + bitmap.xOffset = record.bearingX.float32 + bitmap.yOffset = record.bearingY.float32 + pngLen = opentype.buf.readUint32(record.dataOffset + 0).swap().int + pngOffset = record.dataOffset + 4 + else: + return false + opentype.buf.eofCheck(pngOffset + pngLen) + bitmap.png = opentype.buf[pngOffset ..< pngOffset + pngLen] + bitmap.ppem = strike.ppem.float32 + bitmap.yOffsetIsBottom = false + return true + + if opentype.sbix != nil: + var best = -1 + for i, strike in opentype.sbix.strikes: + var candidate: BitmapGlyph + if not opentype.sbixGlyph(strike, glyphId, candidate): + continue + if best == -1: + best = i + continue + let bestPpem = opentype.sbix.strikes[best].ppem + if bestPpem.float32 < sizePx: + if strike.ppem > bestPpem: + best = i + elif strike.ppem.float32 >= sizePx and strike.ppem < bestPpem: + best = i + if best >= 0: + return opentype.sbixGlyph(opentype.sbix.strikes[best], glyphId, bitmap) + + false + +proc hasColorGlyph*(opentype: OpenType, rune: Rune): bool {.raises: [].} = + ## Returns true if the rune has a color (emoji) glyph. + if rune notin opentype.cmap.runeToGlyphId: + return false + let glyphId = opentype.getGlyphId(rune) + + if opentype.colr != nil and opentype.cpal != nil and + opentype.cpal.palettes.len > 0 and glyphId in opentype.colr.baseGlyphs: + return true + + if opentype.cblc != nil: + for strike in opentype.cblc.strikes: + if glyphId in strike.glyphs: + return true + + if opentype.sbix != nil: + for strike in opentype.sbix.strikes: + if glyphId.int + 1 < strike.glyphDataOffsets.len: + let + o1 = strike.glyphDataOffsets[glyphId].int + o2 = strike.glyphDataOffsets[glyphId + 1].int + if o2 - o1 > 8: + return true + + false + proc getLeftSideBearing*(opentype: OpenType, rune: Rune): float32 {.raises: [].} = let glyphId = opentype.getGlyphId(rune).int if glyphId < opentype.hmtx.hMetrics.len: @@ -2577,6 +3035,22 @@ proc parseOpenType*(buf: string, startLoc = 0): OpenType {.raises: [PixieError]. result.name = parseNameTable(buf, result.tableRecords["name"].offset.int) result.os2 = parseOS2Table(buf, result.tableRecords["OS/2"].offset.int) + if "COLR" in result.tableRecords and "CPAL" in result.tableRecords: + result.colr = parseColrTable(buf, result.tableRecords["COLR"].offset.int) + result.cpal = parseCpalTable(buf, result.tableRecords["CPAL"].offset.int) + + if "CBLC" in result.tableRecords and "CBDT" in result.tableRecords: + result.cblc = parseCblcTable( + buf, + result.tableRecords["CBLC"].offset.int, + result.tableRecords["CBDT"].offset.int + ) + + if "sbix" in result.tableRecords: + result.sbix = parseSbixTable( + buf, result.tableRecords["sbix"].offset.int, result.maxp.numGlyphs.int + ) + if "loca" in result.tableRecords and "glyf" in result.tableRecords: result.loca = parseLocaTable( buf, result.tableRecords["loca"].offset.int, result.head, result.maxp @@ -2586,7 +3060,8 @@ proc parseOpenType*(buf: string, startLoc = 0): OpenType {.raises: [PixieError]. elif "CFF " in result.tableRecords: result.cff = parseCFFTable(buf, result.tableRecords["CFF "].offset.int, result.maxp) - else: + elif result.cblc == nil and result.sbix == nil: + # Bitmap-only color fonts (e.g. Noto Color Emoji) have no outlines. failUnsupported("glyph outlines") if "kern" in result.tableRecords: diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 7c3f09b4..bd3e82b9 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -1,4 +1,4 @@ -import bumpy, chroma, common, os, fontformats/opentype, +import bumpy, chroma, common, os, fileformats/png, fontformats/opentype, fontformats/svgfont, images, paints, paths, strutils, unicode, vmath @@ -13,6 +13,7 @@ type svgFont: SvgFont filePath*: string fallbacks*: seq[Typeface] + bitmapGlyphCache: Table[(Rune, int), Image] ## (rune, ppem) -> decoded PNG Font* = ref object typeface*: Typeface @@ -118,6 +119,10 @@ proc hasGlyph*(typeface: Typeface, rune: Rune): bool {.inline.} = else: typeface.svgFont.hasGlyph(rune) +proc hasColorGlyph*(typeface: Typeface, rune: Rune): bool {.inline, raises: [].} = + ## Returns if there is a color (emoji) glyph for this rune. + typeface.opentype != nil and typeface.opentype.hasColorGlyph(rune) + proc fallbackTypeface*(typeface: Typeface, rune: Rune): Typeface = ## Looks through fallback typefaces to find one that has the glyph. if typeface.hasGlyph(rune): @@ -253,6 +258,13 @@ proc convertTextCase(runes: var seq[Rune], textCase: TextCase) = proc canWrap(rune: Rune): bool {.inline.} = rune == Rune(32) or rune.isWhiteSpace() +proc isFormatRune(rune: Rune): bool {.inline.} = + ## Zero-width format runes that should not produce a glyph: zero-width + ## joiner and variation selectors (commonly used in emoji sequences). + rune.uint32 == 0x200D or + rune.uint32 in 0xFE00.uint32 .. 0xFE0F.uint32 or + rune.uint32 in 0xE0100.uint32 .. 0xE01EF.uint32 + proc typeset*( spans: openarray[Span], bounds = vec2(0, 0), @@ -278,8 +290,11 @@ proc typeset*( 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: + # Ignore control runes (0 - 31) except LF for now. + # Also ignore zero-width format runes (ZWJ, variation selectors) so + # emoji sequences degrade gracefully into their individual emoji. + if (rune.uint32 >= SP.uint32 or rune.uint32 == LF.uint32) and + not rune.isFormatRune(): runes.add(rune) if runes.len > 0: @@ -529,8 +544,15 @@ proc computePaths(arrangement: Arrangement): seq[Path] = strikeoutPosition = font.typeface.strikeoutPosition * font.scale for runeIndex in start .. stop: let + rune = arrangement.runes[runeIndex] position = arrangement.positions[runeIndex] - path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) + runeTypeface = font.typeface.fallbackTypeface(rune) + path = + if runeTypeface != nil and runeTypeface.hasColorGlyph(rune): + # Color glyphs are drawn separately, see drawColorGlyphs. + newPath() + else: + font.typeface.getGlyphPath(rune) path.transform( translate(position) * scale(vec2(font.scale)) @@ -563,6 +585,63 @@ proc computePaths(arrangement: Arrangement): seq[Path] = spanPath.addPath(path) result.add(spanPath) +proc drawColorGlyphs( + target: Image, + arrangement: Arrangement, + spanIndex: int, + transform: Mat3 +) {.raises: [PixieError].} = + ## Draws the color (emoji) glyphs of a span onto the target image. + let + font = arrangement.fonts[spanIndex] + (start, stop) = arrangement.spans[spanIndex] + for runeIndex in start .. stop: + let + rune = arrangement.runes[runeIndex] + typeface = font.typeface.fallbackTypeface(rune) + if typeface == nil or not typeface.hasColorGlyph(rune): + continue + + let + position = arrangement.positions[runeIndex] + opentype = typeface.opentype + + let layers = opentype.getColorGlyphLayers(rune) + if layers.len > 0: + # COLR layered vector glyph. + let layerScale = font.size / typeface.scale + for layer in layers: + let path = newPath() + path.addPath(layer.path) + path.transform(translate(position) * scale(vec2(layerScale))) + let paint = newPaint(SolidPaint) + paint.color = + if layer.useTextColor: + font.paint.color + else: + layer.color.color + target.fillPath(path, paint, transform) + continue + + # Embedded PNG bitmap glyph (CBDT or sbix). + var bitmap: BitmapGlyph + if opentype.getBitmapGlyph(rune, font.size, bitmap): + let key = (rune, bitmap.ppem.int) + if key notin typeface.bitmapGlyphCache: + typeface.bitmapGlyphCache[key] = newImage(decodePng(bitmap.png)) + let + image = typeface.bitmapGlyphCache.getOrDefault(key, nil) + bitmapScale = font.size / bitmap.ppem + top = + if bitmap.yOffsetIsBottom: + bitmap.yOffset + image.height.float32 + else: + bitmap.yOffset + offset = position + vec2(bitmap.xOffset, -top) * bitmapScale + target.draw( + image, transform * translate(offset) * scale(vec2(bitmapScale)) + ) + proc textUber( target: Image, arrangement: Arrangement, @@ -594,6 +673,7 @@ proc textUber( let font = arrangement.fonts[spanIndex] for paint in font.paints: target.fillPath(path, paint, transform) + target.drawColorGlyphs(arrangement, spanIndex, transform) proc computeBounds*( arrangement: Arrangement, diff --git a/tests/fonts/EmojiCbdt.ttf b/tests/fonts/EmojiCbdt.ttf new file mode 100644 index 0000000000000000000000000000000000000000..182f585541e2a6ca2c9793444b9f2be4943e1c35 GIT binary patch literal 3248 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 literal 0 HcmV?d00001 diff --git a/tests/fonts/EmojiSbix.ttf b/tests/fonts/EmojiSbix.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0202c782ea9f36c7ee7f2ab655af02eb2cce6811 GIT binary patch literal 3248 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 LdJ~#f-5LA8YSFuP literal 0 HcmV?d00001 diff --git a/tests/fonts/generate_emoji_fonts.py b/tests/fonts/generate_emoji_fonts.py new file mode 100644 index 00000000..a38106b8 --- /dev/null +++ b/tests/fonts/generate_emoji_fonts.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python3 +"""Generates the tiny color emoji test fonts used by test_fonts.nim. + +Each font maps U+1F600 (grinning face), U+2764 (heavy black heart), +U+2B50 (star), U+1F319 (crescent moon) and U+2600 (sun) and stores the +color glyphs in a different OpenType color format: + + EmojiColr.ttf - COLR/CPAL layered vector glyphs + EmojiCbdt.ttf - CBDT/CBLC embedded PNG bitmaps (no glyf table, like + Noto Color Emoji) + EmojiSbix.ttf - sbix embedded PNG bitmaps (like Apple Color Emoji) + +tests/fonts/TwemojiMozilla-subset.ttf is a real-world COLRv0 color emoji +font (Twemoji Mozilla, CC-BY 4.0 Twitter emoji graphics). It is produced +from https://github.com/mozilla/twemoji-colr/releases (Twemoji.Mozilla.ttf, +v0.7.0) with: + + pyftsubset Twemoji.Mozilla.ttf \ + --output-file=tests/fonts/TwemojiMozilla-subset.ttf \ + --unicodes="1F600,1F602,2764,FE0F,1F44D,1F389,1F30D,1F355,1F680,2B50,\ +1F319,2600,1F436,1F431,1F98A,1F354,26BD,1F3B8,1F4A1,2705,1F525" \ + --no-layout-closure + +Requires: pip install fonttools pillow +""" + +import math +import struct + +from fontTools.fontBuilder import FontBuilder +from fontTools.pens.ttGlyphPen import TTGlyphPen +from fontTools.ttLib import newTable +from fontTools.ttLib.tables.sbixGlyph import Glyph as SbixGlyph +from fontTools.ttLib.tables.sbixStrike import Strike as SbixStrike +from fontTools.ttLib.tables.DefaultTable import DefaultTable +from PIL import Image, ImageDraw + +UPM = 1000 +ASCENT = 800 +DESCENT = -200 +PPEM = 64 + +EMOJI_NAMES = ["smiley", "heart", "star", "moon", "sun"] +CMAP = { + 0x1F600: "smiley", + 0x2764: "heart", + 0x2B50: "star", + 0x1F319: "moon", + 0x2600: "sun", +} + + +def circle(pen, cx, cy, r, clockwise=True): + # A circle approximated with four TrueType quadratic arcs. Clockwise by + # default like rect() and heart_shape(), so that overlapping contours + # within a glyph add up instead of cancelling under non-zero filling. + # A counterclockwise circle subtracts (cuts a hole) instead. + pen.moveTo((cx + r, cy)) + if clockwise: + pen.qCurveTo((cx + r, cy - r), (cx, cy - r)) + pen.qCurveTo((cx - r, cy - r), (cx - r, cy)) + pen.qCurveTo((cx - r, cy + r), (cx, cy + r)) + pen.qCurveTo((cx + r, cy + r), (cx + r, cy)) + else: + pen.qCurveTo((cx + r, cy + r), (cx, cy + r)) + pen.qCurveTo((cx - r, cy + r), (cx - r, cy)) + pen.qCurveTo((cx - r, cy - r), (cx, cy - r)) + pen.qCurveTo((cx + r, cy - r), (cx + r, cy)) + pen.closePath() + + +def star_points(cx, cy, r): + pts = [] + for i in range(10): + a = math.pi / 2 + i * math.pi / 5 + rad = r if i % 2 == 0 else r * 0.4 + pts.append((cx + rad * math.cos(a), cy + rad * math.sin(a))) + return pts + + +def star_shape(pen, cx, cy, r): + pts = [(round(x), round(y)) for x, y in reversed(star_points(cx, cy, r))] + pen.moveTo(pts[0]) + for pt in pts[1:]: + pen.lineTo(pt) + pen.closePath() + + +def moon_shape(pen, cx, cy, r): + # A crescent as a single contour: the left half of a circle closed with + # an inward arc. Two overlapping circles cannot express a crescent under + # non-zero winding (the cutter fills wherever it leaves the outer circle). + top = (cx, cy + r) + bottom = (cx, cy - r) + pen.moveTo(top) + pen.qCurveTo((cx - r, cy + r), (cx - r, cy)) + pen.qCurveTo((cx - r, cy - r), bottom) + pen.qCurveTo((cx - r * 0.35, cy - r * 0.6), (cx - r * 0.35, cy)) + pen.qCurveTo((cx - r * 0.35, cy + r * 0.6), top) + pen.closePath() + + +def sun_rays_shape(pen, cx, cy, r1, r2): + # Eight triangular rays pointing outward. + for i in range(8): + a = i * math.pi / 4 + tip = (cx + r2 * math.cos(a), cy + r2 * math.sin(a)) + base1 = (cx + r1 * math.cos(a - 0.2), cy + r1 * math.sin(a - 0.2)) + base2 = (cx + r1 * math.cos(a + 0.2), cy + r1 * math.sin(a + 0.2)) + pen.moveTo((round(tip[0]), round(tip[1]))) + pen.lineTo((round(base1[0]), round(base1[1]))) + pen.lineTo((round(base2[0]), round(base2[1]))) + pen.closePath() + + +def rect(pen, x, y, w, h): + pen.moveTo((x, y)) + pen.lineTo((x, y + h)) + pen.lineTo((x + w, y + h)) + pen.lineTo((x + w, y)) + pen.closePath() + + +def heart_shape(pen, cx, cy, s): + # A simple heart from a triangle and two circles. + pen.moveTo((cx - s, cy + s * 0.35)) + pen.lineTo((cx + s, cy + s * 0.35)) + pen.lineTo((cx, cy - s)) + pen.closePath() + circle(pen, cx - s * 0.48, cy + s * 0.35, s * 0.52) + circle(pen, cx + s * 0.48, cy + s * 0.35, s * 0.52) + + +def build_glyph(draw_func): + pen = TTGlyphPen(None) + draw_func(pen) + return pen.glyph() + + +def empty_glyph(): + return TTGlyphPen(None).glyph() + + +def base_font(fb_glyphs): + """Builds the common font scaffolding shared by all three test fonts.""" + glyph_order = [".notdef"] + list(fb_glyphs.keys()) + fb = FontBuilder(UPM) + fb.setupGlyphOrder(glyph_order) + fb.setupCharacterMap(CMAP) + glyphs = {".notdef": empty_glyph()} + glyphs.update(fb_glyphs) + fb.setupGlyf(glyphs) + metrics = {} + for name in glyph_order: + advance = UPM if name != ".notdef" else 500 + metrics[name] = (advance, 0) + fb.setupHorizontalMetrics(metrics) + fb.setupHorizontalHeader(ascent=ASCENT, descent=DESCENT) + fb.setupOS2(sTypoAscender=ASCENT, sTypoDescender=DESCENT, + usWinAscent=ASCENT, usWinDescent=-DESCENT) + fb.setupPost() + return fb + + +def name_font(fb, family): + fb.setupNameTable({"familyName": family, "styleName": "Regular"}) + + +# ---------------------------------------------------------------- COLR/CPAL + +def build_colr(): + s = 300 # smiley radius / heart size + cx, cy = 500, 300 + + glyphs = { + # Fallback monochrome outlines for the base glyphs. + "smiley": build_glyph(lambda p: circle(p, cx, cy, s)), + "heart": build_glyph(lambda p: heart_shape(p, cx, cy, s * 0.8)), + "star": build_glyph(lambda p: star_shape(p, cx, cy, s)), + "moon": build_glyph(lambda p: moon_shape(p, cx, cy, s * 0.9)), + "sun": build_glyph(lambda p: circle(p, cx, cy, s * 0.55)), + # Color layers. + "smiley_face": build_glyph(lambda p: circle(p, cx, cy, s)), + "smiley_eyes": build_glyph(lambda p: ( + circle(p, cx - 120, cy + 90, 50), circle(p, cx + 120, cy + 90, 50))), + "smiley_mouth": build_glyph(lambda p: rect(p, cx - 150, cy - 160, 300, 80)), + "heart_fill": build_glyph(lambda p: heart_shape(p, cx, cy, s * 0.8)), + "heart_accent": build_glyph(lambda p: rect(p, cx - 60, cy - 290, 120, 60)), + "star_fill": build_glyph(lambda p: star_shape(p, cx, cy, s)), + "moon_fill": build_glyph(lambda p: moon_shape(p, cx, cy, s * 0.9)), + "sun_rays": build_glyph(lambda p: sun_rays_shape(p, cx, cy, s * 0.7, s)), + "sun_disk": build_glyph(lambda p: circle(p, cx, cy, s * 0.55)), + } + + fb = base_font(glyphs) + name_font(fb, "Emoji Colr Test") + + fb.setupCOLR({ + "smiley": [("smiley_face", 0), ("smiley_eyes", 1), ("smiley_mouth", 2)], + # The 0xFFFF palette index means "use the text color". + "heart": [("heart_fill", 3), ("heart_accent", 0xFFFF)], + "star": [("star_fill", 4)], + "moon": [("moon_fill", 5)], + "sun": [("sun_rays", 6), ("sun_disk", 0)], + }) + fb.setupCPAL([[ + (1.00, 0.80, 0.10, 1.0), # 0 yellow face + (0.25, 0.18, 0.10, 1.0), # 1 dark eyes + (0.75, 0.15, 0.15, 1.0), # 2 red mouth + (0.90, 0.20, 0.35, 1.0), # 3 heart pink + (1.00, 0.72, 0.05, 1.0), # 4 star gold + (0.95, 0.90, 0.55, 1.0), # 5 moon cream + (0.98, 0.55, 0.10, 1.0), # 6 sun ray orange + ]]) + + fb.save("tests/fonts/EmojiColr.ttf") + + +# ------------------------------------------------------------------ bitmaps + +def smiley_png(size): + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + m = size * 0.04 + d.ellipse([m, m, size - m, size - m], fill=(255, 204, 26, 255)) + r = size * 0.07 + for ex in (size * 0.32, size * 0.68): + d.ellipse([ex - r, size * 0.34 - r, ex + r, size * 0.34 + r], + fill=(64, 46, 26, 255)) + d.arc([size * 0.25, size * 0.35, size * 0.75, size * 0.78], + 20, 160, fill=(64, 46, 26, 255), width=max(2, size // 16)) + return img + + +def heart_png(size): + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + s = size + d.polygon([(s * 0.08, s * 0.38), (s * 0.92, s * 0.38), (s * 0.5, s * 0.95)], + fill=(230, 50, 90, 255)) + d.ellipse([s * 0.04, s * 0.08, s * 0.5, s * 0.54], fill=(230, 50, 90, 255)) + d.ellipse([s * 0.5, s * 0.08, s * 0.96, s * 0.54], fill=(230, 50, 90, 255)) + return img + + +def star_png(size): + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + # star_points works in y-up coordinates, flip y for the bitmap. + pts = [(x, size - y) for x, y in + star_points(size / 2, size / 2, size * 0.48)] + d.polygon(pts, fill=(255, 184, 13, 255)) + return img + + +def moon_png(size): + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + m = size * 0.05 + d.ellipse([m, m, size - m, size - m], fill=(242, 230, 140, 255)) + # ImageDraw writes raw pixel values, so a transparent fill cuts a hole. + o = size * 0.3 + d.ellipse([m + o, m - o * 0.4, size - m + o, size - m - o * 0.4], + fill=(0, 0, 0, 0)) + return img + + +def sun_png(size): + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + c = size / 2 + for i in range(8): + a = i * math.pi / 4 + tip = (c + size * 0.48 * math.cos(a), c + size * 0.48 * math.sin(a)) + base1 = (c + size * 0.3 * math.cos(a - 0.25), + c + size * 0.3 * math.sin(a - 0.25)) + base2 = (c + size * 0.3 * math.cos(a + 0.25), + c + size * 0.3 * math.sin(a + 0.25)) + d.polygon([tip, base1, base2], fill=(250, 140, 26, 255)) + r = size * 0.28 + d.ellipse([c - r, c - r, c + r, c + r], fill=(255, 204, 26, 255)) + return img + + +PNG_DRAWERS = { + "smiley": smiley_png, + "heart": heart_png, + "star": star_png, + "moon": moon_png, + "sun": sun_png, +} + + +def png_bytes(img): + import io + buf = io.BytesIO() + img.save(buf, "PNG") + return buf.getvalue() + + +def build_cbdt(): + glyphs = {name: empty_glyph() for name in EMOJI_NAMES} + fb = base_font(glyphs) + name_font(fb, "Emoji Cbdt Test") + font = fb.font + + pngs = [png_bytes(PNG_DRAWERS[name](PPEM)) for name in EMOJI_NAMES] + + # CBDT: header, then per glyph image format 17 data + # (small glyph metrics + png length + png data). + bearing_y = int(PPEM * 0.85) + cbdt = struct.pack(">HH", 3, 0) + offsets = [] # relative to the start of the glyph data area + pos = 0 + blocks = b"" + for png in pngs: + offsets.append(pos) + block = struct.pack(">BBbbB", PPEM, PPEM, 0, bearing_y, PPEM) + block += struct.pack(">I", len(png)) + png + blocks += block + pos += len(block) + offsets.append(pos) + cbdt += blocks + + # CBLC: one strike covering glyphs 1..len(EMOJI_NAMES), index format 1, + # image format 17. + last_gid = len(EMOJI_NAMES) + line_metrics = struct.pack(">bbBbbbbbbbbb", + bearing_y, bearing_y - PPEM, PPEM, + 0, 1, 0, 0, 0, 0, 0, 0, 0) + index_subtable = struct.pack(">HHI", 1, 17, 4) # format 1, png, after header + index_subtable += b"".join(struct.pack(">I", o) for o in offsets) + subtable_array = struct.pack(">HHI", 1, last_gid, 8) # after the array + index_tables_size = len(subtable_array) + len(index_subtable) + bitmap_size = struct.pack(">IIII", 56, index_tables_size, 1, 0) + bitmap_size += line_metrics + line_metrics + bitmap_size += struct.pack(">HHBBBb", 1, last_gid, PPEM, PPEM, 32, 1) + assert len(bitmap_size) == 48 + cblc = struct.pack(">HHI", 3, 0, 1) + bitmap_size + subtable_array + index_subtable + + for tag, data in (("CBDT", cbdt), ("CBLC", cblc)): + table = DefaultTable(tag) + table.data = data + font[tag] = table + + # Bitmap-only font: drop the outlines like real CBDT emoji fonts do. + font.recalcBBoxes = False + font["maxp"].tableVersion = 0x00005000 + del font["glyf"] + del font["loca"] + + fb.save("tests/fonts/EmojiCbdt.ttf") + + +def build_sbix(): + glyphs = {name: empty_glyph() for name in EMOJI_NAMES} + fb = base_font(glyphs) + name_font(fb, "Emoji Sbix Test") + font = fb.font + + sbix = newTable("sbix") + sbix.version = 1 + sbix.flags = 1 + strike = SbixStrike(ppem=PPEM, resolution=72) + descent_px = int(PPEM * 0.15) + for name in EMOJI_NAMES: + glyph = SbixGlyph(glyphName=name, graphicType="png ", + imageData=png_bytes(PNG_DRAWERS[name](PPEM)), + originOffsetX=0, + originOffsetY=-descent_px) + strike.glyphs[name] = glyph + sbix.strikes = {PPEM: strike} + font["sbix"] = sbix + + fb.save("tests/fonts/EmojiSbix.ttf") + + +if __name__ == "__main__": + build_colr() + build_cbdt() + build_sbix() + print("Wrote tests/fonts/EmojiColr.ttf, EmojiCbdt.ttf, EmojiSbix.ttf") diff --git a/tests/fonts/masters/emoji_cbdt.png b/tests/fonts/masters/emoji_cbdt.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f13d5d1c31cd6584e872fe8b6867c83bfa4fa2 GIT binary patch literal 2229 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 F`#<>T<{kh5 literal 0 HcmV?d00001 diff --git a/tests/fonts/masters/emoji_fallback.png b/tests/fonts/masters/emoji_fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..4175f5eb62550aa87b4b49d83ccdb00c06f5a802 GIT binary patch literal 4767 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 literal 0 HcmV?d00001 diff --git a/tests/fonts/masters/emoji_real.png b/tests/fonts/masters/emoji_real.png new file mode 100644 index 0000000000000000000000000000000000000000..afbb4244c70ffdc19c5a3a445a4b6e64bf4b3349 GIT binary patch literal 38406 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 $mj-(pWUZ=c1dw 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 literal 0 HcmV?d00001 diff --git a/tests/fonts/masters/emoji_sbix.png b/tests/fonts/masters/emoji_sbix.png new file mode 100644 index 0000000000000000000000000000000000000000..6b44b00acc84dc0de7664955c917e42a476652f6 GIT binary patch literal 2211 zcmd5+`#;kS1N{!M#7w#6VK$dsa*5YvGILwDkVS^(Qj2=Za(ftaZ7Q0?=yfTHFiCDv zdC-rjI{<(( z_jMpxe4n57T!{t%aL{Rtod*@PGWjuh0tZbY#e&pYqwPpKNk2Z=33+mIUCil6x5oZ$ z0ZK+JhNw{nMApwbgbRoK{3Z#XJ@K|p)ju*Vxlz4a8v%ccCtn_ZgC%w+sJt<4QGtJ#t#>VjrBg7lZ_33!l6Uc?WNpm3wf zV_uOg4VzTRyyQfJiZyD)TlL3KuB}AX_M?73V?<@3al4g&HGRxZ^ zy)g3BerWLC?28w+c)%%ivMV$E_U~WO6~%SDoxLjKPuYCS%zmz+4d>~2O`t>k--ZwX(39B5EMH!%>RVF|QHT{Ju!4rRT|QWp7>yE;1B5XxhYp_20OD?ThL_ zs<1c~BG%|Wm@x3V(ad_b0r{%|rgp=ZD^W6$B-L?ZX9pdVg_0xl{QZBG}_*S@BuFVtUoCrAily7NcXt2X^)QO>htd;Phs+dKd zb?;2uuxm+w+UqOf7dW2jYe;*=`z?%CmFmrUHJU>n;D%c-;u`(0IKwG4)?i6pk=y$^ zVoxKzYr#FHU`RIXsKBM)k51*Pr{Fp*tu|MSQwkyNeGxOoX)ChuL$Oj>vk7W3G z+TA;4=}yW(E<6Kg%3CMX`ERI(eHlE31k0*Ck%^u}>M7dwMFi&g@~u+)-S=|^Hg9^=9+Zd`kz~!;8^O>t8)JmBdbs?)(R%6;6+QHn&70kgRophG z{p)$}!kXt-5p&Ih3n&|l9@9mlHNMh4Y#13TyY_vNK<(G< zA;LTr=8`@a13uCtvZ{w7CYF-gLh;Myn6Rq?QH<_KvV2s|iYM|?IHB83bNzBdb*n#* z`~_6N9#n|hUt(ETCHYSG;LjkGx&Lzt9ZLMRF_!!w89Xy{lVh2gkC?Sa5hD~~3eHZT zytZ`Q12vs-TtqeJW-ck`-bf6c3)Y6X-Kk)XCT#1Ig|E*(1zzMVV{3gxYSr!==5K zWjZM3nxPTBb;Aya`fO7f>1%Yc=87=Gzvxvl25V+$ax^0n1|c+Ooozn1wx7x~LH!j> zQGqne+G8AdJ5Or&P7-CH$-e;`cAjEldhn}dw4*fLX7&;O^T`xDs>?OhugPGgGJQmjm_7e6;p@P)^V6-|JNMn07Ddi1 zFT6Rr_FaI(WFk!}iG92iiE`9J%$lQ8Zj00s#(dIW7E|)3jdA8|HO=C%@+ffGxH^VY z5d{(Z>U*b4VdeW#NG^-0xWUDjL!Es*8Tf^|6*Q9qcW5*0JiTdrKN3+R8d!b&ncs5t zd+V2a{1Md;8+0}^Oh7>p$B+4Pa01%``S~-Dh5SFnYdQi>S5`i|*DPCUkB-vJeVn>k znke}Gw_B`)$@)|(Qi@TL?W&2Px>$iKOG1zmp*_wcs!ou;3mY20Rd7pXF)Zq1FUI@! z-~_?@$YoYpwG&GIIzB?zj@r3@wW}50arr$Nt*h#oP2({mtyW{HI=sc^(|6;s@wzul z)Gm9GVss{8yv%C`nqY=VN<_WRtn4k(U0wFQ(V!(A;r-7j{r1}u3M{^ONt9~bqSK|p zW*H{IP6np3IPN`dmKL8LOw>5>>&Xp`8Sm*FwWlKiVfXwbCb30NlWod{&RmICxi^l6 zrVcyPtPV*6J%#v&NR1?xG!%FMx4krX-W%wJ!s=w<03a(O2EgrU0Du8Q0U8)20zeVA n0H7`g25fHwAb<}2KQ!&gu*c{{1?S{zfqk4l<%+4Xzj*T>9fRtK literal 0 HcmV?d00001 diff --git a/tests/fonts/masters/emoji_scaled.png b/tests/fonts/masters/emoji_scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..ff3e6d61c7a2bed528966e5bc6df3dd49ff4a943 GIT binary patch literal 6023 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 DKVDHq literal 0 HcmV?d00001 diff --git a/tests/fonts/masters/emoji_ubuntu.png b/tests/fonts/masters/emoji_ubuntu.png new file mode 100644 index 0000000000000000000000000000000000000000..e6dc53cade433d6212e3552c048859ae294a3683 GIT binary patch literal 23436 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