/* * Copyright © 2026 Behdad Esfahbod * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * Author(s): Behdad Esfahbod */ #include "hb.hh" #include "hb-raster-paint.hh" #include "hb-machinery.hh" #include "hb-paint.hh" #include /* * Pixel helpers (paint-specific) */ /* Convert unpremultiplied hb_color_t (BGRA order) to premultiplied BGRA32 pixel. */ static inline uint32_t color_to_premul_pixel (hb_color_t color) { uint8_t a = hb_color_get_alpha (color); uint8_t r = hb_raster_div255 (hb_color_get_red (color) * a); uint8_t g = hb_raster_div255 (hb_color_get_green (color) * a); uint8_t b = hb_raster_div255 (hb_color_get_blue (color) * a); return (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16) | ((uint32_t) a << 24); } /* Bilinear sample from a premultiplied BGRA32 image. */ static inline uint32_t hb_raster_sample_bilinear_premul (const hb_packed_t *src, unsigned width, unsigned height, float x, float y) { int x0 = (int) floorf (x); int y0 = (int) floorf (y); int x1 = x0 + 1; int y1 = y0 + 1; if (x1 >= (int) width) x1 = (int) width - 1; if (y1 >= (int) height) y1 = (int) height - 1; float tx = x - x0; float ty = y - y0; float omtx = 1.f - tx; float omty = 1.f - ty; uint32_t p00 = (uint32_t) src[(size_t) y0 * width + (size_t) x0]; uint32_t p10 = (uint32_t) src[(size_t) y0 * width + (size_t) x1]; uint32_t p01 = (uint32_t) src[(size_t) y1 * width + (size_t) x0]; uint32_t p11 = (uint32_t) src[(size_t) y1 * width + (size_t) x1]; float w00 = omtx * omty; float w10 = tx * omty; float w01 = omtx * ty; float w11 = tx * ty; float b = ((p00 >> 0) & 0xff) * w00 + ((p10 >> 0) & 0xff) * w10 + ((p01 >> 0) & 0xff) * w01 + ((p11 >> 0) & 0xff) * w11; float g = ((p00 >> 8) & 0xff) * w00 + ((p10 >> 8) & 0xff) * w10 + ((p01 >> 8) & 0xff) * w01 + ((p11 >> 8) & 0xff) * w11; float r = ((p00 >> 16) & 0xff) * w00 + ((p10 >> 16) & 0xff) * w10 + ((p01 >> 16) & 0xff) * w01 + ((p11 >> 16) & 0xff) * w11; float a = ((p00 >> 24) & 0xff) * w00 + ((p10 >> 24) & 0xff) * w10 + ((p01 >> 24) & 0xff) * w01 + ((p11 >> 24) & 0xff) * w11; return (uint32_t) (b + 0.5f) | ((uint32_t) (g + 0.5f) << 8) | ((uint32_t) (r + 0.5f) << 16) | ((uint32_t) (a + 0.5f) << 24); } /* * Paint callbacks */ /* Lazy initialization: set up root surface, initial clip and transform. * Called from every paint callback that needs state. * hb_font_paint_glyph() does NOT wrap with push/pop_transform, * so the first callback could be push_clip_glyph or paint_color. */ static void ensure_initialized (hb_raster_paint_t *c) { if (c->surface_stack.length) return; /* A failed stack push (OOM) leaves the vector in a sticky error * state, so initialization can never succeed; bail out cheaply * instead of re-paying the surface acquire-and-clear below on * every subsequent paint callback. */ if (unlikely (c->surface_stack.in_error () || c->clip_stack.in_error ())) return; /* acquire_surface() clears a full surface; charge its area, so * repeated failed initialization attempts cannot re-pay it * indefinitely. Successful initialization re-arms the session * budget below, making the charge a no-op for normal sessions. */ if (unlikely (!c->charge_work ((int64_t) c->fixed_extents.width * c->fixed_extents.height))) return; /* Root surface */ hb_raster_image_t *root = c->acquire_surface (); if (unlikely (!root)) return; if (hb_color_get_alpha (c->background)) { uint32_t bg = HB_COLOR (hb_color_get_blue (c->background), hb_color_get_green (c->background), hb_color_get_red (c->background), hb_color_get_alpha (c->background)); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" uint32_t *pixels = (uint32_t *) hb_raster_image_get_buffer (root); #pragma GCC diagnostic pop hb_raster_extents_t ext; hb_raster_image_get_extents (root, &ext); unsigned count = ext.width * ext.height; for (unsigned i = 0; i < count; i++) pixels[i] = bg; } if (unlikely (!c->surface_stack.push_or_fail (root))) { c->release_surface (root); return; } /* Resolve the surface-sized pixel budget now that the surface size is * known. An unlimited policy disables the pixel budget, so leave it. * The outline budget is re-resolved to the same value it already holds * (nothing has been drawn yet). */ if (c->budget != HB_BUDGET_UNLIMITED) c->recharge_budget (); /* Initial clip: full coverage rectangle */ hb_raster_clip_t clip; clip.init_full (c->fixed_extents.width, c->fixed_extents.height); if (unlikely (!c->clip_stack.push_or_fail (std::move (clip)))) { c->release_surface (c->surface_stack.pop ()); return; } } static void hb_raster_paint_push_transform (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, float xx, float yx, float xy, float yy, float dx, float dy, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_transform_t<> t = c->current_transform (); t.multiply ({xx, yx, xy, yy, dx, dy}); (void) c->transform_stack.push (t); } static void hb_raster_paint_pop_transform (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; if (c->transform_stack.length) c->transform_stack.pop (); } static hb_bool_t hb_raster_paint_color_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data HB_UNUSED, hb_codepoint_t glyph HB_UNUSED, hb_font_t *font HB_UNUSED, void *user_data HB_UNUSED) { return false; } typedef void (*hb_raster_paint_clip_mask_emit_t) (hb_raster_draw_t *rdr, void *user_data); /* Attach the surface box to @rdr and seed its outline budget before * outlines are drawn into it: curves outside the surface collapse to * their chord, and outline traversal and curve flattening charge the * seeded budget. hb_raster_paint_readback_budget() pulls the remainder * back into the session afterwards. */ static void hb_raster_paint_attach_clip_rdr (hb_raster_paint_t *c, hb_raster_draw_t *rdr, const hb_raster_image_t *surf) { hb_raster_draw_set_clip_box (rdr, (float) surf->extents.x_origin, (float) surf->extents.y_origin, (float) surf->extents.x_origin + (float) surf->extents.width, (float) surf->extents.y_origin + (float) surf->extents.height); hb_draw_set_budget (hb_raster_draw_get_funcs (rdr), rdr, c->budget_remaining); } /* Pull the outline budget the rasterizer has consumed back into the * session, so it aggregates across every glyph in the paint walk. */ static void hb_raster_paint_readback_budget (hb_raster_paint_t *c, hb_raster_draw_t *rdr) { c->budget_remaining = hb_draw_get_budget_remaining (hb_raster_draw_get_funcs (rdr), rdr); } static void hb_raster_paint_push_empty_clip (hb_raster_paint_t *c, unsigned w, unsigned h) { hb_raster_clip_t new_clip = c->acquire_clip (w, h); new_clip.init_full (w, h); new_clip.is_rect = true; new_clip.rect_x0 = new_clip.rect_y0 = 0; new_clip.rect_x1 = new_clip.rect_y1 = 0; new_clip.min_x = new_clip.min_y = new_clip.max_x = new_clip.max_y = 0; (void) c->clip_stack.push (std::move (new_clip)); } static bool hb_raster_paint_intersect_mask (const hb_raster_image_t *surf, const hb_raster_extents_t &mask_ext, const hb_raster_clip_t &clip, unsigned w, unsigned h, int64_t *mask_x0, int64_t *mask_y0, unsigned *ix0, unsigned *iy0, unsigned *ix1, unsigned *iy1) { /* Convert mask extents from surface coordinates to clip-buffer coordinates. */ int64_t mx0 = (int64_t) mask_ext.x_origin - (int64_t) surf->extents.x_origin; int64_t my0 = (int64_t) mask_ext.y_origin - (int64_t) surf->extents.y_origin; int64_t x0 = hb_max ((int64_t) clip.min_x, hb_max (mx0, (int64_t) 0)); int64_t y0 = hb_max ((int64_t) clip.min_y, hb_max (my0, (int64_t) 0)); int64_t x1 = hb_min ((int64_t) clip.max_x, hb_min (mx0 + mask_ext.width, (int64_t) w)); int64_t y1 = hb_min ((int64_t) clip.max_y, hb_min (my0 + mask_ext.height, (int64_t) h)); if (x0 >= x1 || y0 >= y1) return false; *mask_x0 = mx0; *mask_y0 = my0; *ix0 = (unsigned) x0; *iy0 = (unsigned) y0; *ix1 = (unsigned) x1; *iy1 = (unsigned) y1; return true; } /* Render whatever edges have been accumulated into @rdr and * push the result as a new clip on the stack, intersecting * with the existing clip. Used by the emitter-based clip * helpers (push_clip_glyph) and by push_clip_path_end once * the caller has drawn the path into @rdr. */ static void hb_raster_paint_finalize_path_clip (hb_raster_paint_t *c, hb_raster_draw_t *rdr, hb_raster_image_t *surf, unsigned w, unsigned h) { if (unlikely (!c->precharge_work (hb_raster_draw_get_pixel_work (rdr, h, w)))) { hb_raster_draw_clear (rdr); hb_raster_paint_push_empty_clip (c, w, h); return; } hb_raster_image_t *mask_img = hb_raster_draw_render (rdr); if (unlikely (!mask_img)) { hb_raster_paint_push_empty_clip (c, w, h); return; } /* Charge the mask render (its own area; may exceed the surface) plus * the clip-buffer pass below. */ hb_raster_extents_t mask_render_ext; hb_raster_image_get_extents (mask_img, &mask_render_ext); if (unlikely (!c->charge_work ((int64_t) mask_render_ext.width * mask_render_ext.height + (int64_t) w * h))) { hb_raster_draw_recycle_image (rdr, mask_img); hb_raster_paint_push_empty_clip (c, w, h); return; } hb_raster_clip_t new_clip = c->acquire_clip (w, h); /* Allocate alpha buffer and intersect with previous clip */ size_t clip_size = (size_t) new_clip.stride * h; if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE || !new_clip.alpha.resize ((unsigned) clip_size))) { hb_raster_draw_recycle_image (rdr, mask_img); hb_raster_paint_push_empty_clip (c, w, h); return; } hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size); const uint8_t *mask_buf = hb_raster_image_get_buffer (mask_img); hb_raster_extents_t mask_ext; hb_raster_image_get_extents (mask_img, &mask_ext); const hb_raster_clip_t &old_clip = c->current_clip (); if (unlikely (!old_clip.has_valid_alpha_mask ())) { hb_raster_draw_recycle_image (rdr, mask_img); hb_raster_paint_push_empty_clip (c, w, h); return; } int64_t mask_x0, mask_y0; unsigned ix0, iy0, ix1, iy1; if (!hb_raster_paint_intersect_mask (surf, mask_ext, old_clip, w, h, &mask_x0, &mask_y0, &ix0, &iy0, &ix1, &iy1)) { hb_raster_draw_recycle_image (rdr, mask_img); hb_raster_paint_push_empty_clip (c, w, h); return; } new_clip.min_x = w; new_clip.min_y = h; new_clip.max_x = 0; new_clip.max_y = 0; if (old_clip.is_rect) { for (unsigned y = iy0; y < iy1; y++) { const uint8_t *mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride; uint8_t *out_row = new_clip.alpha.arrayZ + y * new_clip.stride; unsigned row_min = ix1; unsigned row_max = ix0; unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0); for (unsigned x = ix0; x < ix1; x++) { uint8_t a = mask_row[mx++]; out_row[x] = a; if (a && row_min == ix1) { row_min = x; row_max = x + 1; } else if (a) row_max = x + 1; } if (row_min < row_max) { new_clip.min_x = hb_min (new_clip.min_x, row_min); new_clip.min_y = hb_min (new_clip.min_y, y); new_clip.max_x = hb_max (new_clip.max_x, row_max); new_clip.max_y = hb_max (new_clip.max_y, y + 1); } } } else { for (unsigned y = iy0; y < iy1; y++) { const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride; const uint8_t *mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride; uint8_t *out_row = new_clip.alpha.arrayZ + y * new_clip.stride; unsigned row_min = ix1; unsigned row_max = ix0; for (unsigned x = ix0; x < ix1; x++) { unsigned mx = (unsigned) ((int64_t) x - mask_x0); uint8_t a = hb_raster_div255 (mask_row[mx] * old_row[x]); out_row[x] = a; if (a) { row_min = hb_min (row_min, x); row_max = x + 1; } } if (row_min < row_max) { new_clip.min_x = hb_min (new_clip.min_x, row_min); new_clip.min_y = hb_min (new_clip.min_y, y); new_clip.max_x = hb_max (new_clip.max_x, row_max); new_clip.max_y = hb_max (new_clip.max_y, y + 1); } } } hb_raster_draw_recycle_image (rdr, mask_img); if (unlikely (!c->clip_stack.push_or_fail (std::move (new_clip)))) hb_raster_paint_push_empty_clip (c, w, h); } static void hb_raster_paint_push_clip_from_emitter (hb_raster_paint_t *c, hb_raster_paint_clip_mask_emit_t emit, void *emit_data) { ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; unsigned w = surf->extents.width; unsigned h = surf->extents.height; /* Out of budget: skip the glyph-outline extraction entirely, so * per-glyph outline limits cannot multiply with the caller's * paint-graph traversal limits. */ if (unlikely (c->budget_remaining < 0)) { hb_raster_paint_push_empty_clip (c, w, h); return; } hb_raster_draw_t *rdr = c->clip_rdr; hb_transform_t<> t = c->current_effective_transform (); hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0); hb_raster_paint_attach_clip_rdr (c, rdr, surf); emit (rdr, emit_data); hb_raster_paint_readback_budget (c, rdr); hb_raster_paint_finalize_path_clip (c, rdr, surf, w, h); } struct hb_raster_paint_glyph_clip_data_t { hb_codepoint_t glyph; hb_font_t *font; }; static void hb_raster_paint_emit_clip_glyph_mask (hb_raster_draw_t *rdr, void *user_data) { hb_raster_paint_glyph_clip_data_t *data = (hb_raster_paint_glyph_clip_data_t *) user_data; /* Let draw-render choose tight glyph extents; we map by mask origin below. */ hb_font_draw_glyph (data->font, data->glyph, hb_raster_draw_get_funcs (rdr), rdr); } static void hb_raster_paint_push_clip_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_codepoint_t glyph, hb_font_t *font, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; hb_raster_paint_glyph_clip_data_t data = {glyph, font}; hb_raster_paint_push_clip_from_emitter (c, hb_raster_paint_emit_clip_glyph_mask, &data); } static void hb_raster_paint_push_clip_rectangle (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, float xmin, float ymin, float xmax, float ymax, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); if (!c->surface_stack.length) return; hb_transform_t<> t = c->current_effective_transform (); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; unsigned w = surf->extents.width; unsigned h = surf->extents.height; bool is_axis_aligned = (t.xy == 0.f && t.yx == 0.f); /* Transform the four corners to pixel space */ float cx[4], cy[4]; cx[0] = xmin; cy[0] = ymin; cx[1] = xmax; cy[1] = ymin; cx[2] = xmax; cy[2] = ymax; cx[3] = xmin; cy[3] = ymax; for (unsigned i = 0; i < 4; i++) t.transform_point (cx[i], cy[i]); /* Compute bounding box in pixel coords */ float fmin_x = cx[0], fmin_y = cy[0], fmax_x = cx[0], fmax_y = cy[0]; for (unsigned i = 1; i < 4; i++) { fmin_x = hb_min (fmin_x, cx[i]); fmin_y = hb_min (fmin_y, cy[i]); fmax_x = hb_max (fmax_x, cx[i]); fmax_y = hb_max (fmax_y, cy[i]); } /* A spec-legal but very large transform can push these bounds past the int range or to non-finite values; a bare float->int conversion would then be undefined. Saturate through hb_clamp_to<> (which also maps NaN to INT32_MIN); the subtraction is done in double to avoid overflow. */ int px0 = hb_clamp_to ((double) floorf (fmin_x) - surf->extents.x_origin); int py0 = hb_clamp_to ((double) floorf (fmin_y) - surf->extents.y_origin); int px1 = hb_clamp_to ((double) ceilf (fmax_x) - surf->extents.x_origin); int py1 = hb_clamp_to ((double) ceilf (fmax_y) - surf->extents.y_origin); /* Clamp to surface bounds */ px0 = hb_max (px0, 0); py0 = hb_max (py0, 0); px1 = hb_min (px1, (int) w); py1 = hb_min (py1, (int) h); const hb_raster_clip_t &old_clip = c->current_clip (); if (unlikely (!old_clip.has_valid_alpha_mask ())) { hb_raster_paint_push_empty_clip (c, w, h); return; } /* All paths below except rect-on-rect run a full clip-buffer pass. */ if (!(is_axis_aligned && old_clip.is_rect) && unlikely (!c->charge_work ((int64_t) w * h))) { hb_raster_paint_push_empty_clip (c, w, h); return; } hb_raster_clip_t new_clip = c->acquire_clip (w, h); if (is_axis_aligned && old_clip.is_rect) { /* Fast path: axis-aligned rect-on-rect intersection */ new_clip.is_rect = true; new_clip.rect_x0 = hb_max (px0, old_clip.rect_x0); new_clip.rect_y0 = hb_max (py0, old_clip.rect_y0); new_clip.rect_x1 = hb_min (px1, old_clip.rect_x1); new_clip.rect_y1 = hb_min (py1, old_clip.rect_y1); new_clip.update_bounds_from_rect (); } else if (is_axis_aligned) { /* Fast path: intersect an axis-aligned rect with an existing alpha clip. */ new_clip.is_rect = false; size_t clip_size = (size_t) new_clip.stride * h; if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE || !new_clip.alpha.resize ((unsigned) clip_size))) { hb_raster_paint_push_empty_clip (c, w, h); return; } hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size); int iy0_i = hb_max (py0, (int) old_clip.min_y); int iy1_i = hb_min (py1, (int) old_clip.max_y); int ix0_i = hb_max (px0, (int) old_clip.min_x); int ix1_i = hb_min (px1, (int) old_clip.max_x); if (ix0_i < ix1_i && iy0_i < iy1_i) { unsigned iy0 = (unsigned) iy0_i; unsigned iy1 = (unsigned) iy1_i; unsigned ix0 = (unsigned) ix0_i; unsigned ix1 = (unsigned) ix1_i; for (unsigned y = iy0; y < iy1; y++) { const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride; uint8_t *new_row = new_clip.alpha.arrayZ + y * new_clip.stride; hb_memcpy (new_row + ix0, old_row + ix0, ix1 - ix0); } new_clip.min_x = ix0; new_clip.min_y = iy0; new_clip.max_x = ix1; new_clip.max_y = iy1; } } else { /* General case: rasterize transformed quad as alpha mask */ new_clip.is_rect = false; size_t clip_size = (size_t) new_clip.stride * h; if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE || !new_clip.alpha.resize ((unsigned) clip_size))) { hb_raster_paint_push_empty_clip (c, w, h); return; } hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size); /* Convert quad corners to pixel-relative coords */ float qx[4], qy[4]; int ox = surf->extents.x_origin; int oy = surf->extents.y_origin; for (unsigned i = 0; i < 4; i++) { qx[i] = cx[i] - ox; qy[i] = cy[i] - oy; } /* For each pixel in the bounding box, test if inside the quad * using cross-product edge tests (winding order). */ int iy0_i = hb_max (py0, (int) old_clip.min_y); int iy1_i = hb_min (py1, (int) old_clip.max_y); int ix0_i = hb_max (px0, (int) old_clip.min_x); int ix1_i = hb_min (px1, (int) old_clip.max_x); new_clip.min_x = w; new_clip.min_y = h; new_clip.max_x = 0; new_clip.max_y = 0; if (ix0_i < ix1_i && iy0_i < iy1_i) { unsigned iy0 = (unsigned) iy0_i; unsigned iy1 = (unsigned) iy1_i; unsigned ix0 = (unsigned) ix0_i; unsigned ix1 = (unsigned) ix1_i; /* Precompute edge normals for point-in-quad test. * Edge i goes from corner i to corner (i+1)%4. * Normal = (dy, -dx); inside test: dot(normal, p-corner) >= 0 */ float enx[4], eny[4], ed[4]; for (unsigned i = 0; i < 4; i++) { unsigned j = (i + 1) & 3; float edx = qx[j] - qx[i], edy = qy[j] - qy[i]; enx[i] = edy; /* normal x */ eny[i] = -edx; /* normal y */ ed[i] = enx[i] * qx[i] + eny[i] * qy[i]; /* distance threshold */ } float area2 = 0.f; for (unsigned i = 0; i < 4; i++) { unsigned j = (i + 1) & 3; area2 += qx[i] * qy[j] - qx[j] * qy[i]; } bool ccw = area2 >= 0.f; if (old_clip.is_rect) { for (unsigned y = iy0; y < iy1; y++) for (unsigned x = ix0; x < ix1; x++) { float px_f = x + 0.5f, py_f = y + 0.5f; /* Test if pixel center is inside the quad */ bool inside = true; for (unsigned i = 0; i < 4; i++) { float d = enx[i] * px_f + eny[i] * py_f; if (ccw ? d < ed[i] : d > ed[i]) { inside = false; break; } } uint8_t a = inside ? 255 : 0; new_clip.alpha[y * new_clip.stride + x] = a; if (a) { new_clip.min_x = hb_min (new_clip.min_x, x); new_clip.min_y = hb_min (new_clip.min_y, y); new_clip.max_x = hb_max (new_clip.max_x, x + 1); new_clip.max_y = hb_max (new_clip.max_y, y + 1); } } } else { for (unsigned y = iy0; y < iy1; y++) { const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride; for (unsigned x = ix0; x < ix1; x++) { float px_f = x + 0.5f, py_f = y + 0.5f; /* Test if pixel center is inside the quad */ bool inside = true; for (unsigned i = 0; i < 4; i++) { float d = enx[i] * px_f + eny[i] * py_f; if (ccw ? d < ed[i] : d > ed[i]) { inside = false; break; } } uint8_t a = inside ? old_row[x] : 0; new_clip.alpha[y * new_clip.stride + x] = a; if (a) { new_clip.min_x = hb_min (new_clip.min_x, x); new_clip.min_y = hb_min (new_clip.min_y, y); new_clip.max_x = hb_max (new_clip.max_x, x + 1); new_clip.max_y = hb_max (new_clip.max_y, y + 1); } } } } } } if (unlikely (!c->clip_stack.push_or_fail (std::move (new_clip)))) hb_raster_paint_push_empty_clip (c, surf->extents.width, surf->extents.height); } static hb_draw_funcs_t * hb_raster_paint_push_clip_path_start (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, void **draw_data, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); /* Prime clip_rdr with the current effective transform; the * caller then drives hb_draw_*() into it, and _end renders * the accumulated edges into a mask and intersects with the * current clip. */ if (unlikely (!c->surface_stack.length || !c->clip_rdr)) { *draw_data = nullptr; return nullptr; } hb_raster_draw_t *rdr = c->clip_rdr; hb_transform_t<> t = c->current_effective_transform (); hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0); hb_raster_image_t *surf = c->current_surface (); if (likely (surf)) hb_raster_paint_attach_clip_rdr (c, rdr, surf); *draw_data = rdr; return hb_raster_draw_get_funcs (rdr); } static void hb_raster_paint_push_clip_path_end (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf || !c->clip_rdr)) return; unsigned w = surf->extents.width; unsigned h = surf->extents.height; hb_raster_paint_readback_budget (c, c->clip_rdr); if (unlikely (c->budget_remaining < 0)) { hb_raster_draw_clear (c->clip_rdr); hb_raster_paint_push_empty_clip (c, w, h); return; } hb_raster_paint_finalize_path_clip (c, c->clip_rdr, surf, w, h); } static void hb_raster_paint_pop_clip (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; if (!c->clip_stack.length) return; c->release_clip (c->clip_stack.pop ()); } static void hb_raster_paint_push_group (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); /* acquire_surface() clears a full surface; charge its area. */ if (unlikely (!c->charge_work ((int64_t) c->fixed_extents.width * c->fixed_extents.height))) return; hb_raster_image_t *new_surf = c->acquire_surface (); if (unlikely (!new_surf)) return; if (unlikely (!c->surface_stack.push_or_fail (new_surf))) c->release_surface (new_surf); } static void hb_raster_paint_pop_group (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_paint_composite_mode_t mode, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; if (c->surface_stack.length < 2) return; hb_raster_image_t *src = c->surface_stack.pop (); hb_raster_image_t *dst = c->current_surface (); if (dst && src && likely (c->charge_work ((int64_t) dst->extents.width * dst->extents.height))) hb_raster_image_composite (dst, src, mode); c->release_surface (src); } static void hb_raster_paint_solid (hb_raster_paint_t *c, hb_raster_image_t *surf, hb_color_t color, hb_raster_image_t *mask_img) { uint32_t premul = color_to_premul_pixel (color); uint8_t premul_a = (uint8_t) (premul >> 24); const hb_raster_clip_t &clip = c->current_clip (); unsigned stride = surf->extents.stride; if (clip.min_x >= clip.max_x || clip.min_y >= clip.max_y) return; if (premul_a == 0) return; unsigned ix0 = clip.min_x, iy0 = clip.min_y; unsigned ix1 = clip.max_x, iy1 = clip.max_y; const uint8_t *mask_buf = nullptr; hb_raster_extents_t mask_ext; int64_t mask_x0 = 0, mask_y0 = 0; if (mask_img) { mask_buf = hb_raster_image_get_buffer (mask_img); hb_raster_image_get_extents (mask_img, &mask_ext); if (!hb_raster_paint_intersect_mask (surf, mask_ext, clip, surf->extents.width, surf->extents.height, &mask_x0, &mask_y0, &ix0, &iy0, &ix1, &iy1)) return; } if (unlikely (!c->charge_work ((int64_t) (ix1 - ix0) * (iy1 - iy0)))) return; if (likely (!clip.is_rect)) { for (unsigned y = iy0; y < iy1; y++) { hb_packed_t *__restrict row = (hb_packed_t *) (surf->buffer.arrayZ + y * stride); const uint8_t *__restrict clip_row = clip.alpha.arrayZ + y * clip.stride; const uint8_t *__restrict mask_row = mask_buf ? mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride : nullptr; unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0); for (unsigned x = ix0; x < ix1; x++) { uint8_t clip_alpha = clip_row[x]; if (mask_row) clip_alpha = hb_raster_div255 (mask_row[mx++] * clip_alpha); if (clip_alpha == 0) continue; if (clip_alpha == 255) { if (premul_a == 255) row[x] = hb_packed_t (premul); else row[x] = hb_packed_t (hb_raster_src_over (premul, (uint32_t) row[x])); } else { uint32_t src = hb_raster_alpha_mul (premul, clip_alpha); row[x] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[x])); } } } } else if (mask_buf) { for (unsigned y = iy0; y < iy1; y++) { hb_packed_t *__restrict row = (hb_packed_t *) (surf->buffer.arrayZ + y * stride); const uint8_t *__restrict mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride; unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0); for (unsigned x = ix0; x < ix1; x++) { uint8_t mask_alpha = mask_row[mx++]; if (mask_alpha == 0) continue; uint32_t src = hb_raster_alpha_mul (premul, mask_alpha); row[x] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[x])); } } } else { for (unsigned y = clip.min_y; y < clip.max_y; y++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + y * stride); for (unsigned x = clip.min_x; x < clip.max_x; x++) row[x] = hb_packed_t (hb_raster_src_over (premul, (uint32_t) row[x])); } } } static void hb_raster_paint_color (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_bool_t is_foreground HB_UNUSED, hb_color_t color, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; hb_raster_paint_solid (c, surf, color, nullptr); } static void hb_raster_paint_fill_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_codepoint_t glyph, hb_font_t *font, hb_bool_t is_foreground HB_UNUSED, hb_color_t color, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; /* Out of budget: skip the glyph-outline extraction entirely. */ if (unlikely (c->budget_remaining < 0)) return; hb_raster_draw_t *rdr = c->clip_rdr; hb_transform_t<> t = c->current_effective_transform (); hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0); hb_raster_paint_attach_clip_rdr (c, rdr, surf); hb_raster_paint_glyph_clip_data_t data = {glyph, font}; hb_raster_paint_emit_clip_glyph_mask (rdr, &data); hb_raster_paint_readback_budget (c, rdr); if (unlikely (!c->precharge_work (hb_raster_draw_get_pixel_work (rdr, surf->extents.height, surf->extents.width)))) { hb_raster_draw_clear (rdr); return; } hb_raster_image_t *mask_img = hb_raster_draw_render (rdr); if (unlikely (!mask_img)) return; hb_raster_extents_t mask_ext; hb_raster_image_get_extents (mask_img, &mask_ext); if (likely (c->charge_work ((int64_t) mask_ext.width * mask_ext.height))) hb_raster_paint_solid (c, surf, color, mask_img); hb_raster_draw_recycle_image (rdr, mask_img); } static hb_bool_t hb_raster_paint_image (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_blob_t *blob, unsigned width, unsigned height, hb_tag_t format, float slant HB_UNUSED, hb_glyph_extents_t *extents, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); /* Out of pixel budget: skip, including the image decode below. Image * compositing is pixel work and does no outline traversal. */ if (unlikely (c->pixel_remaining < 0)) return false; unsigned src_width = width; unsigned src_height = height; const hb_packed_t *src_data = nullptr; hb_raster_image_t decoded_png; if (format == HB_PAINT_IMAGE_FORMAT_BGRA) { if (src_width == 0 || src_height == 0) return false; if (src_width > (unsigned) INT_MAX || src_height > (unsigned) INT_MAX) return false; unsigned data_len; const uint8_t *data = (const uint8_t *) hb_blob_get_data (blob, &data_len); size_t pixel_count = (size_t) src_width * (size_t) src_height; if (src_width && pixel_count / src_width != src_height) return false; if (pixel_count > (size_t) -1 / 4u) return false; size_t required_size = pixel_count * 4u; if (!data || (size_t) data_len < required_size) return false; src_data = (const hb_packed_t *) data; } else if (format == HB_PAINT_IMAGE_FORMAT_PNG) { #ifdef HAVE_PNG if (!decoded_png.deserialize_from_png (blob)) return false; src_width = decoded_png.extents.width; src_height = decoded_png.extents.height; src_data = (const hb_packed_t *) decoded_png.buffer.arrayZ; #else return false; #endif } else return false; hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return false; if (!extents) return false; const hb_raster_clip_t &clip = c->current_clip (); hb_transform_t<> t = c->current_effective_transform (); /* Compute inverse transform for sampling */ float det = t.xx * t.yy - t.xy * t.yx; if (fabsf (det) < 1e-10f) return false; float inv_det = 1.f / det; float inv_xx = t.yy * inv_det; float inv_xy = -t.xy * inv_det; float inv_yx = -t.yx * inv_det; float inv_yy = t.xx * inv_det; float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det; float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det; unsigned surf_stride = surf->extents.stride; int ox = surf->extents.x_origin; int oy = surf->extents.y_origin; /* Image source rectangle in glyph space */ float img_x = extents->x_bearing; float img_y = extents->y_bearing; float img_sx = (float) extents->width / src_width; float img_sy = (float) extents->height / src_height; if (fabsf (img_sx) < 1e-10f || fabsf (img_sy) < 1e-10f) return false; { unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0; unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0; if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h + (int64_t) src_width * src_height))) return false; } if (clip.is_rect) { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * surf_stride); float gx = inv_xx * (float) ((int) clip.min_x + ox) + inv_xy * (float) ((int) py + oy) + inv_x0; float gy = inv_yx * (float) ((int) clip.min_x + ox) + inv_yy * (float) ((int) py + oy) + inv_y0; for (unsigned px = clip.min_x; px < clip.max_x; px++) { /* Map glyph space to image texel; bilinear reconstruction. */ float ix = (gx - img_x) / img_sx; float iy = (float) (src_height - 1) - (gy - img_y) / img_sy; if (unlikely (!std::isfinite (ix) || !std::isfinite (iy)) || ix < 0.f || iy < 0.f || ix > (float) (src_width - 1) || iy > (float) (src_height - 1)) { gx += inv_xx; gy += inv_yx; continue; } uint32_t src_px = hb_raster_sample_bilinear_premul (src_data, src_width, src_height, ix, iy); row[px] = hb_packed_t (hb_raster_src_over (src_px, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } else { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * surf_stride); const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride; float gx = inv_xx * (float) ((int) clip.min_x + ox) + inv_xy * (float) ((int) py + oy) + inv_x0; float gy = inv_yx * (float) ((int) clip.min_x + ox) + inv_yy * (float) ((int) py + oy) + inv_y0; for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } /* Map glyph space to image texel; bilinear reconstruction. */ float ix = (gx - img_x) / img_sx; float iy = (float) (src_height - 1) - (gy - img_y) / img_sy; if (unlikely (!std::isfinite (ix) || !std::isfinite (iy)) || ix < 0.f || iy < 0.f || ix > (float) (src_width - 1) || iy > (float) (src_height - 1)) { gx += inv_xx; gy += inv_yx; continue; } uint32_t src_px = hb_raster_sample_bilinear_premul (src_data, src_width, src_height, ix, iy); src_px = hb_raster_alpha_mul (src_px, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src_px, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } return true; } /* * Gradient helpers */ #define GRADIENT_LUT_SIZE 256 #define GRADIENT_LUT_MIN_PIXELS (64u * 64u) static HB_ALWAYS_INLINE float reflect_gradient_t (float t) { t = fmodf (fabsf (t), 2.f); return t > 1.f ? 2.f - t : t; } /* Evaluate color at normalized position t, interpolating in premultiplied space. */ static uint32_t evaluate_color_line (const hb_color_stop_t *stops, unsigned len, float t, hb_paint_extend_t extend) { if (unlikely (!len)) return 0; if (unlikely (len == 1)) return color_to_premul_pixel (stops[0].color); if (unlikely (!std::isfinite (t))) t = 0.f; /* Apply extend mode */ if (extend == HB_PAINT_EXTEND_PAD) { t = hb_clamp (t, 0.f, 1.f); } else if (extend == HB_PAINT_EXTEND_REPEAT) { t = t - floorf (t); } else /* REFLECT */ { t = reflect_gradient_t (t); } /* Find bounding stops */ if (t <= stops[0].offset) return color_to_premul_pixel (stops[0].color); if (t >= stops[len - 1].offset) return color_to_premul_pixel (stops[len - 1].color); unsigned i; for (i = 0; i < len - 1; i++) if (t < stops[i + 1].offset) break; float range = stops[i + 1].offset - stops[i].offset; float k = range > 0.f ? (t - stops[i].offset) / range : 0.f; /* Interpolate in premultiplied [0,255] space */ hb_color_t c0 = stops[i].color; hb_color_t c1 = stops[i + 1].color; float a0 = hb_color_get_alpha (c0) / 255.f; float r0 = hb_color_get_red (c0) / 255.f * a0; float g0 = hb_color_get_green (c0) / 255.f * a0; float b0 = hb_color_get_blue (c0) / 255.f * a0; float a1 = hb_color_get_alpha (c1) / 255.f; float r1 = hb_color_get_red (c1) / 255.f * a1; float g1 = hb_color_get_green (c1) / 255.f * a1; float b1 = hb_color_get_blue (c1) / 255.f * a1; float a = a0 + k * (a1 - a0); float r = r0 + k * (r1 - r0); float g = g0 + k * (g1 - g0); float b = b0 + k * (b1 - b0); uint8_t pa = (uint8_t) (a * 255.f + 0.5f); uint8_t pr = (uint8_t) (r * 255.f + 0.5f); uint8_t pg = (uint8_t) (g * 255.f + 0.5f); uint8_t pb = (uint8_t) (b * 255.f + 0.5f); return (uint32_t) pb | ((uint32_t) pg << 8) | ((uint32_t) pr << 16) | ((uint32_t) pa << 24); } static HB_ALWAYS_INLINE float normalize_gradient_t (float t, hb_paint_extend_t extend) { if (unlikely (!std::isfinite (t))) return 0.f; if (extend == HB_PAINT_EXTEND_PAD) return hb_clamp (t, 0.f, 1.f); if (extend == HB_PAINT_EXTEND_REPEAT) { t = t - floorf (t); return t < 0.f ? t + 1.f : t; } /* REFLECT */ return reflect_gradient_t (t); } static void build_gradient_lut (const hb_color_stop_t *stops, unsigned len, uint32_t *lut) { for (unsigned i = 0; i < GRADIENT_LUT_SIZE; i++) { float t = (float) i / (GRADIENT_LUT_SIZE - 1); lut[i] = evaluate_color_line (stops, len, t, HB_PAINT_EXTEND_PAD); } } static HB_ALWAYS_INLINE uint32_t lookup_gradient_lut (const uint32_t *lut, float t, hb_paint_extend_t extend) { float u = normalize_gradient_t (t, extend); if (unlikely (!std::isfinite (u))) u = 0.f; else u = hb_clamp (u, 0.f, 1.f); unsigned idx = (unsigned) (u * (GRADIENT_LUT_SIZE - 1) + 0.5f); return lut[idx]; } /* * Gradient paint callbacks */ static void hb_raster_paint_linear_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_color_line_t *color_line, float x0, float y0, float x1, float y1, float x2, float y2, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; if (unlikely (!c->fetch_color_stops (color_line))) return; hb_color_stop_t *stops = c->scratch_color_stops.arrayZ; unsigned len = c->scratch_color_stops.length; float mn, mx; hb_paint_normalize_color_line (stops, len, &mn, &mx); hb_paint_extend_t extend = hb_color_line_get_extend (color_line); const hb_raster_clip_t &clip = c->current_clip (); unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0; unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0; if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h))) return; bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS; uint32_t lut[GRADIENT_LUT_SIZE]; if (use_lut) build_gradient_lut (stops, len, lut); /* Reduce 3-point anchor to 2-point gradient axis */ float lx0, ly0, lx1, ly1; hb_paint_reduce_linear_anchors (x0, y0, x1, y1, x2, y2, &lx0, &ly0, &lx1, &ly1); /* Apply normalization to endpoints */ float gx0 = lx0 + mn * (lx1 - lx0); float gy0 = ly0 + mn * (ly1 - ly0); float gx1 = lx0 + mx * (lx1 - lx0); float gy1 = ly0 + mx * (ly1 - ly0); /* Inverse transform: pixel → glyph space */ hb_transform_t<> t = c->current_effective_transform (); float det = t.xx * t.yy - t.xy * t.yx; if (fabsf (det) < 1e-10f) return; { float inv_det = 1.f / det; float inv_xx = t.yy * inv_det; float inv_xy = -t.xy * inv_det; float inv_yx = -t.yx * inv_det; float inv_yy = t.xx * inv_det; float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det; float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det; /* Gradient direction vector and denominator for projection */ float dx = gx1 - gx0, dy = gy1 - gy0; float denom = dx * dx + dy * dy; if (denom < 1e-10f) return; float inv_denom = 1.f / denom; unsigned stride = surf->extents.stride; int ox = surf->extents.x_origin; int oy = surf->extents.y_origin; if (clip.is_rect) { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom; uint32_t src = lookup_gradient_lut (lut, proj_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom; uint32_t src = evaluate_color_line (stops, len, proj_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } else { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride; float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom; uint32_t src = lookup_gradient_lut (lut, proj_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom; uint32_t src = evaluate_color_line (stops, len, proj_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } } } static void hb_raster_paint_radial_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_color_line_t *color_line, float x0, float y0, float r0, float x1, float y1, float r1, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; if (unlikely (!c->fetch_color_stops (color_line))) return; hb_color_stop_t *stops = c->scratch_color_stops.arrayZ; unsigned len = c->scratch_color_stops.length; float mn, mx; hb_paint_normalize_color_line (stops, len, &mn, &mx); hb_paint_extend_t extend = hb_color_line_get_extend (color_line); const hb_raster_clip_t &clip = c->current_clip (); unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0; unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0; if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h))) return; bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS; uint32_t lut[GRADIENT_LUT_SIZE]; if (use_lut) build_gradient_lut (stops, len, lut); /* Apply normalization to circle parameters */ float cx0 = x0 + mn * (x1 - x0); float cy0 = y0 + mn * (y1 - y0); float cr0 = r0 + mn * (r1 - r0); float cx1 = x0 + mx * (x1 - x0); float cy1 = y0 + mx * (y1 - y0); float cr1 = r0 + mx * (r1 - r0); /* Inverse transform */ hb_transform_t<> t = c->current_effective_transform (); float det = t.xx * t.yy - t.xy * t.yx; if (fabsf (det) < 1e-10f) return; { float inv_det = 1.f / det; float inv_xx = t.yy * inv_det; float inv_xy = -t.xy * inv_det; float inv_yx = -t.yx * inv_det; float inv_yy = t.xx * inv_det; float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det; float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det; /* Precompute quadratic coefficients for radial gradient: * |p - c0 - t*(c1-c0)|^2 = (r0 + t*(r1-r0))^2 * * Expanding gives At^2 + Bt + C = 0 where: * cdx = c1.x - c0.x, cdy = c1.y - c0.y, dr = r1 - r0 * A = cdx^2 + cdy^2 - dr^2 * B = -2*(px-c0.x)*cdx - 2*(py-c0.y)*cdy - 2*r0*dr * C = (px-c0.x)^2 + (py-c0.y)^2 - r0^2 */ float cdx = cx1 - cx0, cdy = cy1 - cy0; float dr = cr1 - cr0; float A = cdx * cdx + cdy * cdy - dr * dr; unsigned stride = surf->extents.stride; int ox = surf->extents.x_origin; int oy = surf->extents.y_origin; if (clip.is_rect) { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float dpx = gx - cx0, dpy = gy - cy0; float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr); float C = dpx * dpx + dpy * dpy - cr0 * cr0; float grad_t; if (fabsf (A) > 1e-10f) { float disc = B * B - 4.f * A * C; if (disc < 0.f) { gx += inv_xx; gy += inv_yx; continue; } float sq = sqrtf (disc); /* Pick the larger root (t closer to 1 = outer circle) */ float t1 = (-B + sq) / (2.f * A); float t2 = (-B - sq) / (2.f * A); /* Choose the root that gives a positive radius */ if (cr0 + t1 * dr >= 0.f) grad_t = t1; else grad_t = t2; } else { /* Linear case: Bt + C = 0 */ if (fabsf (B) < 1e-10f) { gx += inv_xx; gy += inv_yx; continue; } grad_t = -C / B; } uint32_t src = lookup_gradient_lut (lut, grad_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float dpx = gx - cx0, dpy = gy - cy0; float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr); float C = dpx * dpx + dpy * dpy - cr0 * cr0; float grad_t; if (fabsf (A) > 1e-10f) { float disc = B * B - 4.f * A * C; if (disc < 0.f) { gx += inv_xx; gy += inv_yx; continue; } float sq = sqrtf (disc); float t1 = (-B + sq) / (2.f * A); float t2 = (-B - sq) / (2.f * A); grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2; } else { if (fabsf (B) < 1e-10f) { gx += inv_xx; gy += inv_yx; continue; } grad_t = -C / B; } uint32_t src = evaluate_color_line (stops, len, grad_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } else { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride; float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float dpx = gx - cx0, dpy = gy - cy0; float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr); float C = dpx * dpx + dpy * dpy - cr0 * cr0; float grad_t; if (fabsf (A) > 1e-10f) { float disc = B * B - 4.f * A * C; if (disc < 0.f) { gx += inv_xx; gy += inv_yx; continue; } float sq = sqrtf (disc); float t1 = (-B + sq) / (2.f * A); float t2 = (-B - sq) / (2.f * A); grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2; } else { if (fabsf (B) < 1e-10f) { gx += inv_xx; gy += inv_yx; continue; } grad_t = -C / B; } uint32_t src = lookup_gradient_lut (lut, grad_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float dpx = gx - cx0, dpy = gy - cy0; float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr); float C = dpx * dpx + dpy * dpy - cr0 * cr0; float grad_t; if (fabsf (A) > 1e-10f) { float disc = B * B - 4.f * A * C; if (disc < 0.f) { gx += inv_xx; gy += inv_yx; continue; } float sq = sqrtf (disc); float t1 = (-B + sq) / (2.f * A); float t2 = (-B - sq) / (2.f * A); grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2; } else { if (fabsf (B) < 1e-10f) { gx += inv_xx; gy += inv_yx; continue; } grad_t = -C / B; } uint32_t src = evaluate_color_line (stops, len, grad_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } } } static void hb_raster_paint_sweep_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED, void *paint_data, hb_color_line_t *color_line, float cx, float cy, float start_angle, float end_angle, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; ensure_initialized (c); hb_raster_image_t *surf = c->current_surface (); if (unlikely (!surf)) return; if (unlikely (!c->fetch_color_stops (color_line))) return; hb_color_stop_t *stops = c->scratch_color_stops.arrayZ; unsigned len = c->scratch_color_stops.length; float mn, mx; hb_paint_normalize_color_line (stops, len, &mn, &mx); hb_paint_extend_t extend = hb_color_line_get_extend (color_line); const hb_raster_clip_t &clip = c->current_clip (); unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0; unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0; if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h))) return; bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS; uint32_t lut[GRADIENT_LUT_SIZE]; if (use_lut) build_gradient_lut (stops, len, lut); /* Apply normalization to angle range */ float a0 = start_angle + mn * (end_angle - start_angle); float a1 = start_angle + mx * (end_angle - start_angle); float angle_range = a1 - a0; /* Inverse transform */ hb_transform_t<> t = c->current_effective_transform (); float det = t.xx * t.yy - t.xy * t.yx; if (fabsf (det) < 1e-10f || fabsf (angle_range) < 1e-10f) return; { float inv_det = 1.f / det; float inv_xx = t.yy * inv_det; float inv_xy = -t.xy * inv_det; float inv_yx = -t.yx * inv_det; float inv_yy = t.xx * inv_det; float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det; float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det; float inv_angle_range = 1.f / angle_range; unsigned stride = surf->extents.stride; int ox = surf->extents.x_origin; int oy = surf->extents.y_origin; if (clip.is_rect) { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float angle = atan2f (gy - cy, gx - cx); if (angle < 0) angle += (float) HB_2_PI; float grad_t = (angle - a0) * inv_angle_range; uint32_t src = lookup_gradient_lut (lut, grad_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { float angle = atan2f (gy - cy, gx - cx); if (angle < 0) angle += (float) HB_2_PI; float grad_t = (angle - a0) * inv_angle_range; uint32_t src = evaluate_color_line (stops, len, grad_t, extend); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } else { for (unsigned py = clip.min_y; py < clip.max_y; py++) { hb_packed_t *row = (hb_packed_t *) (surf->buffer.arrayZ + py * stride); const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride; float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0; float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0; if (use_lut) { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float angle = atan2f (gy - cy, gx - cx); if (angle < 0) angle += (float) HB_2_PI; float grad_t = (angle - a0) * inv_angle_range; uint32_t src = lookup_gradient_lut (lut, grad_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } else { for (unsigned px = clip.min_x; px < clip.max_x; px++) { uint8_t clip_alpha = clip_row[px]; if (clip_alpha == 0) { gx += inv_xx; gy += inv_yx; continue; } float angle = atan2f (gy - cy, gx - cx); if (angle < 0) angle += (float) HB_2_PI; float grad_t = (angle - a0) * inv_angle_range; uint32_t src = evaluate_color_line (stops, len, grad_t, extend); src = hb_raster_alpha_mul (src, clip_alpha); row[px] = hb_packed_t (hb_raster_src_over (src, (uint32_t) row[px])); gx += inv_xx; gy += inv_yx; } } } } } } static hb_bool_t hb_raster_paint_custom_palette_color (hb_paint_funcs_t *funcs HB_UNUSED, void *paint_data, unsigned int color_index, hb_color_t *color, void *user_data HB_UNUSED) { hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data; if (likely (c->custom_palette && hb_map_has (c->custom_palette, color_index))) { *color = hb_map_get (c->custom_palette, color_index); return true; } return false; } static hb_bool_t hb_raster_paint_set_budget (hb_paint_funcs_t *, void *paint_data, int64_t budget, void *) { auto *paint = (hb_raster_paint_t *) paint_data; paint->budget = budget; paint->recharge_budget (); return true; } static int64_t hb_raster_paint_get_budget (hb_paint_funcs_t *, void *paint_data, void *) { return ((hb_raster_paint_t *) paint_data)->budget; } static int64_t * hb_raster_paint_get_budget_remaining (hb_paint_funcs_t *, void *paint_data, void *) { return &((hb_raster_paint_t *) paint_data)->budget_remaining; } /* * Lazy-loader singleton for paint funcs */ static inline void free_static_raster_paint_funcs (); static struct hb_raster_paint_funcs_lazy_loader_t : hb_paint_funcs_lazy_loader_t { static hb_paint_funcs_t *create () { hb_paint_funcs_t *funcs = hb_paint_funcs_create (); hb_paint_funcs_set_push_transform_func (funcs, hb_raster_paint_push_transform, nullptr, nullptr); hb_paint_funcs_set_pop_transform_func (funcs, hb_raster_paint_pop_transform, nullptr, nullptr); hb_paint_funcs_set_fill_glyph_func (funcs, hb_raster_paint_fill_glyph, nullptr, nullptr); hb_paint_funcs_set_color_glyph_func (funcs, hb_raster_paint_color_glyph, nullptr, nullptr); hb_paint_funcs_set_push_clip_glyph_func (funcs, hb_raster_paint_push_clip_glyph, nullptr, nullptr); hb_paint_funcs_set_push_clip_rectangle_func (funcs, hb_raster_paint_push_clip_rectangle, nullptr, nullptr); hb_paint_funcs_set_push_clip_path_start_func (funcs, hb_raster_paint_push_clip_path_start, nullptr, nullptr); hb_paint_funcs_set_push_clip_path_end_func (funcs, hb_raster_paint_push_clip_path_end, nullptr, nullptr); hb_paint_funcs_set_pop_clip_func (funcs, hb_raster_paint_pop_clip, nullptr, nullptr); hb_paint_funcs_set_push_group_func (funcs, hb_raster_paint_push_group, nullptr, nullptr); hb_paint_funcs_set_pop_group_func (funcs, hb_raster_paint_pop_group, nullptr, nullptr); hb_paint_funcs_set_color_func (funcs, hb_raster_paint_color, nullptr, nullptr); hb_paint_funcs_set_image_func (funcs, hb_raster_paint_image, nullptr, nullptr); hb_paint_funcs_set_linear_gradient_func (funcs, hb_raster_paint_linear_gradient, nullptr, nullptr); hb_paint_funcs_set_radial_gradient_func (funcs, hb_raster_paint_radial_gradient, nullptr, nullptr); hb_paint_funcs_set_sweep_gradient_func (funcs, hb_raster_paint_sweep_gradient, nullptr, nullptr); hb_paint_funcs_set_custom_palette_color_func (funcs, hb_raster_paint_custom_palette_color, nullptr, nullptr); hb_paint_funcs_set_set_budget_func (funcs, hb_raster_paint_set_budget, nullptr, nullptr); hb_paint_funcs_set_get_budget_func (funcs, hb_raster_paint_get_budget, nullptr, nullptr); hb_paint_funcs_set_get_budget_remaining_func (funcs, hb_raster_paint_get_budget_remaining, nullptr, nullptr); hb_paint_funcs_make_immutable (funcs); hb_atexit (free_static_raster_paint_funcs); return funcs; } } static_raster_paint_funcs; static inline void free_static_raster_paint_funcs () { static_raster_paint_funcs.free_instance (); } /* * Public API */ /** * hb_raster_paint_create_or_fail: * * Creates a new color-glyph paint context. * * Return value: (transfer full): * A newly allocated #hb_raster_paint_t, or `NULL` on allocation failure. * * Since: 13.0.0 **/ hb_raster_paint_t * hb_raster_paint_create_or_fail (void) { hb_raster_paint_t *paint = hb_object_create (); if (unlikely (!paint)) return nullptr; paint->clip_rdr = hb_raster_draw_create_or_fail (); if (unlikely (!paint->clip_rdr)) { hb_free (paint); return nullptr; } return paint; } /** * hb_raster_paint_reference: (skip) * @paint: a paint context * * Increases the reference count on @paint by one. * * Return value: (transfer full): * The referenced #hb_raster_paint_t. * * Since: 13.0.0 **/ hb_raster_paint_t * hb_raster_paint_reference (hb_raster_paint_t *paint) { return hb_object_reference (paint); } /** * hb_raster_paint_destroy: (skip) * @paint: a paint context * * Decreases the reference count on @paint by one. When the * reference count reaches zero, the paint context is freed. * * Since: 13.0.0 **/ void hb_raster_paint_destroy (hb_raster_paint_t *paint) { if (!hb_object_should_destroy (paint)) return; hb_map_destroy (paint->custom_palette); hb_raster_draw_destroy (paint->clip_rdr); for (auto *s : paint->surface_stack) hb_raster_image_destroy (s); for (auto *s : paint->surface_cache) hb_raster_image_destroy (s); hb_object_actually_destroy (paint); hb_free (paint); } /** * hb_raster_paint_set_user_data: (skip) * @paint: a paint context * @key: the user-data key * @data: a pointer to the user data * @destroy: (nullable): a callback to call when @data is not needed anymore * @replace: whether to replace an existing data with the same key * * Attaches a user-data key/data pair to the specified paint context. * * Return value: `true` if success, `false` otherwise * * Since: 13.0.0 **/ hb_bool_t hb_raster_paint_set_user_data (hb_raster_paint_t *paint, hb_user_data_key_t *key, void *data, hb_destroy_func_t destroy, hb_bool_t replace) { return hb_object_set_user_data (paint, key, data, destroy, replace); } /** * hb_raster_paint_get_user_data: (skip) * @paint: a paint context * @key: the user-data key * * Fetches the user-data associated with the specified key, * attached to the specified paint context. * * Return value: (transfer none): * A pointer to the user data * * Since: 13.0.0 **/ void * hb_raster_paint_get_user_data (const hb_raster_paint_t *paint, hb_user_data_key_t *key) { return hb_object_get_user_data (paint, key); } /** * hb_raster_paint_set_transform: * @paint: a paint context * @xx: xx component of the transform matrix * @yx: yx component of the transform matrix * @xy: xy component of the transform matrix * @yy: yy component of the transform matrix * @dx: x translation * @dy: y translation * * Sets the base 2×3 affine transform that maps from glyph-space * coordinates to pixel-space coordinates. * * Since: 13.0.0 **/ void hb_raster_paint_set_transform (hb_raster_paint_t *paint, float xx, float yx, float xy, float yy, float dx, float dy) { paint->base_transform = {xx, yx, xy, yy, dx, dy}; } /** * hb_raster_paint_get_transform: * @paint: a paint context * @xx: (out) (nullable): xx component of the transform matrix * @yx: (out) (nullable): yx component of the transform matrix * @xy: (out) (nullable): xy component of the transform matrix * @yy: (out) (nullable): yy component of the transform matrix * @dx: (out) (nullable): x translation * @dy: (out) (nullable): y translation * * Gets the current base 2x3 affine transform. * * Since: 13.0.0 **/ void hb_raster_paint_get_transform (const hb_raster_paint_t *paint, float *xx, float *yx, float *xy, float *yy, float *dx, float *dy) { if (xx) *xx = paint->base_transform.xx; if (yx) *yx = paint->base_transform.yx; if (xy) *xy = paint->base_transform.xy; if (yy) *yy = paint->base_transform.yy; if (dx) *dx = paint->base_transform.x0; if (dy) *dy = paint->base_transform.y0; } /** * hb_raster_paint_set_scale_factor: * @paint: a paint context * @x_scale_factor: x-axis minification factor * @y_scale_factor: y-axis minification factor * * Sets post-transform minification factors applied during painting. * Factors larger than 1 shrink the output in pixels. The default is 1. * * Since: 13.0.0 **/ void hb_raster_paint_set_scale_factor (hb_raster_paint_t *paint, float x_scale_factor, float y_scale_factor) { paint->x_scale_factor = x_scale_factor > 0.f ? x_scale_factor : 1.f; paint->y_scale_factor = y_scale_factor > 0.f ? y_scale_factor : 1.f; } /** * hb_raster_paint_get_scale_factor: * @paint: a paint context * @x_scale_factor: (out) (nullable): x-axis minification factor * @y_scale_factor: (out) (nullable): y-axis minification factor * * Fetches the current post-transform minification factors. * * Since: 13.0.0 **/ void hb_raster_paint_get_scale_factor (const hb_raster_paint_t *paint, float *x_scale_factor, float *y_scale_factor) { if (x_scale_factor) *x_scale_factor = paint->x_scale_factor; if (y_scale_factor) *y_scale_factor = paint->y_scale_factor; } /** * hb_raster_paint_set_extents: * @paint: a paint context * @extents: the desired output extents * * Sets the output image extents (pixel rectangle). * * Call this before hb_font_paint_glyph() for each render. * A common pattern is: * |[ * hb_glyph_extents_t gext; * if (hb_font_get_glyph_extents (font, gid, &gext)) * hb_raster_paint_set_glyph_extents (paint, &gext); * ]| * * Since: 13.0.0 **/ void hb_raster_paint_set_extents (hb_raster_paint_t *paint, const hb_raster_extents_t *extents) { paint->fixed_extents = *extents; paint->has_extents = true; if (paint->fixed_extents.stride == 0) paint->fixed_extents.stride = paint->fixed_extents.width * 4; } /** * hb_raster_paint_get_extents: * @paint: a paint context * @extents: (out) (nullable): where to write current extents * * Gets currently configured output extents. * * Return value: `true` if extents are set, `false` otherwise. * * Since: 13.0.0 **/ hb_bool_t hb_raster_paint_get_extents (const hb_raster_paint_t *paint, hb_raster_extents_t *extents) { if (!paint->has_extents) return false; if (extents) *extents = paint->fixed_extents; return true; } /** * hb_raster_paint_set_glyph_extents: * @paint: a paint context * @glyph_extents: glyph extents from hb_font_get_glyph_extents() * * Transforms @glyph_extents with the paint context's base transform and * sets the resulting output image extents. * * This is equivalent to computing a transformed bounding box in pixel * space and calling hb_raster_paint_set_extents(). * * The resulting dimensions are capped at 4096 pixels per side. * * Return value: `true` if transformed extents are non-empty and set; * `false` otherwise. * * Since: 13.0.0 **/ hb_bool_t hb_raster_paint_set_glyph_extents (hb_raster_paint_t *paint, const hb_glyph_extents_t *glyph_extents) { float x0 = (float) glyph_extents->x_bearing; float y0 = (float) glyph_extents->y_bearing; float x1 = (float) glyph_extents->x_bearing + glyph_extents->width; float y1 = (float) glyph_extents->y_bearing + glyph_extents->height; float xmin = hb_min (x0, x1); float xmax = hb_max (x0, x1); float ymin = hb_min (y0, y1); float ymax = hb_max (y0, y1); float px[4] = {xmin, xmin, xmax, xmax}; float py[4] = {ymin, ymax, ymin, ymax}; hb_transform_t<> t = paint->base_transform; paint->apply_scale_factor (t); float tx, ty; t.transform_point (px[0], py[0]); tx = px[0]; ty = py[0]; float tx_min = tx, tx_max = tx; float ty_min = ty, ty_max = ty; for (unsigned i = 1; i < 4; i++) { t.transform_point (px[i], py[i]); tx_min = hb_min (tx_min, px[i]); tx_max = hb_max (tx_max, px[i]); ty_min = hb_min (ty_min, py[i]); ty_max = hb_max (ty_max, py[i]); } int32_t ex0 = hb_clamp_to (floorf (tx_min)); int32_t ey0 = hb_clamp_to (floorf (ty_min)); int32_t ex1 = hb_clamp_to (ceilf (tx_max)); int32_t ey1 = hb_clamp_to (ceilf (ty_max)); if (ex1 <= ex0 || ey1 <= ey0) { paint->fixed_extents = {}; paint->has_extents = false; return false; } paint->fixed_extents = { ex0, ey0, (unsigned) hb_min ((int64_t) ex1 - ex0, (int64_t) HB_RASTER_MAX_AUTO_DIMENSION), (unsigned) hb_min ((int64_t) ey1 - ey0, (int64_t) HB_RASTER_MAX_AUTO_DIMENSION), 0 }; paint->has_extents = true; if (paint->fixed_extents.stride == 0) paint->fixed_extents.stride = paint->fixed_extents.width * 4; return true; } /** * hb_raster_paint_set_foreground: * @paint: a paint context * @foreground: the foreground color * * Sets the foreground color used when paint callbacks request it * (e.g. `is_foreground` in color stops or solid fills). * * Since: 13.0.0 **/ void hb_raster_paint_set_foreground (hb_raster_paint_t *paint, hb_color_t foreground) { paint->foreground = foreground; } /** * hb_raster_paint_get_foreground: * @paint: a paint context * * Returns the foreground color previously set on @paint, or the * default opaque black if none was set. * * Return value: the foreground color. * * Since: 14.2.0 **/ hb_color_t hb_raster_paint_get_foreground (const hb_raster_paint_t *paint) { return paint->foreground; } /** * hb_raster_paint_set_background: * @paint: a paint context * @background: the background color * * Sets the background color for @paint. If set to a non-transparent * value, the rendered image is pre-filled with this color before * glyph content is composited on top. Default is transparent. * * Since: 14.2.0 **/ void hb_raster_paint_set_background (hb_raster_paint_t *paint, hb_color_t background) { paint->background = background; } /** * hb_raster_paint_get_background: * @paint: a paint context * * Returns the background color previously set on @paint, or * transparent if none was set. * * Return value: the background color. * * Since: 14.2.0 **/ hb_color_t hb_raster_paint_get_background (const hb_raster_paint_t *paint) { return paint->background; } /** * hb_raster_paint_set_palette: * @paint: a paint context * @palette: palette index * * Selects which font palette is used when paint callbacks look up * indexed colors. Default is palette 0. * * Since: 14.2.0 **/ void hb_raster_paint_set_palette (hb_raster_paint_t *paint, unsigned palette) { paint->palette = palette; } /** * hb_raster_paint_get_palette: * @paint: a paint context * * Returns the palette index previously set on @paint, or 0 if none * was set. * * Return value: the palette index. * * Since: 14.2.0 **/ unsigned hb_raster_paint_get_palette (const hb_raster_paint_t *paint) { return paint->palette; } /** * hb_raster_paint_clear_custom_palette_colors: * @paint: a paint context. * * Clears all custom palette color overrides previously set on @paint. * * After this call, palette lookups use the selected font palette without * custom override entries. * * Since: 13.0.0 **/ void hb_raster_paint_clear_custom_palette_colors (hb_raster_paint_t *paint) { if (paint->custom_palette) hb_map_clear (paint->custom_palette); } /** * hb_raster_paint_set_custom_palette_color: * @paint: a paint context. * @color_index: color index to override. * @color: replacement color. * * Overrides one font palette color entry for subsequent paint operations. * Overrides are keyed by @color_index and persist on @paint until cleared * (or replaced for the same index). * * These overrides are consulted by paint operations that resolve CPAL * entries. * * Return value: `true` if the override was set; `false` on allocation failure. * * Since: 13.0.0 **/ hb_bool_t hb_raster_paint_set_custom_palette_color (hb_raster_paint_t *paint, unsigned int color_index, hb_color_t color) { if (unlikely (!paint->custom_palette)) { paint->custom_palette = hb_map_create (); if (unlikely (!paint->custom_palette)) return false; } hb_map_set (paint->custom_palette, color_index, color); return hb_map_allocation_successful (paint->custom_palette); } /** * hb_raster_paint_get_funcs: * @paint: a rasterizer paint context. * * Fetches the #hb_paint_funcs_t that renders color glyphs into * @paint. Pass @paint as the @paint_data argument when calling * hb_font_paint_glyph(). * * Return value: (transfer none): * The rasterizer paint functions * * Since: 14.2.0 **/ hb_paint_funcs_t * hb_raster_paint_get_funcs (const hb_raster_paint_t *paint HB_UNUSED) { return static_raster_paint_funcs.get_unconst (); } static hb_bool_t hb_raster_paint_glyph_impl (hb_raster_paint_t *paint, hb_font_t *font, hb_codepoint_t glyph, hb_bool_t fallible) { if (!paint->has_extents) { hb_glyph_extents_t ge; if (hb_font_get_glyph_extents (font, glyph, &ge)) hb_raster_paint_set_glyph_extents (paint, &ge); } hb_paint_funcs_t *funcs = hb_raster_paint_get_funcs (paint); hb_paint_push_transform (funcs, paint, paint->base_transform.xx, paint->base_transform.yx, paint->base_transform.xy, paint->base_transform.yy, paint->base_transform.x0, paint->base_transform.y0); hb_bool_t ret = true; if (fallible) ret = hb_font_paint_glyph_or_fail (font, glyph, funcs, paint, paint->palette, paint->foreground); else hb_font_paint_glyph (font, glyph, funcs, paint, paint->palette, paint->foreground); hb_paint_pop_transform (funcs, paint); return ret; } /** * hb_raster_paint_glyph_or_fail: * @paint: a paint context * @font: font to paint from * @glyph: glyph ID to paint * * Convenience to paint one color glyph. Equivalent to: * * |[ * hb_font_paint_glyph_or_fail (font, glyph, * hb_raster_paint_get_funcs (paint), paint, * palette, foreground); * ]| * * Return value: `true` if painting succeeded, `false` otherwise. * * Since: 14.2.0 **/ hb_bool_t hb_raster_paint_glyph_or_fail (hb_raster_paint_t *paint, hb_font_t *font, hb_codepoint_t glyph) { return hb_raster_paint_glyph_impl (paint, font, glyph, true); } /** * hb_raster_paint_glyph: * @paint: a paint context * @font: font to paint from * @glyph: glyph ID to paint * * Paints one glyph into @paint. Unlike * hb_raster_paint_glyph_or_fail(), glyphs with no color paint data * fall back to a synthesized foreground-colored outline, so any * glyph with an outline or bitmap image produces output. * * Since: 14.2.0 **/ void hb_raster_paint_glyph (hb_raster_paint_t *paint, hb_font_t *font, hb_codepoint_t glyph) { hb_raster_paint_glyph_impl (paint, font, glyph, false); } /** * hb_raster_paint_render: * @paint: a paint context * * Extracts the rendered image after hb_font_paint_glyph() has * completed. The paint context's surface stack is consumed and * the result returned as a new #hb_raster_image_t. Output format is * always @HB_RASTER_FORMAT_BGRA32. * * Call hb_font_paint_glyph() before calling this function. * hb_raster_paint_set_extents() or hb_raster_paint_set_glyph_extents() * must be called before painting; otherwise this function returns `NULL`. * Internal drawing state is cleared here so the same object can * be reused without client-side clearing. * * Return value: (transfer full): * A rendered #hb_raster_image_t. Returns `NULL` if extents were not set * or if allocation/configuration fails. If extents were set but nothing * was painted, returns an empty image. * * Since: 13.0.0 **/ hb_raster_image_t * hb_raster_paint_render (hb_raster_paint_t *paint) { /* Common per-exit state reset. Runs on every return path, * including the early-return failure cases below. */ HB_SCOPE_GUARD (hb_raster_paint_clear (paint)); if (unlikely (!paint->has_extents)) return nullptr; hb_raster_image_t *result; if (paint->surface_stack.length) { result = paint->surface_stack[0]; /* Release any remaining group surfaces (shouldn't happen with * well-formed paint calls, but be safe). */ for (unsigned i = 1; i < paint->surface_stack.length; i++) paint->release_surface (paint->surface_stack[i]); paint->surface_stack.clear (); } else { result = paint->acquire_surface (); if (unlikely (!result)) return nullptr; } return result; } /** * hb_raster_paint_clear: * @paint: a paint context * * Discards accumulated paint output so @paint can be reused for * another render. User configuration (base transform, scale * factors, foreground, custom palette colors) is preserved. Call * hb_raster_paint_reset() to also reset user configuration to * defaults. * * Since: 14.2.0 **/ void hb_raster_paint_clear (hb_raster_paint_t *paint) { paint->fixed_extents = {}; paint->has_extents = false; paint->transform_stack.clear (); paint->release_all_clips (); for (auto *s : paint->surface_stack) paint->release_surface (s); paint->surface_stack.clear (); paint->recharge_budget (); hb_raster_draw_reset (paint->clip_rdr); } /** * hb_raster_paint_reset: * @paint: a paint context * * Resets the paint context to its initial state, clearing all * configuration while preserving internal image caches. * * Since: 13.0.0 **/ void hb_raster_paint_reset (hb_raster_paint_t *paint) { paint->base_transform = {1, 0, 0, 1, 0, 0}; paint->x_scale_factor = 1.f; paint->y_scale_factor = 1.f; paint->foreground = HB_COLOR (0, 0, 0, 255); paint->budget = HB_BUDGET_DEFAULT; hb_raster_paint_clear_custom_palette_colors (paint); hb_raster_paint_clear (paint); } /** * hb_raster_paint_recycle_image: * @paint: a paint context * @image: a raster image to recycle * * Recycles @image for reuse by subsequent render calls. * The caller transfers ownership of @image to @paint. * * Since: 13.0.0 **/ void hb_raster_paint_recycle_image (hb_raster_paint_t *paint, hb_raster_image_t *image) { paint->release_surface (image); }