Color fonts: “Create Outlines”, changing palettes
-
I realize this is a pretty niche request, but I found it interesting so I figured I’d share it. Perhaps there is even a way of doing this already?
-
Normally, to convert text into outlines I would use a
BezierPath
object with a text method, but this doesn’t help me with aCOLR/CPAL
color font because the color information is lost to the monochromaticBezierPath
object. Is there another way of converting text to a series of differently-colored paths? -
I would also love to access alternative color palettes stored in the
CPAL
table, as well as to designate my own (as was requested here: https://twitter.com/bicvudesign/status/1058063097485803520). Maybe this requires us to wait for OS support, but I figured I’d mention it in case you were interested in pursuing.
As always, thanks again for all your work on this amazing tool!
-
-
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)
-
@robstenson Wow!! This works perfectly for me.
Of course it would be cool to see something like these solutions worked into the drawbot API, but this solves my problem. Thanks so much!
-
Waauw, nice!!
Im opening a issue to discuss it further see https://github.com/typemytype/drawbot/issues/237