Hello! I'm no expert on DrawBot but I found your question to be very interesting, so I looked into figuring out a way to do a "color separation" of sorts — basically, I wanted to know if it was possible, as you suggested, to get a different BezierPath for each layer of color in a given string.
And it looks like it is (at least for Pappardelle Party)! Here's some rough & ready code (a lot of it is a copy/paste from the DrawBot BezierPath.textBox method, just to convert a string into its glyphNames — probably this method could be simplified but I don't know my way around the CoreText calls all that well), but the code seems to be working well on my computer running in the latest DrawBot app.
The bit at the end is where the customization happens, and you can access all the alternative CPAL palettes, just with the index of the palette in the CPAL table. (The one pictured is 1
I think).
import CoreText
import AppKit
def glyphIdentifiers(txt, font, fontSize):
from drawBot.context.baseContext import BaseContext
context = BaseContext()
context.font(font, fontSize)
path, (x, y) = context._getPathForFrameSetter((0, 0, 1200, 1200))
attributedString = context.attributedString(txt, "left")
setter = CoreText.CTFramesetterCreateWithAttributedString(attributedString)
frame = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None)
ctLines = CoreText.CTFrameGetLines(frame)
origins = CoreText.CTFrameGetLineOrigins(frame, (0, len(ctLines)), None)
glyphIDs = []
for i, (originX, originY) in enumerate(origins):
ctLine = ctLines[i]
ctRuns = CoreText.CTLineGetGlyphRuns(ctLine)
for ctRun in ctRuns:
attributes = CoreText.CTRunGetAttributes(ctRun)
font = attributes.get(AppKit.NSFontAttributeName)
baselineShift = attributes.get(AppKit.NSBaselineOffsetAttributeName, 0)
glyphCount = CoreText.CTRunGetGlyphCount(ctRun)
for i in range(glyphCount):
glyph = CoreText.CTRunGetGlyphs(ctRun, (i, 1), None)[0]
ax, ay = CoreText.CTRunGetPositions(ctRun, (i, 1), None)[0]
if glyph:
glyphIDs.append(glyph)
return glyphIDs
def getFontToolsFont(font_path):
from fontTools.ttLib import TTFont, TTLibError
from fontTools.misc.macRes import ResourceReader, ResourceError
return TTFont(font_path, lazy=True, fontNumber=None, res_name_or_index=None)
def glyphIdentifierToGlyphName(ft_font, glyph_id):
return ft_font.getGlyphOrder()[glyph_id]
from fontTools.ttLib.tables.C_O_L_R_ import table_C_O_L_R_
from fontTools.ttLib.tables.C_P_A_L_ import table_C_P_A_L_
def hex_to_tuple(palette):
return tuple([c/255 for c in (palette.red, palette.green, palette.blue, palette.alpha)])
def getPathAndColorForColorID(f, font_size, txt, _rect, color_id=0, palette_id=0):
fs2 = FormattedString()
fs2.font(f)
fs2.fontSize(font_size)
ft_font = getFontToolsFont(fs2.fontFilePath())
colr = table_C_O_L_R_()
cpal = table_C_P_A_L_()
colr.decompile(ft_font.getTableData("COLR"), ft_font)
cpal.decompile(ft_font.getTableData("CPAL"), ft_font)
ids = glyphIdentifiers(txt, f, 500)
glyphNames = [glyphIdentifierToGlyphName(ft_font, _id) for _id in ids]
for glyphName in glyphNames:
for layer in colr[glyphName]:
if layer.colorID == color_id:
fs2.fill(*hex_to_tuple(cpal.palettes[palette_id][layer.colorID]))
fs2.appendGlyph(layer.name)
bp = BezierPath()
bp.textBox(fs2, _rect)
return bp, hex_to_tuple(cpal.palettes[palette_id][color_id])
#############################
##### CUSTOMIZABLE CODE #####
#############################
font_name = "Pappardelle Party Regular"
font_size = 300
txt = "HELLO WORLD"
r = (100, 100, 800, 800)
palette_id = 1
for c in range(0, 4): # <----- which layers of color to actually print
path, color = getPathAndColorForColorID(font_name, font_size, txt, r, color_id=c, palette_id=palette_id)
fill(*color)
drawPath(path)