From bc3aea8a4de51d6bcc9dd714b8b0ee86204b6707 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 20 Jan 2024 21:40:20 +0200 Subject: [PATCH] Start binding hb-paint API --- src/uharfbuzz/_harfbuzz.pyx | 643 ++++++++++++++++++++++++++++++++++++ src/uharfbuzz/charfbuzz.pxd | 1 + 2 files changed, 644 insertions(+) diff --git a/src/uharfbuzz/_harfbuzz.pyx b/src/uharfbuzz/_harfbuzz.pyx index 8644a96..fb45759 100644 --- a/src/uharfbuzz/_harfbuzz.pyx +++ b/src/uharfbuzz/_harfbuzz.pyx @@ -852,6 +852,21 @@ cdef class Font: draw_state_p = PyCapsule_GetPointer(draw_state, NULL) hb_font_draw_glyph(self._hb_font, gid, draw_funcs._hb_drawfuncs, draw_state_p); + def paint_glyph(self, gid: int, + paint_funcs: PaintFuncs, + paint_state: object = None, + palette_index: int = 0, + foreground: int = 0x000000FF) -> None: + cdef void *paint_state_p = paint_state + if PyCapsule_IsValid(paint_state, NULL): + paint_state_p = PyCapsule_GetPointer(paint_state, NULL) + hb_font_paint_glyph(self._hb_font, + gid, + paint_funcs._hb_paintfuncs, + paint_state_p, + palette_index, + foreground) + def draw_glyph_with_pen(self, gid: int, pen): global drawfuncs if drawfuncs == NULL: @@ -1488,6 +1503,634 @@ def ot_math_get_glyph_assembly(font: Font, def ot_font_set_funcs(Font font): hb_ot_font_set_funcs(font._hb_font) + +class PaintCompositeMode(IntEnum): + CLEAR = HB_PAINT_COMPOSITE_MODE_CLEAR + SRC = HB_PAINT_COMPOSITE_MODE_SRC + DEST = HB_PAINT_COMPOSITE_MODE_DEST + SRC_OVER = HB_PAINT_COMPOSITE_MODE_SRC_OVER + DEST_OVER = HB_PAINT_COMPOSITE_MODE_DEST_OVER + SRC_IN = HB_PAINT_COMPOSITE_MODE_SRC_IN + DEST_IN = HB_PAINT_COMPOSITE_MODE_DEST_IN + SRC_OUT = HB_PAINT_COMPOSITE_MODE_SRC_OUT + DEST_OUT = HB_PAINT_COMPOSITE_MODE_DEST_OUT + SRC_ATOP = HB_PAINT_COMPOSITE_MODE_SRC_ATOP + DEST_ATOP = HB_PAINT_COMPOSITE_MODE_DEST_ATOP + XOR = HB_PAINT_COMPOSITE_MODE_XOR + PLUS = HB_PAINT_COMPOSITE_MODE_PLUS + SCREEN = HB_PAINT_COMPOSITE_MODE_SCREEN + OVERLAY = HB_PAINT_COMPOSITE_MODE_OVERLAY + DARKEN = HB_PAINT_COMPOSITE_MODE_DARKEN + LIGHTEN = HB_PAINT_COMPOSITE_MODE_LIGHTEN + COLOR_DODGE = HB_PAINT_COMPOSITE_MODE_COLOR_DODGE + COLOR_BURN = HB_PAINT_COMPOSITE_MODE_COLOR_BURN + HARD_LIGHT = HB_PAINT_COMPOSITE_MODE_HARD_LIGHT + SOFT_LIGHT = HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT + DIFFERENCE = HB_PAINT_COMPOSITE_MODE_DIFFERENCE + EXCLUSION = HB_PAINT_COMPOSITE_MODE_EXCLUSION + MULTIPLY = HB_PAINT_COMPOSITE_MODE_MULTIPLY + HSL_HUE = HB_PAINT_COMPOSITE_MODE_HSL_HUE + HSL_SATURATION = HB_PAINT_COMPOSITE_MODE_HSL_SATURATION + HSL_COLOR = HB_PAINT_COMPOSITE_MODE_HSL_COLOR + HSL_LUMINOSITY = HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY + + +ColorStop = namedtuple("ColorStop", ["offset", "is_foreground", "color"]) + + +class PaintExtend(IntEnum): + PAD = HB_PAINT_EXTEND_PAD + REPEAT = HB_PAINT_EXTEND_REPEAT + REFLECT = HB_PAINT_EXTEND_REFLECT + + +cdef class ColorLine: + cdef hb_color_line_t* _color_line + + def __cinit__(self): + self._color_line = NULL + + @staticmethod + cdef ColorLine from_ptr(hb_color_line_t* color_line): + cdef ColorLine wrapper = ColorLine() + wrapper._color_line = color_line + return wrapper + + + @property + def color_stops(self) -> Sequence[ColorStop]: + if self._color_line is NULL: + return [] + cdef unsigned int stop_count = STATIC_ARRAY_SIZE + cdef hb_color_stop_t stops_array[STATIC_ARRAY_SIZE] + cdef list stops = [] + cdef unsigned int i + cdef unsigned int start_offset = 0 + while stop_count == STATIC_ARRAY_SIZE: + hb_color_line_get_color_stops( + self._color_line, start_offset, &stop_count, stops_array) + for i in range(stop_count): + c_stop = stops_array[i] + stop = ColorStop(c_stop.offset, c_stop.is_foreground, c_stop.color) + stops.append(stop) + start_offset += stop_count + return stops + + @property + def extend(self) -> PaintExtend: + if self._color_line is NULL: + return None + return PaintExtend(hb_color_line_get_extend(self._color_line)) + + +cdef void _paint_push_transform_func( + hb_paint_funcs_t *funcs, + void *paint_data, + float xx, + float yx, + float xy, + float yy, + float dx, + float dy, + void *user_data) noexcept: + f = user_data + f(xx, yx, xy, yy, dx, dy, paint_data) + + +cdef void _paint_pop_transform_func( + hb_paint_funcs_t *funcs, + void *paint_data, + void *user_data) noexcept: + f = user_data + f(paint_data) + + +cdef hb_bool_t _paint_color_glyph_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_codepoint_t glyph, + hb_font_t *font, + void *user_data) noexcept: + f = user_data + py_font = Font.from_ptr(hb_font_reference(font)) + if f(py_font, glyph, paint_data): + return 1 + return 0 + + +cdef void _paint_push_clip_glyph_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_codepoint_t glyph, + hb_font_t *font, + void *user_data) noexcept: + f = user_data + py_font = Font.from_ptr(hb_font_reference(font)) + f(py_font, glyph, paint_data) + + +cdef void _paint_push_clip_rectangle_func( + hb_paint_funcs_t *funcs, + void *paint_data, + float xmin, + float ymin, + float xmax, + float ymax, + void *user_data) noexcept: + f = user_data + f(xmin, ymin, xmax, ymax, paint_data) + + +cdef void _paint_pop_clip_func( + hb_paint_funcs_t *funcs, + void *paint_data, + void *user_data) noexcept: + f = user_data + f(paint_data) + + +cdef void _paint_color_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_bool_t is_foreground, + hb_color_t color, + void *user_data) noexcept: + f = user_data + f(color, is_foreground, paint_data) + + +cdef hb_bool_t _paint_image_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_blob_t *image, + unsigned int width, + unsigned int height, + hb_tag_t format, + float slant, + hb_glyph_extents_t *extents, + void *user_data) noexcept: + f = user_data + py_image = Blob.from_ptr(hb_blob_reference(image)) + py_format = hb_tag_to_string(format, NULL) + py_extents = GlyphExtents(extents.x_bearing, extents.y_bearing, extents.width, extents.height) + if f(py_image, width, height, py_format, slant, py_extents, paint_data): + return 1 + return 0 + + +cdef void _paint_linear_gradient_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_color_line_t *color_line, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2, + void *user_data) noexcept: + f = user_data + py_color_line = ColorLine.from_ptr(color_line) + f(py_color_line, x0, y0, x1, y1, x2, y2, paint_data) + + +cdef void _paint_radial_gradient_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_color_line_t *color_line, + float x0, + float y0, + float r0, + float x1, + float y1, + float r1, + void *user_data) noexcept: + f = user_data + py_color_line = ColorLine.from_ptr(color_line) + f(py_color_line, x0, y0, r0, x1, y1, r1, paint_data) + + +cdef void _paint_sweep_gradient_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_color_line_t *color_line, + float x0, + float y0, + float start_angle, + float end_angle, + void *user_data) noexcept: + f = user_data + py_color_line = ColorLine.from_ptr(color_line) + f(py_color_line, x0, y0, start_angle, end_angle, paint_data) + + +cdef void _paint_push_group_func( + hb_paint_funcs_t *funcs, + void *paint_data, + void *user_data) noexcept: + f = user_data + f(paint_data) + + +cdef void _paint_pop_group_func( + hb_paint_funcs_t *funcs, + void *paint_data, + hb_paint_composite_mode_t mode, + void *user_data) noexcept: + f = user_data + py_mode = PaintCompositeMode(mode) + f(py_mode, paint_data) + + +cdef hb_bool_t _paint_custom_palette_color_func( + hb_paint_funcs_t *funcs, + void *paint_data, + unsigned int color_index, + hb_color_t *color, + void *user_data) noexcept: + f = user_data + py_color = f(color_index, paint_data) + if py_color is not None: + color[0] = py_color + return 1 + return 0 + + +cdef class PaintFuncs: + cdef hb_paint_funcs_t* _hb_paintfuncs + cdef object _push_transform_func + cdef object _pop_transform_func + cdef object _color_glyph_func + cdef object _push_clip_glyph_func + cdef object _push_clip_rectangle_func + cdef object _pop_clip_func + cdef object _color_func + cdef object _image_func + cdef object _linear_gradient_func + cdef object _radial_gradient_func + cdef object _sweep_gradient_func + cdef object _push_group_func + cdef object _pop_group_func + cdef object _custom_palette_color_func + + def __cinit__(self): + self._hb_paintfuncs = hb_paint_funcs_create() + + def __dealloc__(self): + hb_paint_funcs_destroy(self._hb_paintfuncs) + + def set_push_transform_func(self, + func: Callable[[ + float, # xx + float, # yx + float, # xy + float, # yy + float, # dx + float, # dy + object, # paint_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_push_transform_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._push_transform_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._push_transform_func = func + func_p = _paint_push_transform_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_push_transform_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_pop_transform_func(self, + func: Callable[[ + object, # paint_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_pop_transform_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._pop_transform_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._pop_transform_func = func + func_p = _paint_pop_transform_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_pop_transform_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_color_glyph_func(self, + func: Callable[[ + Font, + int, # gid + object, # user_data + ], bool], + user_data: object = None) -> None: + cdef hb_paint_color_glyph_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._color_glyph_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._color_glyph_func = func + func_p = _paint_color_glyph_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_color_glyph_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_push_clip_glyph_func(self, + func: Callable[[ + Font, + int, # gid + object, # user_data + ], bool], + user_data: object = None) -> None: + cdef hb_paint_push_clip_glyph_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._push_clip_glyph_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._push_clip_glyph_func = func + func_p = _paint_push_clip_glyph_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_push_clip_glyph_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_push_clip_rectangle_func(self, + func: Callable[[ + float, # xmin + float, # ymin + float, # xmax + float, # ymax + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_push_clip_rectangle_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._push_clip_rectangle_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._push_clip_rectangle_func = func + func_p = _paint_push_clip_rectangle_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_push_clip_rectangle_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_pop_clip_func(self, + func: Callable[[ + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_pop_clip_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._pop_clip_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._pop_clip_func = func + func_p = _paint_pop_clip_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_pop_clip_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_color_func(self, + func: Callable[[ + int, # color + bool, # is_foreground + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_color_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._color_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._color_func = func + func_p = _paint_color_func + assert user_data is None, "Pass paint_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_color_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_image_func(self, + func: Callable[[ + Blob, # image + int, # width + int, # height + str, # format + float, # slant + GlyphExtents, # extents + object, # user_data + ], bool], + user_data: object = None) -> None: + cdef hb_paint_image_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._image_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._image_func = func + func_p = _paint_image_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_image_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_linear_gradient_func(self, + func: Callable[[ + ColorLine, # color_line + float, # x0 + float, # y0 + float, # x1 + float, # y1 + float, # x2 + float, # y2 + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_linear_gradient_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._linear_gradient_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._linear_gradient_func = func + func_p = _paint_linear_gradient_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_linear_gradient_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_radial_gradient_func(self, + func: Callable[[ + ColorLine, # color_line + float, # x0 + float, # y0 + float, # r0 + float, # x1 + float, # y1 + float, # r1 + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_radial_gradient_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._radial_gradient_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._radial_gradient_func = func + func_p = _paint_radial_gradient_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_radial_gradient_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_sweep_gradient_func(self, + func: Callable[[ + ColorLine, # color_line + float, # x0 + float, # y0 + float, # start_angle + float, # end_angle + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_sweep_gradient_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._sweep_gradient_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._sweep_gradient_func = func + func_p = _paint_sweep_gradient_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_sweep_gradient_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_push_group_func(self, + func: Callable[[ + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_push_group_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._push_group_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._push_group_func = func + func_p = _paint_push_group_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_push_group_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_pop_group_func(self, + func: Callable[[ + PaintCompositeMode, # mode + object, # user_data + ], None], + user_data: object = None) -> None: + cdef hb_paint_pop_group_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._pop_group_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._pop_group_func = func + func_p = _paint_pop_group_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_pop_group_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + def set_custom_palette_color_func(self, + func: Callable[[ + int, # color_index + object, # user_data + ], int | None], + user_data: object = None) -> None: + cdef hb_paint_custom_palette_color_func_t func_p + cdef void *user_data_p + if PyCapsule_IsValid(func, NULL): + self._custom_palette_color_func = None + func_p = PyCapsule_GetPointer(func, NULL) + if PyCapsule_IsValid(user_data, NULL): + user_data_p = PyCapsule_GetPointer(user_data, NULL) + else: + user_data_p = user_data + else: + self._custom_palette_color_func = func + func_p = _paint_custom_palette_color_func + assert user_data is None, "Pass draw_state to Font.paint_glyph" + user_data_p = func + hb_paint_funcs_set_custom_palette_color_func( + self._hb_paintfuncs, func_p, user_data_p, NULL) + + cdef void _move_to_func(hb_draw_funcs_t *dfuncs, void *draw_data, hb_draw_state_t *st, diff --git a/src/uharfbuzz/charfbuzz.pxd b/src/uharfbuzz/charfbuzz.pxd index f0cfb5f..c32a0bb 100644 --- a/src/uharfbuzz/charfbuzz.pxd +++ b/src/uharfbuzz/charfbuzz.pxd @@ -94,6 +94,7 @@ cdef extern from "hb.h": hb_blob_t* hb_blob_create_from_file_or_fail( const char *file_name) + hb_blob_t* hb_blob_reference(hb_blob_t* blob) void hb_blob_destroy(hb_blob_t* blob) const char* hb_blob_get_data(