diff --git a/setup.py b/setup.py index ef1fd31..f5f9f4a 100755 --- a/setup.py +++ b/setup.py @@ -107,6 +107,7 @@ def _configure_extensions_with_vendored_libs() -> List[Extension]: ("HB_NO_MT", "1"), ("HB_EXPERIMENTAL_API", "1"), ("HB_HAS_SUBSET", "1"), + ("HB_HAS_RASTER", "1"), ] extra_compile_args = [] extra_link_args = [] diff --git a/src/uharfbuzz/__init__.py b/src/uharfbuzz/__init__.py index 3afe9ff..6a1aa14 100644 --- a/src/uharfbuzz/__init__.py +++ b/src/uharfbuzz/__init__.py @@ -45,6 +45,11 @@ "PaintCompositeMode", "PaintExtend", "PaintFuncs", + "RasterDraw", + "RasterExtents", + "RasterFormat", + "RasterImage", + "RasterPaint", "RepackerError", "Set", "SetIter", diff --git a/src/uharfbuzz/_blob.pxi b/src/uharfbuzz/_blob.pxi index 0a52ba7..9e8b10c 100644 --- a/src/uharfbuzz/_blob.pxi +++ b/src/uharfbuzz/_blob.pxi @@ -10,7 +10,7 @@ cdef class Blob: @staticmethod cdef Blob from_ptr(hb_blob_t* hb_blob): - """Create Blob from a pointer taking ownership of a it.""" + """Create Blob from a pointer, taking ownership of it.""" cdef Blob wrapper = Blob.__new__(Blob) wrapper._hb_blob = hb_blob diff --git a/src/uharfbuzz/_face.pxi b/src/uharfbuzz/_face.pxi index 022a402..c353e36 100644 --- a/src/uharfbuzz/_face.pxi +++ b/src/uharfbuzz/_face.pxi @@ -141,7 +141,7 @@ cdef class Face: @staticmethod cdef Face from_ptr(hb_face_t* hb_face): - """Create Face from a pointer taking ownership of a it.""" + """Create Face from a pointer, taking ownership of it.""" cdef Face wrapper = Face.__new__(Face) wrapper._hb_face = hb_face diff --git a/src/uharfbuzz/_font.pxi b/src/uharfbuzz/_font.pxi index 0431184..5a8b7c1 100644 --- a/src/uharfbuzz/_font.pxi +++ b/src/uharfbuzz/_font.pxi @@ -167,7 +167,7 @@ cdef class Font: @staticmethod cdef Font from_ptr(hb_font_t* hb_font): - """Create Font from a pointer taking ownership of a it.""" + """Create Font from a pointer, taking ownership of it.""" cdef Font wrapper = Font.__new__(Font) wrapper._hb_font = hb_font diff --git a/src/uharfbuzz/_harfbuzz.pyx b/src/uharfbuzz/_harfbuzz.pyx index f7f0f9a..83a03c0 100644 --- a/src/uharfbuzz/_harfbuzz.pyx +++ b/src/uharfbuzz/_harfbuzz.pyx @@ -68,6 +68,7 @@ include "_face.pxi" include "_draw.pxi" include "_paint.pxi" include "_font.pxi" +include "_raster.pxi" include "_serialize.pxi" include "_subset.pxi" diff --git a/src/uharfbuzz/_map.pxi b/src/uharfbuzz/_map.pxi index 8005147..9dea30b 100644 --- a/src/uharfbuzz/_map.pxi +++ b/src/uharfbuzz/_map.pxi @@ -3,7 +3,10 @@ cdef class Map: INVALID_VALUE = HB_MAP_VALUE_INVALID - def __cinit__(self, init = dict()): + def __cinit__(self, *args, **kwargs): + self._hb_map = NULL + + def __init__(self, init = dict()): self._hb_map = hb_map_create() if not hb_map_allocation_successful(self._hb_map): raise MemoryError() @@ -15,7 +18,7 @@ cdef class Map: @staticmethod cdef Map from_ptr(hb_map_t* hb_map): - """Create Map from a pointer taking ownership of a it.""" + """Create Map from a pointer, taking ownership of it.""" cdef Map wrapper = Map.__new__(Map) wrapper._hb_map = hb_map diff --git a/src/uharfbuzz/_raster.pxi b/src/uharfbuzz/_raster.pxi new file mode 100644 index 0000000..88aae89 --- /dev/null +++ b/src/uharfbuzz/_raster.pxi @@ -0,0 +1,272 @@ +class RasterFormat(IntEnum): + A8 = HB_RASTER_FORMAT_A8 + BGRA32 = HB_RASTER_FORMAT_BGRA32 + + +class RasterExtents(NamedTuple): + x_origin: int = 0 + y_origin: int = 0 + width: int = 0 + height: int = 0 + stride: int = 0 + + +cdef class RasterImage: + cdef hb_raster_image_t* _hb_raster_image + + def __cinit__(self): + self._hb_raster_image = NULL + + def __init__(self): + self._hb_raster_image = hb_raster_image_create_or_fail() + if self._hb_raster_image is NULL: + raise MemoryError() + + def __dealloc__(self): + hb_raster_image_destroy(self._hb_raster_image) + + @staticmethod + cdef RasterImage from_ptr(hb_raster_image_t* hb_img): + """Create RasterImage from a pointer, taking ownership of it.""" + cdef RasterImage wrapper = RasterImage.__new__(RasterImage) + wrapper._hb_raster_image = hb_img + return wrapper + + def configure(self, format: RasterFormat, + extents: RasterExtents | None = None) -> bool: + cdef hb_raster_extents_t c_extents + cdef hb_raster_extents_t* c_extents_ptr = NULL + if extents is not None: + c_extents.x_origin = extents.x_origin + c_extents.y_origin = extents.y_origin + c_extents.width = extents.width + c_extents.height = extents.height + c_extents.stride = extents.stride + c_extents_ptr = &c_extents + return hb_raster_image_configure(self._hb_raster_image, format, c_extents_ptr) + + def clear(self): + hb_raster_image_clear(self._hb_raster_image) + + @property + def format(self) -> RasterFormat: + return RasterFormat(hb_raster_image_get_format(self._hb_raster_image)) + + @property + def extents(self) -> RasterExtents: + cdef hb_raster_extents_t c_extents + hb_raster_image_get_extents(self._hb_raster_image, &c_extents) + return RasterExtents( + x_origin=c_extents.x_origin, + y_origin=c_extents.y_origin, + width=c_extents.width, + height=c_extents.height, + stride=c_extents.stride, + ) + + @property + def buffer(self) -> bytes: + cdef hb_raster_extents_t c_extents + hb_raster_image_get_extents(self._hb_raster_image, &c_extents) + cdef const uint8_t* buf = hb_raster_image_get_buffer(self._hb_raster_image) + if buf is NULL: + return b"" + cdef Py_ssize_t size = c_extents.height * c_extents.stride + return buf[:size] + + +cdef class RasterDraw: + cdef hb_raster_draw_t* _hb_raster_draw + + def __cinit__(self): + self._hb_raster_draw = hb_raster_draw_create_or_fail() + if self._hb_raster_draw is NULL: + raise MemoryError() + + def __dealloc__(self): + hb_raster_draw_destroy(self._hb_raster_draw) + + @property + def transform(self) -> Tuple[float, float, float, float, float, float]: + cdef float xx, yx, xy, yy, dx, dy + hb_raster_draw_get_transform(self._hb_raster_draw, &xx, &yx, &xy, &yy, &dx, &dy) + return (xx, yx, xy, yy, dx, dy) + + @transform.setter + def transform(self, value: Tuple[float, float, float, float, float, float]): + xx, yx, xy, yy, dx, dy = value + hb_raster_draw_set_transform(self._hb_raster_draw, xx, yx, xy, yy, dx, dy) + + @property + def scale_factor(self) -> Tuple[float, float]: + cdef float x, y + hb_raster_draw_get_scale_factor(self._hb_raster_draw, &x, &y) + return (x, y) + + @scale_factor.setter + def scale_factor(self, value: Tuple[float, float]): + x, y = value + hb_raster_draw_set_scale_factor(self._hb_raster_draw, x, y) + + @property + def extents(self) -> RasterExtents | None: + cdef hb_raster_extents_t c_extents + if not hb_raster_draw_get_extents(self._hb_raster_draw, &c_extents): + return None + return RasterExtents( + x_origin=c_extents.x_origin, + y_origin=c_extents.y_origin, + width=c_extents.width, + height=c_extents.height, + stride=c_extents.stride, + ) + + @extents.setter + def extents(self, value: RasterExtents): + cdef hb_raster_extents_t c_extents + c_extents.x_origin = value.x_origin + c_extents.y_origin = value.y_origin + c_extents.width = value.width + c_extents.height = value.height + c_extents.stride = value.stride + hb_raster_draw_set_extents(self._hb_raster_draw, &c_extents) + + def set_glyph_extents(self, extents: GlyphExtents) -> bool: + cdef hb_glyph_extents_t c_extents + c_extents.x_bearing = extents.x_bearing + c_extents.y_bearing = extents.y_bearing + c_extents.width = extents.width + c_extents.height = extents.height + return hb_raster_draw_set_glyph_extents(self._hb_raster_draw, &c_extents) + + def draw_glyph(self, font: Font, glyph: int): + hb_raster_draw_glyph(self._hb_raster_draw, font._hb_font, glyph) + + def draw_glyph_or_fail(self, font: Font, glyph: int) -> bool: + return hb_raster_draw_glyph_or_fail(self._hb_raster_draw, font._hb_font, glyph) + + def render(self) -> RasterImage | None: + cdef hb_raster_image_t* img = hb_raster_draw_render(self._hb_raster_draw) + if img is NULL: + return None + return RasterImage.from_ptr(img) + + def clear(self): + hb_raster_draw_clear(self._hb_raster_draw) + + def reset(self): + hb_raster_draw_reset(self._hb_raster_draw) + + +cdef class RasterPaint: + cdef hb_raster_paint_t* _hb_raster_paint + + def __cinit__(self): + self._hb_raster_paint = hb_raster_paint_create_or_fail() + if self._hb_raster_paint is NULL: + raise MemoryError() + + def __dealloc__(self): + hb_raster_paint_destroy(self._hb_raster_paint) + + @property + def transform(self) -> Tuple[float, float, float, float, float, float]: + cdef float xx, yx, xy, yy, dx, dy + hb_raster_paint_get_transform(self._hb_raster_paint, &xx, &yx, &xy, &yy, &dx, &dy) + return (xx, yx, xy, yy, dx, dy) + + @transform.setter + def transform(self, value: Tuple[float, float, float, float, float, float]): + xx, yx, xy, yy, dx, dy = value + hb_raster_paint_set_transform(self._hb_raster_paint, xx, yx, xy, yy, dx, dy) + + @property + def scale_factor(self) -> Tuple[float, float]: + cdef float x, y + hb_raster_paint_get_scale_factor(self._hb_raster_paint, &x, &y) + return (x, y) + + @scale_factor.setter + def scale_factor(self, value: Tuple[float, float]): + x, y = value + hb_raster_paint_set_scale_factor(self._hb_raster_paint, x, y) + + @property + def extents(self) -> RasterExtents | None: + cdef hb_raster_extents_t c_extents + if not hb_raster_paint_get_extents(self._hb_raster_paint, &c_extents): + return None + return RasterExtents( + x_origin=c_extents.x_origin, + y_origin=c_extents.y_origin, + width=c_extents.width, + height=c_extents.height, + stride=c_extents.stride, + ) + + @extents.setter + def extents(self, value: RasterExtents): + cdef hb_raster_extents_t c_extents + c_extents.x_origin = value.x_origin + c_extents.y_origin = value.y_origin + c_extents.width = value.width + c_extents.height = value.height + c_extents.stride = value.stride + hb_raster_paint_set_extents(self._hb_raster_paint, &c_extents) + + def set_glyph_extents(self, extents: GlyphExtents) -> bool: + cdef hb_glyph_extents_t c_extents + c_extents.x_bearing = extents.x_bearing + c_extents.y_bearing = extents.y_bearing + c_extents.width = extents.width + c_extents.height = extents.height + return hb_raster_paint_set_glyph_extents(self._hb_raster_paint, &c_extents) + + @property + def foreground(self) -> Color: + return Color.from_int(hb_raster_paint_get_foreground(self._hb_raster_paint)) + + @foreground.setter + def foreground(self, value: Color): + hb_raster_paint_set_foreground(self._hb_raster_paint, value.to_int()) + + @property + def background(self) -> Color: + return Color.from_int(hb_raster_paint_get_background(self._hb_raster_paint)) + + @background.setter + def background(self, value: Color): + hb_raster_paint_set_background(self._hb_raster_paint, value.to_int()) + + @property + def palette(self) -> int: + return hb_raster_paint_get_palette(self._hb_raster_paint) + + @palette.setter + def palette(self, value: int): + hb_raster_paint_set_palette(self._hb_raster_paint, value) + + def set_custom_palette_color(self, color_index: int, color: Color) -> bool: + return hb_raster_paint_set_custom_palette_color( + self._hb_raster_paint, color_index, color.to_int()) + + def clear_custom_palette_colors(self): + hb_raster_paint_clear_custom_palette_colors(self._hb_raster_paint) + + def paint_glyph(self, font: Font, glyph: int): + hb_raster_paint_glyph(self._hb_raster_paint, font._hb_font, glyph) + + def paint_glyph_or_fail(self, font: Font, glyph: int) -> bool: + return hb_raster_paint_glyph_or_fail(self._hb_raster_paint, font._hb_font, glyph) + + def render(self) -> RasterImage | None: + cdef hb_raster_image_t* img = hb_raster_paint_render(self._hb_raster_paint) + if img is NULL: + return None + return RasterImage.from_ptr(img) + + def clear(self): + hb_raster_paint_clear(self._hb_raster_paint) + + def reset(self): + hb_raster_paint_reset(self._hb_raster_paint) diff --git a/src/uharfbuzz/_set.pxi b/src/uharfbuzz/_set.pxi index eabd7db..d47805c 100644 --- a/src/uharfbuzz/_set.pxi +++ b/src/uharfbuzz/_set.pxi @@ -3,7 +3,10 @@ cdef class Set: INVALID_VALUE = HB_SET_VALUE_INVALID - def __cinit__(self, init = set()): + def __cinit__(self, *args, **kwargs): + self._hb_set = NULL + + def __init__(self, init = set()): self._hb_set = hb_set_create() if not hb_set_allocation_successful(self._hb_set): raise MemoryError() @@ -15,7 +18,7 @@ cdef class Set: @staticmethod cdef Set from_ptr(hb_set_t* hb_set): - """Create Set from a pointer taking ownership of a it.""" + """Create Set from a pointer, taking ownership of it.""" cdef Set wrapper = Set.__new__(Set) wrapper._hb_set = hb_set diff --git a/src/uharfbuzz/charfbuzz.pxd b/src/uharfbuzz/charfbuzz.pxd index 63bedf5..fac7f48 100644 --- a/src/uharfbuzz/charfbuzz.pxd +++ b/src/uharfbuzz/charfbuzz.pxd @@ -1409,3 +1409,117 @@ cdef extern from "hb-subset.h": hb_subset_plan_t* hb_subset_plan_reference(hb_subset_plan_t* plan) hb_bool_t hb_subset_plan_set_user_data(hb_subset_plan_t* plan, hb_user_data_key_t* key, void* data, hb_destroy_func_t destroy, hb_bool_t replace) void* hb_subset_plan_get_user_data(const hb_subset_plan_t* plan, hb_user_data_key_t* key) + + +cdef extern from "hb-raster.h": + + ctypedef enum hb_raster_format_t: + HB_RASTER_FORMAT_A8 + HB_RASTER_FORMAT_BGRA32 + + ctypedef struct hb_raster_extents_t: + int x_origin + int y_origin + unsigned int width + unsigned int height + unsigned int stride + + ctypedef struct hb_raster_image_t: + pass + ctypedef struct hb_raster_draw_t: + pass + ctypedef struct hb_raster_paint_t: + pass + + # Image + hb_raster_image_t* hb_raster_image_create_or_fail() + hb_raster_image_t* hb_raster_image_reference(hb_raster_image_t* image) + void hb_raster_image_destroy(hb_raster_image_t* image) + hb_bool_t hb_raster_image_configure( + hb_raster_image_t* image, + hb_raster_format_t format, + const hb_raster_extents_t* extents) + void hb_raster_image_clear(hb_raster_image_t* image) + const uint8_t* hb_raster_image_get_buffer(const hb_raster_image_t* image) + void hb_raster_image_get_extents( + const hb_raster_image_t* image, + hb_raster_extents_t* extents) + hb_raster_format_t hb_raster_image_get_format(const hb_raster_image_t* image) + hb_bool_t hb_raster_image_deserialize_from_png_or_fail( + hb_raster_image_t* image, + hb_blob_t* png) + hb_blob_t* hb_raster_image_serialize_to_png_or_fail(const hb_raster_image_t* image) + + # Draw + hb_raster_draw_t* hb_raster_draw_create_or_fail() + hb_raster_draw_t* hb_raster_draw_reference(hb_raster_draw_t* draw) + void hb_raster_draw_destroy(hb_raster_draw_t* draw) + void hb_raster_draw_set_transform( + hb_raster_draw_t* draw, + float xx, float yx, float xy, float yy, float dx, float dy) + void hb_raster_draw_get_transform( + const hb_raster_draw_t* draw, + float* xx, float* yx, float* xy, float* yy, float* dx, float* dy) + void hb_raster_draw_set_scale_factor( + hb_raster_draw_t* draw, float x_scale_factor, float y_scale_factor) + void hb_raster_draw_get_scale_factor( + const hb_raster_draw_t* draw, float* x_scale_factor, float* y_scale_factor) + void hb_raster_draw_set_extents( + hb_raster_draw_t* draw, const hb_raster_extents_t* extents) + hb_bool_t hb_raster_draw_get_extents( + const hb_raster_draw_t* draw, hb_raster_extents_t* extents) + hb_bool_t hb_raster_draw_set_glyph_extents( + hb_raster_draw_t* draw, const hb_glyph_extents_t* glyph_extents) + hb_draw_funcs_t* hb_raster_draw_get_funcs(const hb_raster_draw_t* draw) + void hb_raster_draw_glyph( + hb_raster_draw_t* draw, hb_font_t* font, hb_codepoint_t glyph) + hb_bool_t hb_raster_draw_glyph_or_fail( + hb_raster_draw_t* draw, hb_font_t* font, hb_codepoint_t glyph) + hb_raster_image_t* hb_raster_draw_render(hb_raster_draw_t* draw) + void hb_raster_draw_clear(hb_raster_draw_t* draw) + void hb_raster_draw_reset(hb_raster_draw_t* draw) + void hb_raster_draw_recycle_image( + hb_raster_draw_t* draw, hb_raster_image_t* image) + + # Paint + hb_raster_paint_t* hb_raster_paint_create_or_fail() + hb_raster_paint_t* hb_raster_paint_reference(hb_raster_paint_t* paint) + void hb_raster_paint_destroy(hb_raster_paint_t* paint) + void hb_raster_paint_set_transform( + hb_raster_paint_t* paint, + float xx, float yx, float xy, float yy, float dx, float dy) + void hb_raster_paint_get_transform( + const hb_raster_paint_t* paint, + float* xx, float* yx, float* xy, float* yy, float* dx, float* dy) + void hb_raster_paint_set_scale_factor( + hb_raster_paint_t* paint, float x_scale_factor, float y_scale_factor) + void hb_raster_paint_get_scale_factor( + const hb_raster_paint_t* paint, float* x_scale_factor, float* y_scale_factor) + void hb_raster_paint_set_extents( + hb_raster_paint_t* paint, const hb_raster_extents_t* extents) + hb_bool_t hb_raster_paint_get_extents( + const hb_raster_paint_t* paint, hb_raster_extents_t* extents) + hb_bool_t hb_raster_paint_set_glyph_extents( + hb_raster_paint_t* paint, const hb_glyph_extents_t* glyph_extents) + void hb_raster_paint_set_foreground( + hb_raster_paint_t* paint, hb_color_t foreground) + hb_color_t hb_raster_paint_get_foreground(const hb_raster_paint_t* paint) + void hb_raster_paint_set_background( + hb_raster_paint_t* paint, hb_color_t background) + hb_color_t hb_raster_paint_get_background(const hb_raster_paint_t* paint) + void hb_raster_paint_set_palette( + hb_raster_paint_t* paint, unsigned int palette) + unsigned int hb_raster_paint_get_palette(const hb_raster_paint_t* paint) + void hb_raster_paint_clear_custom_palette_colors(hb_raster_paint_t* paint) + hb_bool_t hb_raster_paint_set_custom_palette_color( + hb_raster_paint_t* paint, unsigned int color_index, hb_color_t color) + hb_paint_funcs_t* hb_raster_paint_get_funcs(const hb_raster_paint_t* paint) + void hb_raster_paint_glyph( + hb_raster_paint_t* paint, hb_font_t* font, hb_codepoint_t glyph) + hb_bool_t hb_raster_paint_glyph_or_fail( + hb_raster_paint_t* paint, hb_font_t* font, hb_codepoint_t glyph) + hb_raster_image_t* hb_raster_paint_render(hb_raster_paint_t* paint) + void hb_raster_paint_clear(hb_raster_paint_t* paint) + void hb_raster_paint_reset(hb_raster_paint_t* paint) + void hb_raster_paint_recycle_image( + hb_raster_paint_t* paint, hb_raster_image_t* image) diff --git a/tests/test_raster.py b/tests/test_raster.py new file mode 100644 index 0000000..04894b4 --- /dev/null +++ b/tests/test_raster.py @@ -0,0 +1,131 @@ +from pathlib import Path + +import pytest + +import uharfbuzz as hb + + +TESTDATA = Path(__file__).parent / "data" +OPENSANS_TTF = TESTDATA / "OpenSans.subset.ttf" +COLORV1_TTF = TESTDATA / "test_glyphs-glyf_colr_1.ttf" + + +@pytest.fixture +def font(): + blob = hb.Blob(OPENSANS_TTF.read_bytes()) + return hb.Font(hb.Face(blob)) + + +@pytest.fixture +def colorv1font(): + blob = hb.Blob(COLORV1_TTF.read_bytes()) + return hb.Font(hb.Face(blob)) + + +class TestRasterImage: + def test_configure(self): + img = hb.RasterImage() + ok = img.configure( + hb.RasterFormat.A8, + hb.RasterExtents(width=16, height=16), + ) + assert ok + assert img.format is hb.RasterFormat.A8 + assert img.extents.width == 16 + assert img.extents.height == 16 + assert img.extents.stride >= 16 + assert len(img.buffer) == img.extents.height * img.extents.stride + + +class TestRasterDraw: + def test_render_a8(self, font): + gid = 1 + draw = hb.RasterDraw() + draw.scale_factor = (1.0, 1.0) + assert draw.set_glyph_extents(font.get_glyph_extents(gid)) + draw.draw_glyph(font, gid) + image = draw.render() + assert image.format is hb.RasterFormat.A8 + assert image.extents.width > 0 + assert image.extents.height > 0 + assert len(image.buffer) == image.extents.height * image.extents.stride + assert any(b != 0 for b in image.buffer) + + def test_transform_roundtrip(self): + draw = hb.RasterDraw() + draw.transform = (2.0, 0.0, 0.0, 2.0, 10.0, 20.0) + assert draw.transform == (2.0, 0.0, 0.0, 2.0, 10.0, 20.0) + + def test_scale_factor_roundtrip(self): + draw = hb.RasterDraw() + draw.scale_factor = (1.5, 2.5) + assert draw.scale_factor == (1.5, 2.5) + + def test_extents_roundtrip(self): + draw = hb.RasterDraw() + assert draw.extents is None + draw.extents = hb.RasterExtents(0, 0, 32, 32, 0) + assert draw.extents is not None + assert draw.extents.width == 32 + assert draw.extents.height == 32 + + def test_clear_and_reset(self, font): + gid = 1 + draw = hb.RasterDraw() + draw.scale_factor = (1.0, 1.0) + draw.set_glyph_extents(font.get_glyph_extents(gid)) + draw.draw_glyph(font, gid) + draw.clear() + draw.reset() + image = draw.render() + assert isinstance(image, hb.RasterImage) + + +class TestRasterPaint: + def test_render_bgra32(self, colorv1font): + gid = 10 + paint = hb.RasterPaint() + paint.scale_factor = (1.0, 1.0) + paint.set_glyph_extents(colorv1font.get_glyph_extents(gid)) + paint.paint_glyph(colorv1font, gid) + image = paint.render() + assert image.format is hb.RasterFormat.BGRA32 + assert image.extents.width > 0 + assert image.extents.height > 0 + assert len(image.buffer) == image.extents.height * image.extents.stride + + def test_foreground_roundtrip(self): + paint = hb.RasterPaint() + paint.foreground = hb.Color(red=10, green=20, blue=30, alpha=40) + color = paint.foreground + assert (color.red, color.green, color.blue, color.alpha) == (10, 20, 30, 40) + + def test_background_roundtrip(self): + paint = hb.RasterPaint() + paint.background = hb.Color(red=0, green=0, blue=0, alpha=255) + color = paint.background + assert color.alpha == 255 + + def test_palette_roundtrip(self): + paint = hb.RasterPaint() + paint.palette = 2 + assert paint.palette == 2 + + def test_custom_palette_color(self): + paint = hb.RasterPaint() + assert paint.set_custom_palette_color(0, hb.Color(255, 0, 0, 255)) + paint.clear_custom_palette_colors() + + def test_paint_glyph_or_fail(self, font): + # OpenSans glyphs are monochrome + gid = 1 + paint = hb.RasterPaint() + paint.scale_factor = (1.0, 1.0) + paint.set_glyph_extents(font.get_glyph_extents(gid)) + # _or_fail variant signals "no color paint data" via False + assert not paint.paint_glyph_or_fail(font, gid) + # plain variant falls back to outline drawing + paint.paint_glyph(font, gid) + image = paint.render() + assert image is not None + assert image.format is hb.RasterFormat.BGRA32