diff --git a/Lib/blackrenderer/__main__.py b/Lib/blackrenderer/__main__.py index 49335eb..8e6e56b 100644 --- a/Lib/blackrenderer/__main__.py +++ b/Lib/blackrenderer/__main__.py @@ -31,6 +31,7 @@ def main(): parser.add_argument("--features", type=parseFeatures) parser.add_argument("--variations", type=parseVariations) parser.add_argument("--margin", type=float, default=20) + parser.add_argument("--palette-index", type=int, default=0) parser.add_argument( "--backend", default=None, @@ -47,6 +48,7 @@ def main(): margin=args.margin, features=args.features, variations=args.variations, + paletteIndex=args.palette_index, backendName=args.backend, ) diff --git a/Lib/blackrenderer/font.py b/Lib/blackrenderer/font.py index 6c772c9..15efa0a 100644 --- a/Lib/blackrenderer/font.py +++ b/Lib/blackrenderer/font.py @@ -97,6 +97,13 @@ def __init__(self, path=None, *, fontNumber=0, lazy=True, ttFont=None, hbFont=No def unitsPerEm(self): return self.hbFont.face.upem + def getPalette(self, paletteIndex): + if not self.palettes: + return None + # clamp index + paletteIndex = max(0, min(paletteIndex, len(self.palettes) - 1)) + return self.palettes[paletteIndex] + def setLocation(self, location): if location is None: location = {} diff --git a/Lib/blackrenderer/render.py b/Lib/blackrenderer/render.py index fecf515..4455311 100644 --- a/Lib/blackrenderer/render.py +++ b/Lib/blackrenderer/render.py @@ -25,6 +25,7 @@ def renderText( margin=20, features=None, variations=None, + paletteIndex=0, backendName=None, lang=None, script=None, @@ -44,6 +45,7 @@ def renderText( buf.language = lang if variations: font.setLocation(variations) + palette = font.getPalette(paletteIndex) hb.shape(font.hbFont, buf, features) @@ -73,7 +75,7 @@ def renderText( for glyph in glyphLine: with canvas.savedState(): canvas.translate(glyph.xOffset, glyph.yOffset) - font.drawGlyph(glyph.name, canvas) + font.drawGlyph(glyph.name, canvas, palette=palette) canvas.translate(glyph.xAdvance, glyph.yAdvance) if outputPath is not None: diff --git a/Tests/data/Nabla.subset.ttf b/Tests/data/Nabla.subset.ttf new file mode 100644 index 0000000..d6356fe Binary files /dev/null and b/Tests/data/Nabla.subset.ttf differ diff --git a/Tests/expectedOutput/glyph_nabla_A_1_cairo.png b/Tests/expectedOutput/glyph_nabla_A_1_cairo.png new file mode 100644 index 0000000..454a1da Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_1_cairo.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_1_coregraphics.png b/Tests/expectedOutput/glyph_nabla_A_1_coregraphics.png new file mode 100644 index 0000000..258bdb6 Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_1_coregraphics.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_1_skia.png b/Tests/expectedOutput/glyph_nabla_A_1_skia.png new file mode 100644 index 0000000..fbaa6d9 Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_1_skia.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_1_svg.svg b/Tests/expectedOutput/glyph_nabla_A_1_svg.svg new file mode 100644 index 0000000..21e136a --- /dev/null +++ b/Tests/expectedOutput/glyph_nabla_A_1_svg.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/expectedOutput/glyph_nabla_A_cairo.png b/Tests/expectedOutput/glyph_nabla_A_cairo.png new file mode 100644 index 0000000..64032a3 Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_cairo.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_coregraphics.png b/Tests/expectedOutput/glyph_nabla_A_coregraphics.png new file mode 100644 index 0000000..4fa0ee7 Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_coregraphics.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_skia.png b/Tests/expectedOutput/glyph_nabla_A_skia.png new file mode 100644 index 0000000..32eb1e6 Binary files /dev/null and b/Tests/expectedOutput/glyph_nabla_A_skia.png differ diff --git a/Tests/expectedOutput/glyph_nabla_A_svg.svg b/Tests/expectedOutput/glyph_nabla_A_svg.svg new file mode 100644 index 0000000..6707c07 --- /dev/null +++ b/Tests/expectedOutput/glyph_nabla_A_svg.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/test_glyph_render.py b/Tests/test_glyph_render.py index cfaebc1..168daaf 100644 --- a/Tests/test_glyph_render.py +++ b/Tests/test_glyph_render.py @@ -29,81 +29,85 @@ "crash": dataDir / "crash.subset.otf", "nested_paintglyph": dataDir / "nested-paintglyph.ttf", "ftvartest": dataDir / "TestVariableCOLR-VF.ttf", + "nabla": dataDir / "Nabla.subset.ttf", } test_glyphs = [ - ("noto", "uni2693", None), - ("noto", "uni2694", None), - ("noto", "u1F30A", None), - ("noto", "u1F943", None), - ("mutator", "B", None), - ("mutator", "D", {"wdth": 1000}), - ("twemoji", "uni3299", None), - ("more_samples", "cross_glyph", None), - ("more_samples", "skew_0_15_center_500.0_500.0", None), - ("more_samples", "skew_-10_20_center_500.0_500.0", None), - ("more_samples", "skew_-10_20_center_1000_1000", None), - ("more_samples", "transform_matrix_1_0_0_1_125_125", None), - ("more_samples", "transform_matrix_1.5_0_0_1.5_0_0", None), - ("more_samples", "transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0", None), - ("more_samples", "transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0", None), - ("more_samples", "clip_box_top_left", None), - ("more_samples", "clip_box_bottom_left", None), - ("more_samples", "clip_box_bottom_right", None), - ("more_samples", "clip_box_top_right", None), - ("more_samples", "clip_box_center", None), - ("more_samples", "composite_DEST_OVER", None), - ("more_samples", "composite_XOR", None), - ("more_samples", "composite_OVERLAY", None), - ("more_samples", "composite_SRC_IN", None), - ("more_samples", "composite_PLUS", None), - ("more_samples", "composite_LIGHTEN", None), - ("more_samples", "composite_MULTIPLY", None), - ("more_samples", "clip_shade_center", None), - ("more_samples", "clip_shade_top_left", None), - ("more_samples", "clip_shade_bottom_left", None), - ("more_samples", "clip_shade_bottom_right", None), - ("more_samples", "clip_shade_top_right", None), - ("more_samples", "inset_clipped_radial_reflect", None), - # ("more_samples", "sweep", None), - ("more_samples", "transformed_sweep", None), - ("more_samples", "composite_colr_glyph", None), - ("more_samples", "linear_repeat_0_1", None), - ("more_samples", "linear_repeat_0.2_0.8", None), - ("more_samples", "linear_repeat_0_1.5", None), - ("more_samples", "linear_repeat_0.5_1.5", None), - ("more_samples", "scale_0.5_1.5_center_500.0_500.0", None), - ("more_samples", "scale_1.5_1.5_center_500.0_500.0", None), - ("more_samples", "scale_0.5_1.5_center_0_0", None), - ("more_samples", "scale_1.5_1.5_center_0_0", None), - ("more_samples", "scale_0.5_1.5_center_1000_1000", None), - ("more_samples", "scale_1.5_1.5_center_1000_1000", None), - ("more_samples", "linear_gradient_extend_mode_pad", None), - ("more_samples", "linear_gradient_extend_mode_repeat", None), - ("more_samples", "linear_gradient_extend_mode_reflect", None), - ("more_samples", "radial_gradient_extend_mode_pad", None), - ("more_samples", "radial_gradient_extend_mode_repeat", None), - ("more_samples", "radial_gradient_extend_mode_reflect", None), - ("more_samples", "rotate_10_center_0_0", None), - ("more_samples", "rotate_-10_center_1000_1000", None), - ("more_samples", "rotate_25_center_500.0_500.0", None), - ("more_samples", "rotate_-15_center_500.0_500.0", None), - ("more_samples", "skew_25_0_center_0_0", None), - ("more_samples", "skew_25_0_center_500.0_500.0", None), - ("more_samples", "skew_0_15_center_0_0", None), - ("more_samples", "upem_box_glyph", None), - ("nested_paintglyph", "A", None), - ("ftvartest", "A", {"wght": 400}), - ("ftvartest", "A", {"wght": 700}), - ("ftvartest", "B", {"wght": 400}), - ("ftvartest", "B", {"wght": 700}), + ("noto", "uni2693", None, 0), + ("noto", "uni2694", None, 0), + ("noto", "u1F30A", None, 0), + ("noto", "u1F943", None, 0), + ("mutator", "B", None, 0), + ("mutator", "D", {"wdth": 1000}, 0), + ("twemoji", "uni3299", None, 0), + ("more_samples", "cross_glyph", None, 0), + ("more_samples", "skew_0_15_center_500.0_500.0", None, 0), + ("more_samples", "skew_-10_20_center_500.0_500.0", None, 0), + ("more_samples", "skew_-10_20_center_1000_1000", None, 0), + ("more_samples", "transform_matrix_1_0_0_1_125_125", None, 0), + ("more_samples", "transform_matrix_1.5_0_0_1.5_0_0", None, 0), + ("more_samples", "transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0", None, 0), + ("more_samples", "transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0", None, 0), + ("more_samples", "clip_box_top_left", None, 0), + ("more_samples", "clip_box_bottom_left", None, 0), + ("more_samples", "clip_box_bottom_right", None, 0), + ("more_samples", "clip_box_top_right", None, 0), + ("more_samples", "clip_box_center", None, 0), + ("more_samples", "composite_DEST_OVER", None, 0), + ("more_samples", "composite_XOR", None, 0), + ("more_samples", "composite_OVERLAY", None, 0), + ("more_samples", "composite_SRC_IN", None, 0), + ("more_samples", "composite_PLUS", None, 0), + ("more_samples", "composite_LIGHTEN", None, 0), + ("more_samples", "composite_MULTIPLY", None, 0), + ("more_samples", "clip_shade_center", None, 0), + ("more_samples", "clip_shade_top_left", None, 0), + ("more_samples", "clip_shade_bottom_left", None, 0), + ("more_samples", "clip_shade_bottom_right", None, 0), + ("more_samples", "clip_shade_top_right", None, 0), + ("more_samples", "inset_clipped_radial_reflect", None, 0), + ("more_samples", "transformed_sweep", None, 0), + ("more_samples", "composite_colr_glyph", None, 0), + ("more_samples", "linear_repeat_0_1", None, 0), + ("more_samples", "linear_repeat_0.2_0.8", None, 0), + ("more_samples", "linear_repeat_0_1.5", None, 0), + ("more_samples", "linear_repeat_0.5_1.5", None, 0), + ("more_samples", "scale_0.5_1.5_center_500.0_500.0", None, 0), + ("more_samples", "scale_1.5_1.5_center_500.0_500.0", None, 0), + ("more_samples", "scale_0.5_1.5_center_0_0", None, 0), + ("more_samples", "scale_1.5_1.5_center_0_0", None, 0), + ("more_samples", "scale_0.5_1.5_center_1000_1000", None, 0), + ("more_samples", "scale_1.5_1.5_center_1000_1000", None, 0), + ("more_samples", "linear_gradient_extend_mode_pad", None, 0), + ("more_samples", "linear_gradient_extend_mode_repeat", None, 0), + ("more_samples", "linear_gradient_extend_mode_reflect", None, 0), + ("more_samples", "radial_gradient_extend_mode_pad", None, 0), + ("more_samples", "radial_gradient_extend_mode_repeat", None, 0), + ("more_samples", "radial_gradient_extend_mode_reflect", None, 0), + ("more_samples", "rotate_10_center_0_0", None, 0), + ("more_samples", "rotate_-10_center_1000_1000", None, 0), + ("more_samples", "rotate_25_center_500.0_500.0", None, 0), + ("more_samples", "rotate_-15_center_500.0_500.0", None, 0), + ("more_samples", "skew_25_0_center_0_0", None, 0), + ("more_samples", "skew_25_0_center_500.0_500.0", None, 0), + ("more_samples", "skew_0_15_center_0_0", None, 0), + ("more_samples", "upem_box_glyph", None, 0), + ("nested_paintglyph", "A", None, 0), + ("ftvartest", "A", {"wght": 400}, 0), + ("ftvartest", "A", {"wght": 700}, 0), + ("ftvartest", "B", {"wght": 400}, 0), + ("ftvartest", "B", {"wght": 700}, 0), + ("nabla", "A", None, 0), + ("nabla", "A", None, 1), ] -@pytest.mark.parametrize("fontName, glyphName, location", test_glyphs) +@pytest.mark.parametrize("fontName, glyphName, location, paletteIndex", test_glyphs) @pytest.mark.parametrize("backendName, surfaceClass", backends) -def test_renderGlyph(backendName, surfaceClass, fontName, glyphName, location): +def test_renderGlyph( + backendName, surfaceClass, fontName, glyphName, location, paletteIndex +): font = BlackRendererFont(testFonts[fontName]) font.setLocation(location) @@ -111,15 +115,20 @@ def test_renderGlyph(backendName, surfaceClass, fontName, glyphName, location): boundingBox = font.getGlyphBounds(glyphName) boundingBox = scaleRect(boundingBox, scaleFactor, scaleFactor) boundingBox = intRect(boundingBox) + palette = font.getPalette(paletteIndex) surface = surfaceClass() ext = surface.fileExtension with surface.canvas(boundingBox) as canvas: canvas.scale(scaleFactor) - font.drawGlyph(glyphName, canvas) + font.drawGlyph(glyphName, canvas, palette=palette) locationString = "_" + _locationToString(location) if location else "" - fileName = f"glyph_{fontName}_{glyphName}{locationString}_{backendName}{ext}" + paletteString = "_" + str(paletteIndex) if paletteIndex else "" + fileName = ( + f"glyph_{fontName}_{glyphName}{locationString}{paletteString}" + f"_{backendName}{ext}" + ) expectedPath = expectedOutputDir / fileName outputPath = tmpOutputDir / fileName surface.saveImage(outputPath)