Package: paintcompiler
Paint compiler for COLRv1 fonts%paintcompiler--output"Example-Color.ttf"Example-Mono.ttfThere's a huge amount of very clever and pretty things you can do with theCOLRv1 font fonmat. However, most font editors only expose a very small subset of the capabilities of the format. This is largely because, due to the fact that COLRv1 is so rich and extensive, it's not easy to produce a user interface which exposes all the functionality in a flexible way.paintcompileris a Python library and command line tool which makes it (slightly) easier to add COLRv1COLRandCPALtables to your fonts, using all the features that the format has to offer. That's the positive side. The negative side is that you need to describe your COLRv1 paints as Python code.COLRv1 describes color glyphs as a kind of tree structure. Inpaintcompilerthese tree nodes are described using Python functions. For example, to say that the glyphAis made up of a redsquareglyph and a bluecircleglyph, you would say:glyphs["A"]=PaintColrLayers([PaintGlyph("square",PaintSolid("#FF0000FF")),PaintGlyph("circle",PaintSolid("#0000FFFF"))])The full interface is described below. See the example inexample/paints.pyfor a paint file which exercises all paints.UsingpaintcompilerTo usepaintcompiler, you need to write apaint definition file. This is a Python program which specifies which paints are applied to each glyph.paintcompilerwill call your Python program in an environment with certain variables predefined. Your job is to fill theglyphsdictionary with the result of certainPaint...()function calls. In the example above, we callPaintColorLayersto define our top-level paint, and associate this with theAglyph.As well as theglyphsdictionary and thePaint...()(andColorLine/VarColorLine) functions,paintcompileralso provides thefontvariable, which is afontTools.ttLib.TTFontobject representing the font. This means you can query the font and define your paints programmatically. For example:SOLIDRED=PaintSolid("#FF0000FF")BLUEDOT=PaintGlyph("dot",PaintSolid("#0000FFFF"))forglyphnameinfont.getGlyphOrder():advancewidth=font["hmtx"][glyphname][0]glyphs[glyphname]=PaintColrLayers(# Paint the glyph in redPaintGlyph(glyphname,SOLIDRED),# Put a blue dot in the bottom right corner of the glyphPaintTranslate(advancewidth-100,-100,BLUEDOT))Typically the paint definition file is calledpaints.py. If not, you can use the-p/--paintsflag on the command line to specify a different name.UsingpaintdecompilerIf you have anexistingCOLRv1 font and you want to turn it back into a Python definition (paints file), you can use thepaintdecompilerutility included with this module to do this. It is not smart enough to e.g. extract shared colorlines or variations into a Python variable, but it should get you started.$ paintdecompiler example/COLRv1-Test.ttf
Written on paints.py
$ head paints.py
glyphs["p1_PaintColrLayers"] = PaintColrLayers(
    [
        PaintGlyph("square", PaintSolid("#EA4335FF")),
        PaintGlyph("circle", PaintSolid("#4285F4FF")),
    ],
)
glyphs["p2_PaintSolid"] = PaintGlyph("square", PaintSolid("#EA4335FF"))
...Variation specificationsYou can createvariablepaints by passing avariation specificationto the paint function. This variation specification describes how the values to the paint functions vary at different positions in the designspace, and is made up of a dictionary mapping a tuple of axis/location pairs to a value. For example, to create a glyph which rotates -180 degrees when theROTAaxis is at -1.0 and rotates 359 degrees when theROTAaxis is at 2.0, do this:PaintRotate({(("ROTA",-1.0),):-180,(("ROTA",2.0),):359,},PaintGlyph("square",PaintSolid("#FF0000FF")))Noticethat the method here isPaintRotate. Youcanalso callPaintVarRotate, but the compiler knows that this has a variation specifier, and so should be upgraded to a variable rotation.(A variation specification can also be written as a variable-FEA-like string"ROTA=-1.0:-180 ROTA=2.0:359", but that's deprecated and you'll get a warning.)You can specify multiple axes in your variation specification by adding more axis/location pairs to the tuple:PaintRotate({(("ROTX",-1.0),("ROTY",-1.0)):-180,(("ROTX",-1.0),("ROTY",0.0)):-90,(("ROTX",2.0),("ROTY",0.0)):-90,(("ROTX",2.0),("ROTY",-1.0)):-359,},PaintGlyph("square",PaintSolid("#FF0000FF")))Adding synthetic axesWhen adding variable paints, you might want to add additional variation axes to your font - in other words, the axes only control the variable paints, and so aren't present when the font is compiled as a non-color font.fontcompilerprovides the--add-axiscommand line flag to add one or more axes to your font:$paintcompiler\--add-axis"ALPH:0:0.5:1:Alpha value"\--add-axis"STAX:0:0:1000:Start X coordinate"\--add-axis"STAY:0:0:1000:Start Y coordinate"\--output"Example-Color.ttf"\Example-Mono.ttfNow let's look at the functions that are available.Paint FunctionsPaintColrLayers(paints)Stacks a number of paints together.paintsshould be an array of paints returned from other paint functions.PaintSolid(color_or_colors,alpha=1.0)Creates a solid color paint, with the (potentially varying) given alpha value. Acolor stringmust be either an eight digit hex RGBA value, or the stringforegroundto specify the current ink color in the user's application.color_or_colorsis either a single color string or, to give the user a choice of different color palettes, a Python list of color strings. Thealphaparameter can either be a float from 0.0 to 1.0 or avariation specificationas described above.PaintLinearGradient((start_x,start_y),(end_x,end_y),(rot_x,rot_y),colorline)Creates a linear gradient which starts and ends and the given co-ordinates, and is rotated around therot_x,rot_ycoordinate. Any of the coordinates may either be floats orvariation specifications.The colors on the gradient are specified using aColorLine, as described below:ColorLine(stops,extend="pad")ColorLine(start_stop,end_stop,extend="pad")A color line can be specified using a dictionary mapping positions along the gradient (from 0.0 to 1.0) tostops, where each stop is either acolor_or_colorsor a tuple(color_or_colors, alpha); it may also be specified using two stops, in which case one is taken as the start and the other the end. Hence, the following calls are all equivalent:ColorLine({0.0:("#FF0000FF",1.0),1.0:("#00FF00FF",1.0)})ColorLine({0.0:"#FF0000FF",1.0:"#00FF00FF"})ColorLine("#FF0000FF","#00FF00FF")The alpha value of a stop may vary, to make a variable color line:ALPHA_AXIS={(("ALPH",0.0),):0.0,(("ALPH",1.0),):1.0}PaintLinearGradient((0,0),(1000,1000),(0,1000),ColorLine({0.0:"#FF0000FF",0.5:("#0000FFFF",ALPHA_AXIS),1.0:"#00FF00FF"}))and the position of a stop may also vary, in which case alist of tuplesmust be used as a parameter toColorLineinstead of a dictionary:STOP_AXIS={(("STOP",0.0),):0.0,(("STOP",1.0),):0.8}ALPHA_AXIS={(("ALPH",0.0),):0.0,(("ALPH",1.0),):1.0}PaintLinearGradient((0,0),(1000,1000),(0,1000),ColorLine([(STOP_AXIS,"#FF0000FF"),(1.0,("#00FF00FF",ALPHA_AXIS))]))Theextend modeof aColorLinecan bepad,repeatorreflect.PaintRadialGradient((pt0_x,pt0_y),radius_0,(pt1_x,pt1_y),radius_1,colorline)Creates a radial gradient made up of the cylinder defined by two circles, the first centered atpt0_x, pt0_ywith radiusradius_0and the second centered atpt1_x, pt1_ywith radiusradius_1. Any of the coordinates and either of the radii may either be floats orvariation specifications.PaintSweepGradient((pt_x,pt_y),start_angle,end_angle,colorline)Creates a sweep gradient centered atpt_x, pt_ymade up of the arc betweenstart_angleandend_angle. Any of the coordinates and either of the angles may either be floats orvariation specifications.PaintGlyph(glyph,paint)Uses a (non-color) glyph's outline to mask the drawing of the given paint specification. For example,PaintGlyph("A", PaintSolid("#FF0000FF"))will use the shape of the letter "A" to mask out a solid red paint; the effect of this is to draw the letter in red. ButPaintGlyph("B", PaintGlyph("A", PaintSolid("#FF0000FF"))uses the shape of the letter "B" as a mask to further mask out the painting; the effect of this is to draw the overlap between the A and the B outlines in red.PaintColrGlyph(glyph,paint=None)Re-uses a color glyph's definition, either to paint it, or as a mask to mask another paint.PaintTransform([xx,xy,yx,yy,dx,dy],paint)Transforms the given paint using an affine matrix. Any of the matrix elements may either be floats orvariation specifications.PaintTranslate(dx,dy,paint)Translates the given paint in X and Y dimensions. Any of the translation coordinates may either be floats orvariation specifications.PaintScale(scale_x,scale_y,paint,center=(pt_x,pt_y))PaintScale(scale_x,paint,center=(pt_x,pt_y))PaintScale(scale_x,scale_y,paint)PaintScale(scale_x,paint)Scales the given paint in X and Y dimensions. Any of the scale factors or the center coordinates may either be floats orvariation specifications. Ifscale_yis not specified, a uniform scale is assumed. If the center is not specified, the scaling is performed around the origin.PaintRotate(angle,paint,center=(pt_x,pt_y))PaintRotate(angle,paint)Rotates the paint the given angle. The angle and/or the center coordinates may either be floats orvariation specifications. If the center is not specified, the rotation is performed around the origin.PaintSkew(angle_x,angle_y,paint,center=(pt_x,pt_y))PaintSkew(angle_x,angle_y,paint)Skews the paint the given angles in X and Y dimensions. Any of the angles and/or the center coordinates may either be floats orvariation specifications. If the center is not specified, the skewing is performed around the origin.PaintComposite(mode,src_paint,dst_paint)Composites the source paint onto the destination paint.modemust be one of'clear', 'src', 'dest', 'src_over', 'dest_over', 'src_in', 'dest_in', 'src_out', 'dest_out', 'src_atop', 'dest_atop', 'xor', 'plus', 'screen', 'overlay', 'darken', 'lighten', 'color_dodge', 'color_burn', 'hard_light', 'soft_light', 'difference', 'exclusion', 'multiply', 'hsl_hue', 'hsl_saturation', 'hsl_color', 'hsl_luminosity'.Why not just usefontTools.colorLib.builder?paintcompilerdoes use the fontTools color builder underneath to construct the COLR tables, but it adds a few helpful facilities on top:paintcompilerprovides a command line interface to add COLR tables. This command line interface allows adding synthetic axes.Color palettes are built automatically, so you don't have to assign each color to an index and carry that index around - just specify the color directly. (You can still specify user-selected alternate color palettes by writing the color as an array of options.)Inpaintcompiler, paint operations are functions; this makes the syntax considerably less verbose and easier to follow. ComparePaintColrLayers([PaintGlyph("square",PaintSolid("#FF0000FF")),PaintGlyph("circle",PaintSolid("#0000FFFF"))])versus(ot.PaintFormat.PaintColrLayers,[{"Format":ot.PaintFormat.PaintGlyph,"Paint":{"Format":ot.PaintFormat.PaintSolid,"PaletteIndex":1,"Alpha":1.0,},"Glyph":"square",},{"Format":ot.PaintFormat.PaintGlyph,"Paint":{"Format":ot.PaintFormat.PaintSolid,"PaletteIndex":2,"Alpha":1.0,},"Glyph":"circle",},])colorLib.builderdoes not provide any help when specifying variable paints; you have to keep track of theVarIndexBaseand populate the variation store yourself. Inpaintcompiler, you can specify how a paint varies directly, and the variation table gets built for you.colorLib.builderrequires you to explicitly choose the appropriate paint representation.paintcompileruses the parameters to work out the correct paint format; a singlePaintScalefunction allows access toPaintScale,PaintVarScale,PaintScaleAroundCenter,PaintVarScaleAroundCenter,PaintScaleUniform,PaintVarScaleUniform,PaintScaleUniformAroundCenterandPaintVarScaleUniformAroundCenter. This greatly reduces the number of functions that you need to worry about.