/* * 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 */ #ifndef HB_RASTER_PAINT_HH #define HB_RASTER_PAINT_HH #include "hb.hh" #include "hb-raster-image.hh" #include "hb-geometry.hh" /* hb_raster_clip_t — alpha mask for clipping */ struct hb_raster_clip_t { hb_vector_t alpha; /* A8 mask, same extents as root surface */ unsigned width = 0; unsigned height = 0; unsigned stride = 0; /* Fast path: simple rectangle (no alpha buffer needed) */ bool is_rect = true; int rect_x0 = 0, rect_y0 = 0; int rect_x1 = 0, rect_y1 = 0; /* Bounding box of non-zero alpha region (valid for both rect and mask) */ unsigned min_x = 0, min_y = 0; unsigned max_x = 0, max_y = 0; void init_full (unsigned w, unsigned h) { width = w; height = h; stride = (w + 3u) & ~3u; is_rect = true; rect_x0 = 0; rect_y0 = 0; rect_x1 = (int) w; rect_y1 = (int) h; min_x = 0; min_y = 0; max_x = w; max_y = h; } void update_bounds_from_rect () { min_x = (unsigned) hb_max (rect_x0, 0); min_y = (unsigned) hb_max (rect_y0, 0); max_x = (unsigned) hb_max (hb_min (rect_x1, (int) width), 0); max_y = (unsigned) hb_max (hb_min (rect_y1, (int) height), 0); } uint8_t get_alpha (unsigned x, unsigned y) const { if (is_rect) return ((int) x >= rect_x0 && (int) x < rect_x1 && (int) y >= rect_y0 && (int) y < rect_y1) ? 255 : 0; if (x >= width || y >= height) return 0; if (stride < width) return 0; if (height && stride > (size_t) -1 / height) return 0; size_t idx = (size_t) y * stride + x; if (!alpha.arrayZ || idx >= alpha.length) return 0; return alpha[idx]; } bool has_valid_alpha_mask () const { if (is_rect) return true; if (!alpha.arrayZ || stride < width) return false; if (height && stride > (size_t) -1 / height) return false; return (size_t) stride * height <= alpha.length; } }; /* hb_raster_paint_t — color glyph paint context */ struct hb_raster_paint_t { hb_object_header_t header; /* Configuration */ hb_transform_t<> base_transform = {1, 0, 0, 1, 0, 0}; float x_scale_factor = 1.f; float y_scale_factor = 1.f; hb_raster_extents_t fixed_extents = {}; bool has_extents = false; hb_color_t foreground = HB_COLOR (0, 0, 0, 255); hb_color_t background = HB_COLOR (0, 0, 0, 0); unsigned palette = 0; hb_map_t *custom_palette = nullptr; /* Stacks */ hb_vector_t> transform_stack; hb_vector_t clip_stack; hb_vector_t clip_cache; hb_vector_t surface_stack; /* Cached surface pool (freelist for reuse across push/pop group) */ hb_vector_t surface_cache; hb_vector_t scratch_color_stops; /* Internal rasterizer for clip-to-glyph */ hb_raster_draw_t *clip_rdr = nullptr; /* Two work budgets for the current paint session, both reset by * hb_raster_paint_clear(), so per-node costs cannot multiply with the * paint-graph traversal limits of the font tables driving us (e.g. * COLR). They bound different resources and must not share a counter: * * - The outline budget (policy + live) bounds glyph outline traversal * and curve flattening. It is the public draw/paint budget, seeded * into the clip rasterizer through the public draw-budget API and * read back after each glyph. Default HB_BUDGET_GLYPH. * * - The pixel budget bounds scanline/fill work, which scales with * surface area rather than outline complexity. It is sized from the * surface once known and is internal (not exposed through the API); * an unlimited outline policy disables it too. */ int64_t budget = HB_BUDGET_DEFAULT; int64_t budget_remaining = HB_BUDGET_GLYPH; int64_t pixel_remaining = HB_BUDGET_RASTER_PIXELS; /* Helpers */ int64_t get_default_pixel_budget () const { return hb_max ((int64_t) HB_BUDGET_RASTER_PIXELS, (int64_t) HB_BUDGET_RASTER_PAINT_PASSES * fixed_extents.width * fixed_extents.height); } void recharge_budget () { budget_remaining = budget == HB_BUDGET_DEFAULT ? (int64_t) HB_BUDGET_GLYPH : budget; pixel_remaining = budget == HB_BUDGET_UNLIMITED ? budget : (surface_stack.length ? get_default_pixel_budget () : (int64_t) HB_BUDGET_RASTER_PIXELS); } /* Returns whether the pixel operation should proceed. Allows a single * overshoot past zero; callers skip work once the budget is spent. */ bool charge_work (int64_t work) { if (unlikely (pixel_remaining < 0)) return false; pixel_remaining -= work; return true; } /* Reject work that would exceed the remaining budget before it starts. */ bool precharge_work (int64_t work) { if (unlikely (pixel_remaining < work)) { pixel_remaining = -1; return false; } pixel_remaining -= work; return true; } hb_raster_image_t *acquire_surface () { hb_raster_image_t *img; if (surface_cache.length) img = surface_cache.pop (); else { img = hb_raster_image_create_or_fail (); if (unlikely (!img)) return nullptr; } if (unlikely (!img->configure (HB_RASTER_FORMAT_BGRA32, fixed_extents))) { hb_raster_image_destroy (img); return nullptr; } img->clear (); return img; } void release_surface (hb_raster_image_t *img) { if (!surface_cache.push_or_fail (img)) hb_raster_image_destroy (img); } hb_raster_clip_t acquire_clip (unsigned w, unsigned h) { hb_raster_clip_t clip; if (clip_cache.length) clip = clip_cache.pop (); clip.width = w; clip.height = h; clip.stride = (w + 3u) & ~3u; clip.is_rect = false; clip.rect_x0 = clip.rect_y0 = 0; clip.rect_x1 = clip.rect_y1 = 0; clip.min_x = clip.min_y = clip.max_x = clip.max_y = 0; return clip; } void release_clip (hb_raster_clip_t &&clip) { if (clip.alpha.arrayZ) clip_cache.push (std::move (clip)); } void release_all_clips () { while (clip_stack.length) release_clip (clip_stack.pop ()); } hb_raster_image_t *current_surface () { return surface_stack.length ? surface_stack.tail () : nullptr; } hb_raster_clip_t ¤t_clip () { return clip_stack.tail (); } hb_transform_t<> current_transform () { if (unlikely (!transform_stack.length)) return {1, 0, 0, 1, 0, 0}; return transform_stack.tail (); } void apply_scale_factor (hb_transform_t<> &t) const { t.xx /= x_scale_factor; t.xy /= x_scale_factor; t.x0 /= x_scale_factor; t.yx /= y_scale_factor; t.yy /= y_scale_factor; t.y0 /= y_scale_factor; } hb_transform_t<> current_effective_transform () { hb_transform_t<> t = current_transform (); apply_scale_factor (t); return t; } bool fetch_color_stops (hb_color_line_t *color_line) { unsigned count = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr); if (unlikely (!count || !scratch_color_stops.resize (count))) { scratch_color_stops.resize (0); return false; } hb_color_line_get_color_stops (color_line, 0, &count, scratch_color_stops.arrayZ); return true; } }; #endif /* HB_RASTER_PAINT_HH */