289 lines
8.3 KiB
C++
289 lines
8.3 KiB
C++
/*
|
|
* 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<uint8_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<hb_transform_t<>> transform_stack;
|
|
hb_vector_t<hb_raster_clip_t> clip_stack;
|
|
hb_vector_t<hb_raster_clip_t> clip_cache;
|
|
hb_vector_t<hb_raster_image_t *> surface_stack;
|
|
|
|
/* Cached surface pool (freelist for reuse across push/pop group) */
|
|
hb_vector_t<hb_raster_image_t *> surface_cache;
|
|
hb_vector_t<hb_color_stop_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 */
|