| /* |
| * Copyright (C) 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-gpu.h" |
| #include "hb-gpu-draw.hh" |
| #include "hb-gpu-cu2qu.hh" |
| #include "hb-machinery.hh" |
| |
| #include <cmath> |
| |
| /* ---- Accumulator ---- */ |
| |
| static void |
| acc_update_extents (hb_gpu_draw_t *g, double x, double y) |
| { |
| g->ext_min_x = hb_min (g->ext_min_x, x); |
| g->ext_min_y = hb_min (g->ext_min_y, y); |
| g->ext_max_x = hb_max (g->ext_max_x, x); |
| g->ext_max_y = hb_max (g->ext_max_y, y); |
| } |
| |
| static void |
| acc_emit (hb_gpu_draw_t *g, |
| bool contour_start, |
| double p1x, double p1y, |
| double p2x, double p2y, |
| double p3x, double p3y) |
| { |
| hb_gpu_curve_t c = {p1x, p1y, p2x, p2y, p3x, p3y, contour_start}; |
| if (unlikely (!g->curves.push_or_fail (c))) |
| { |
| g->success = false; |
| return; |
| } |
| |
| g->num_curves++; |
| g->current_x = p3x; |
| g->current_y = p3y; |
| |
| acc_update_extents (g, p1x, p1y); |
| acc_update_extents (g, p2x, p2y); |
| acc_update_extents (g, p3x, p3y); |
| } |
| |
| static void |
| acc_emit_conic (hb_gpu_draw_t *g, |
| double cx, double cy, |
| double x, double y) |
| { |
| if (g->current_x == x && g->current_y == y) |
| return; |
| |
| bool contour_start = g->need_moveto; |
| if (g->need_moveto) { |
| g->start_x = g->current_x; |
| g->start_y = g->current_y; |
| g->need_moveto = false; |
| } |
| |
| acc_emit (g, contour_start, |
| g->current_x, g->current_y, cx, cy, x, y); |
| } |
| |
| void |
| hb_gpu_draw_t::acc_move_to (double x, double y) |
| { |
| if (unlikely (!success)) |
| return; |
| |
| need_moveto = true; |
| current_x = x; |
| current_y = y; |
| } |
| |
| void |
| hb_gpu_draw_t::acc_line_to (double x, double y) |
| { |
| if (unlikely (!success)) |
| return; |
| |
| acc_emit_conic (this, current_x, current_y, x, y); |
| } |
| |
| void |
| hb_gpu_draw_t::acc_conic_to (double cx, double cy, double x, double y) |
| { |
| if (unlikely (!success)) |
| return; |
| |
| acc_emit_conic (this, cx, cy, x, y); |
| } |
| |
| void |
| hb_gpu_draw_t::acc_close_path () |
| { |
| if (unlikely (!success)) |
| return; |
| |
| if (!need_moveto && |
| (current_x != start_x || current_y != start_y)) |
| acc_line_to (start_x, start_y); |
| } |
| |
| |
| void |
| hb_gpu_draw_t::acc_cubic_to (double c1x, double c1y, |
| double c2x, double c2y, |
| double x, double y) |
| { |
| if (unlikely (!success)) |
| return; |
| |
| double c0x = current_x, c0y = current_y; |
| |
| if (c0x == x && c0y == y && |
| c1x == x && c1y == y && |
| c2x == x && c2y == y) |
| return; |
| |
| /* Degenerate cubic: all control points are collinear. |
| * Emit as a line instead of running cu2qu subdivision. */ |
| double dx = x - c0x, dy = y - c0y; |
| double len_sq = dx * dx + dy * dy; |
| if (len_sq > 0.) |
| { |
| double inv = 1.0 / len_sq; |
| double cross1 = (c1x - c0x) * dy - (c1y - c0y) * dx; |
| double cross2 = (c2x - c0x) * dy - (c2y - c0y) * dx; |
| if (cross1 * cross1 * inv <= HB_GPU_CU2QU_TOLERANCE * HB_GPU_CU2QU_TOLERANCE && |
| cross2 * cross2 * inv <= HB_GPU_CU2QU_TOLERANCE * HB_GPU_CU2QU_TOLERANCE) |
| { |
| acc_line_to (x, y); |
| return; |
| } |
| } |
| |
| hb_gpu_cubic_to_quadratics (this, |
| {c0x, c0y}, {c1x, c1y}, |
| {c2x, c2y}, {x, y}, |
| HB_GPU_CU2QU_TOLERANCE, 0); |
| } |
| |
| |
| /* ---- Draw funcs ---- */ |
| |
| static void |
| hb_gpu_draw_move_to (hb_draw_funcs_t *dfuncs HB_UNUSED, |
| void *data, |
| hb_draw_state_t *st HB_UNUSED, |
| float to_x, float to_y, |
| void *user_data HB_UNUSED) |
| { |
| hb_gpu_draw_t *g = (hb_gpu_draw_t *) data; |
| g->acc_close_path (); |
| g->acc_move_to ((double) to_x, (double) to_y); |
| } |
| |
| static void |
| hb_gpu_draw_line_to (hb_draw_funcs_t *dfuncs HB_UNUSED, |
| void *data, |
| hb_draw_state_t *st HB_UNUSED, |
| float to_x, float to_y, |
| void *user_data HB_UNUSED) |
| { |
| hb_gpu_draw_t *g = (hb_gpu_draw_t *) data; |
| g->acc_line_to ((double) to_x, (double) to_y); |
| } |
| |
| static void |
| hb_gpu_draw_quadratic_to (hb_draw_funcs_t *dfuncs HB_UNUSED, |
| void *data, |
| hb_draw_state_t *st HB_UNUSED, |
| float control_x, float control_y, |
| float to_x, float to_y, |
| void *user_data HB_UNUSED) |
| { |
| hb_gpu_draw_t *g = (hb_gpu_draw_t *) data; |
| g->acc_conic_to ((double) control_x, (double) control_y, |
| (double) to_x, (double) to_y); |
| } |
| |
| static void |
| hb_gpu_draw_cubic_to (hb_draw_funcs_t *dfuncs HB_UNUSED, |
| void *data, |
| hb_draw_state_t *st HB_UNUSED, |
| float control1_x, float control1_y, |
| float control2_x, float control2_y, |
| float to_x, float to_y, |
| void *user_data HB_UNUSED) |
| { |
| hb_gpu_draw_t *g = (hb_gpu_draw_t *) data; |
| g->acc_cubic_to ((double) control1_x, (double) control1_y, |
| (double) control2_x, (double) control2_y, |
| (double) to_x, (double) to_y); |
| } |
| |
| static void |
| hb_gpu_draw_close_path (hb_draw_funcs_t *dfuncs HB_UNUSED, |
| void *data, |
| hb_draw_state_t *st HB_UNUSED, |
| void *user_data HB_UNUSED) |
| { |
| hb_gpu_draw_t *g = (hb_gpu_draw_t *) data; |
| g->acc_close_path (); |
| } |
| |
| static inline void free_static_gpu_draw_funcs (); |
| |
| static struct hb_gpu_draw_funcs_lazy_loader_t : hb_draw_funcs_lazy_loader_t<hb_gpu_draw_funcs_lazy_loader_t> |
| { |
| static hb_draw_funcs_t *create () |
| { |
| hb_draw_funcs_t *funcs = hb_draw_funcs_create (); |
| |
| hb_draw_funcs_set_move_to_func (funcs, hb_gpu_draw_move_to, nullptr, nullptr); |
| hb_draw_funcs_set_line_to_func (funcs, hb_gpu_draw_line_to, nullptr, nullptr); |
| hb_draw_funcs_set_quadratic_to_func (funcs, hb_gpu_draw_quadratic_to, nullptr, nullptr); |
| hb_draw_funcs_set_cubic_to_func (funcs, hb_gpu_draw_cubic_to, nullptr, nullptr); |
| hb_draw_funcs_set_close_path_func (funcs, hb_gpu_draw_close_path, nullptr, nullptr); |
| |
| hb_draw_funcs_make_immutable (funcs); |
| |
| hb_atexit (free_static_gpu_draw_funcs); |
| |
| return funcs; |
| } |
| } static_gpu_draw_funcs; |
| |
| static inline void |
| free_static_gpu_draw_funcs () |
| { |
| static_gpu_draw_funcs.free_instance (); |
| } |
| |
| |
| /* ---- Encode ---- */ |
| |
| struct hb_gpu_texel_t |
| { |
| int16_t r, g, b, a; |
| }; |
| |
| struct hb_gpu_blob_data_t |
| { |
| char *buf; |
| unsigned capacity; |
| }; |
| |
| static void |
| _hb_gpu_blob_data_destroy (void *user_data) |
| { |
| auto *bd = (hb_gpu_blob_data_t *) user_data; |
| hb_free (bd->buf); |
| hb_free (bd); |
| } |
| |
| static hb_position_t |
| clamp_to_hb_position (double v) |
| { |
| return (hb_position_t) hb_clamp (v, |
| (double) hb_int_min (hb_position_t), |
| (double) hb_int_max (hb_position_t)); |
| } |
| |
| static int16_t |
| quantize (double v) |
| { |
| return (int16_t) round (v * HB_GPU_UNITS_PER_EM); |
| } |
| |
| static int16_t |
| quantize_down (double v) |
| { |
| return (int16_t) floor (v * HB_GPU_UNITS_PER_EM); |
| } |
| |
| static int16_t |
| quantize_up (double v) |
| { |
| return (int16_t) ceil (v * HB_GPU_UNITS_PER_EM); |
| } |
| |
| static double |
| dequantize (int16_t v) |
| { |
| return (double) v / HB_GPU_UNITS_PER_EM; |
| } |
| |
| static bool |
| quantize_down_fits_i16 (double v) |
| { |
| double q = floor (v * HB_GPU_UNITS_PER_EM); |
| return q >= INT16_MIN && |
| q <= INT16_MAX; |
| } |
| |
| static bool |
| quantize_up_fits_i16 (double v) |
| { |
| double q = ceil (v * HB_GPU_UNITS_PER_EM); |
| return q >= INT16_MIN && |
| q <= INT16_MAX; |
| } |
| |
| static int16_t |
| encode_offset (unsigned offset) |
| { |
| return (int16_t) (offset - 32768u); |
| } |
| |
| static hb_gpu_encode_curve_info_t |
| encode_curve_info (const hb_gpu_curve_t *c) |
| { |
| hb_gpu_encode_curve_info_t info; |
| |
| info.min_x = hb_min (hb_min (c->p1x, c->p2x), c->p3x); |
| info.max_x = hb_max (hb_max (c->p1x, c->p2x), c->p3x); |
| info.min_y = hb_min (hb_min (c->p1y, c->p2y), c->p3y); |
| info.max_y = hb_max (hb_max (c->p1y, c->p2y), c->p3y); |
| info.is_horizontal = c->p1y == c->p2y && c->p2y == c->p3y; |
| info.is_vertical = c->p1x == c->p2x && c->p2x == c->p3x; |
| info.hband_lo = 0; |
| info.hband_hi = -1; |
| info.vband_lo = 0; |
| info.vband_hi = -1; |
| |
| return info; |
| } |
| |
| /** |
| * hb_gpu_draw_encode: |
| * @draw: a GPU shape encoder |
| * @extents: (out) (nullable): where to store the computed glyph |
| * extents (in font units, Y-up). Pass `NULL` if not |
| * needed. |
| * |
| * Encodes the accumulated outlines into a compact blob |
| * suitable for GPU rendering. The blob data is an array of |
| * RGBA16I texels (8 bytes each) to be uploaded to a texture |
| * buffer object. |
| * |
| * The returned blob owns its own copy of the data. On success |
| * @draw is auto-cleared so it can be reused for the next glyph; |
| * user configuration (font scale) is preserved. |
| * |
| * Return value: (transfer full): |
| * An #hb_blob_t containing the encoded data, or |
| * `NULL` if encoding fails. |
| * |
| * Since: 14.0.0 |
| **/ |
| static void |
| _hb_gpu_draw_get_extents (hb_gpu_draw_t *draw, |
| hb_glyph_extents_t *extents); |
| |
| hb_blob_t * |
| hb_gpu_draw_encode (hb_gpu_draw_t *draw, |
| hb_glyph_extents_t *extents) |
| { |
| /* Capture computed extents before auto-clear wipes them. */ |
| if (extents) |
| _hb_gpu_draw_get_extents (draw, extents); |
| HB_SCOPE_GUARD (hb_gpu_draw_clear (draw)); |
| |
| if (unlikely (!draw->success)) |
| return nullptr; |
| |
| const hb_gpu_curve_t *curves = draw->curves.arrayZ; |
| unsigned num_curves = draw->curves.length; |
| |
| if (num_curves == 0) |
| return hb_blob_get_empty (); |
| |
| hb_gpu_encode_scratch_t &s = draw->scratch; |
| |
| s.curve_infos.reset_if_error (); |
| s.hband_curve_counts.reset_if_error (); |
| s.vband_curve_counts.reset_if_error (); |
| s.hband_offsets.reset_if_error (); |
| s.vband_offsets.reset_if_error (); |
| s.hband_curves.reset_if_error (); |
| s.hband_curves_asc.reset_if_error (); |
| s.vband_curves.reset_if_error (); |
| s.vband_curves_asc.reset_if_error (); |
| s.hband_cursors.reset_if_error (); |
| s.vband_cursors.reset_if_error (); |
| s.curve_texel_offset.reset_if_error (); |
| |
| /* Compute per-curve info and extents */ |
| if (unlikely (!s.curve_infos.resize (num_curves))) |
| return nullptr; |
| |
| int16_t min_x_q = quantize_down (draw->ext_min_x); |
| int16_t min_y_q = quantize_down (draw->ext_min_y); |
| int16_t max_x_q = quantize_up (draw->ext_max_x); |
| int16_t max_y_q = quantize_up (draw->ext_max_y); |
| |
| double min_x = dequantize (min_x_q); |
| double min_y = dequantize (min_y_q); |
| double max_x = dequantize (max_x_q); |
| double max_y = dequantize (max_y_q); |
| |
| for (unsigned i = 0; i < num_curves; i++) |
| s.curve_infos.arrayZ[i] = encode_curve_info (&curves[i]); |
| |
| /* Choose number of bands (capped at 16 per Slug paper) */ |
| unsigned num_hbands = hb_min (num_curves, 16u); |
| unsigned num_vbands = hb_min (num_curves, 16u); |
| num_hbands = hb_max (num_hbands, 1u); |
| num_vbands = hb_max (num_vbands, 1u); |
| |
| double height = max_y - min_y; |
| double width = max_x - min_x; |
| |
| if (height <= 0) num_hbands = 1; |
| if (width <= 0) num_vbands = 1; |
| |
| double hband_size = height / num_hbands; |
| double vband_size = width / num_vbands; |
| |
| if (unlikely (!s.hband_curve_counts.resize (num_hbands) || |
| !s.vband_curve_counts.resize (num_vbands))) |
| return nullptr; |
| |
| for (unsigned b = 0; b < num_hbands; b++) s.hband_curve_counts.arrayZ[b] = 0; |
| for (unsigned b = 0; b < num_vbands; b++) s.vband_curve_counts.arrayZ[b] = 0; |
| |
| for (unsigned i = 0; i < num_curves; i++) |
| { |
| hb_gpu_encode_curve_info_t &info = s.curve_infos.arrayZ[i]; |
| |
| if (!info.is_horizontal) |
| { |
| if (height > 0) { |
| info.hband_lo = (int) floor ((info.min_y - min_y) / hband_size); |
| info.hband_hi = (int) floor ((info.max_y - min_y) / hband_size); |
| info.hband_lo = hb_max (info.hband_lo, 0); |
| info.hband_hi = hb_min (info.hband_hi, (int) num_hbands - 1); |
| for (int b = info.hband_lo; b <= info.hband_hi; b++) |
| s.hband_curve_counts.arrayZ[b]++; |
| } else { |
| info.hband_lo = 0; |
| info.hband_hi = 0; |
| s.hband_curve_counts.arrayZ[0]++; |
| } |
| } |
| |
| if (!info.is_vertical) |
| { |
| if (width > 0) { |
| info.vband_lo = (int) floor ((info.min_x - min_x) / vband_size); |
| info.vband_hi = (int) floor ((info.max_x - min_x) / vband_size); |
| info.vband_lo = hb_max (info.vband_lo, 0); |
| info.vband_hi = hb_min (info.vband_hi, (int) num_vbands - 1); |
| for (int b = info.vband_lo; b <= info.vband_hi; b++) |
| s.vband_curve_counts.arrayZ[b]++; |
| } else { |
| info.vband_lo = 0; |
| info.vband_hi = 0; |
| s.vband_curve_counts.arrayZ[0]++; |
| } |
| } |
| } |
| |
| if (unlikely (!s.hband_offsets.resize (num_hbands) || |
| !s.vband_offsets.resize (num_vbands))) |
| return nullptr; |
| |
| unsigned total_hband_indices = 0; |
| unsigned total_vband_indices = 0; |
| |
| for (unsigned b = 0; b < num_hbands; b++) { |
| s.hband_offsets.arrayZ[b] = total_hband_indices; |
| if (unlikely (hb_unsigned_add_overflows (total_hband_indices, |
| s.hband_curve_counts.arrayZ[b], |
| &total_hband_indices))) |
| return nullptr; |
| } |
| |
| for (unsigned b = 0; b < num_vbands; b++) { |
| s.vband_offsets.arrayZ[b] = total_vband_indices; |
| if (unlikely (hb_unsigned_add_overflows (total_vband_indices, |
| s.vband_curve_counts.arrayZ[b], |
| &total_vband_indices))) |
| return nullptr; |
| } |
| |
| /* Assign curves to bands */ |
| if (unlikely (!s.hband_curves.resize (total_hband_indices) || |
| !s.hband_curves_asc.resize (total_hband_indices) || |
| !s.vband_curves.resize (total_vband_indices) || |
| !s.vband_curves_asc.resize (total_vband_indices) || |
| !s.hband_cursors.resize (num_hbands) || |
| !s.vband_cursors.resize (num_vbands))) |
| return nullptr; |
| |
| for (unsigned b = 0; b < num_hbands; b++) s.hband_cursors.arrayZ[b] = s.hband_offsets.arrayZ[b]; |
| for (unsigned b = 0; b < num_vbands; b++) s.vband_cursors.arrayZ[b] = s.vband_offsets.arrayZ[b]; |
| |
| for (unsigned i = 0; i < num_curves; i++) |
| { |
| const hb_gpu_encode_curve_info_t &info = s.curve_infos.arrayZ[i]; |
| |
| for (int b = info.hband_lo; b <= info.hband_hi; b++) { |
| unsigned idx = s.hband_cursors.arrayZ[b]++; |
| s.hband_curves.arrayZ[idx] = i; |
| s.hband_curves_asc.arrayZ[idx] = i; |
| } |
| |
| for (int b = info.vband_lo; b <= info.vband_hi; b++) { |
| unsigned idx = s.vband_cursors.arrayZ[b]++; |
| s.vband_curves.arrayZ[idx] = i; |
| s.vband_curves_asc.arrayZ[idx] = i; |
| } |
| } |
| |
| /* Sort: descending by max, ascending by min */ |
| const hb_gpu_encode_curve_info_t *infos = s.curve_infos.arrayZ; |
| |
| for (unsigned b = 0; b < num_hbands; b++) |
| { |
| unsigned off = s.hband_offsets.arrayZ[b]; |
| unsigned count = s.hband_curve_counts.arrayZ[b]; |
| s.hband_curves.as_array ().sub_array (off, count) |
| .qsort ([infos] (const unsigned &a, const unsigned &b) { |
| return infos[a].max_x > infos[b].max_x; |
| }); |
| s.hband_curves_asc.as_array ().sub_array (off, count) |
| .qsort ([infos] (const unsigned &a, const unsigned &b) { |
| return infos[a].min_x < infos[b].min_x; |
| }); |
| } |
| |
| for (unsigned b = 0; b < num_vbands; b++) |
| { |
| unsigned off = s.vband_offsets.arrayZ[b]; |
| unsigned count = s.vband_curve_counts.arrayZ[b]; |
| s.vband_curves.as_array ().sub_array (off, count) |
| .qsort ([infos] (const unsigned &a, const unsigned &b) { |
| return infos[a].max_y > infos[b].max_y; |
| }); |
| s.vband_curves_asc.as_array ().sub_array (off, count) |
| .qsort ([infos] (const unsigned &a, const unsigned &b) { |
| return infos[a].min_y < infos[b].min_y; |
| }); |
| } |
| |
| /* Compute sizes */ |
| unsigned total_curve_indices; |
| if (unlikely (hb_unsigned_add_overflows (total_hband_indices, |
| total_vband_indices, |
| &total_curve_indices) || |
| hb_unsigned_mul_overflows (total_curve_indices, |
| 2, |
| &total_curve_indices))) |
| return nullptr; |
| |
| unsigned header_len = 2; |
| |
| unsigned num_contour_breaks = 0; |
| for (unsigned i = 0; i + 1 < num_curves; i++) |
| if (curves[i + 1].contour_start) |
| num_contour_breaks++; |
| |
| unsigned curve_data_len; |
| if (unlikely (hb_unsigned_add_overflows (num_curves, |
| num_contour_breaks, |
| &curve_data_len) || |
| hb_unsigned_add_overflows (curve_data_len, |
| 1, |
| &curve_data_len))) |
| return nullptr; |
| |
| unsigned band_headers_len; |
| if (unlikely (hb_unsigned_add_overflows (num_hbands, |
| num_vbands, |
| &band_headers_len))) |
| return nullptr; |
| |
| unsigned total_len = header_len; |
| if (unlikely (hb_unsigned_add_overflows (total_len, |
| band_headers_len, |
| &total_len) || |
| hb_unsigned_add_overflows (total_len, |
| total_curve_indices, |
| &total_len) || |
| hb_unsigned_add_overflows (total_len, |
| curve_data_len, |
| &total_len))) |
| return nullptr; |
| |
| /* Validate fits in uint16 offsets (stored as int16 with bias) */ |
| if (total_len > (unsigned) UINT16_MAX + 1u) |
| return nullptr; |
| |
| if (!quantize_down_fits_i16 (draw->ext_min_x) || |
| !quantize_down_fits_i16 (draw->ext_min_y) || |
| !quantize_up_fits_i16 (draw->ext_max_x) || |
| !quantize_up_fits_i16 (draw->ext_max_y)) |
| return nullptr; |
| |
| /* Allocate or reuse encode buffer */ |
| unsigned needed_bytes; |
| if (unlikely (hb_unsigned_mul_overflows (total_len, |
| sizeof (hb_gpu_texel_t), |
| &needed_bytes))) |
| return nullptr; |
| hb_gpu_texel_t *buf = nullptr; |
| unsigned buf_capacity = 0; |
| hb_gpu_blob_data_t *recycled_bd = nullptr; |
| char *replaced_recycled_buf = nullptr; |
| |
| if (draw->recycled_blob && |
| draw->recycled_blob->destroy == _hb_gpu_blob_data_destroy) |
| { |
| recycled_bd = (hb_gpu_blob_data_t *) draw->recycled_blob->user_data; |
| if (recycled_bd->capacity >= needed_bytes) |
| { |
| buf = (hb_gpu_texel_t *) (void *) recycled_bd->buf; |
| buf_capacity = recycled_bd->capacity; |
| } |
| else |
| { |
| unsigned alloc_bytes = needed_bytes; |
| if (unlikely (hb_unsigned_add_overflows (needed_bytes, |
| needed_bytes / 2, |
| &alloc_bytes))) |
| alloc_bytes = needed_bytes; |
| char *new_buf = (char *) hb_realloc (recycled_bd->buf, alloc_bytes); |
| if (new_buf) |
| { |
| recycled_bd->buf = new_buf; |
| recycled_bd->capacity = alloc_bytes; |
| buf = (hb_gpu_texel_t *) (void *) new_buf; |
| buf_capacity = alloc_bytes; |
| } |
| else |
| replaced_recycled_buf = recycled_bd->buf; |
| } |
| } |
| |
| if (!buf) |
| { |
| buf_capacity = needed_bytes; |
| buf = (hb_gpu_texel_t *) hb_malloc (needed_bytes); |
| if (unlikely (!buf)) |
| return nullptr; |
| } |
| |
| unsigned curve_data_offset = header_len; |
| if (unlikely (hb_unsigned_add_overflows (curve_data_offset, |
| band_headers_len, |
| &curve_data_offset) || |
| hb_unsigned_add_overflows (curve_data_offset, |
| total_curve_indices, |
| &curve_data_offset))) |
| { |
| if (!recycled_bd || buf != (hb_gpu_texel_t *) (void *) recycled_bd->buf) |
| hb_free (buf); |
| return nullptr; |
| } |
| |
| /* Pack header */ |
| buf[0].r = min_x_q; |
| buf[0].g = min_y_q; |
| buf[0].b = max_x_q; |
| buf[0].a = max_y_q; |
| buf[1].r = (int16_t) num_hbands; |
| buf[1].g = (int16_t) num_vbands; |
| buf[1].b = (int16_t) hb_clamp (draw->x_scale, -32768, 32767); |
| buf[1].a = (int16_t) hb_clamp (draw->y_scale, -32768, 32767); |
| |
| /* Pack curve data with shared endpoints */ |
| if (unlikely (!s.curve_texel_offset.resize (num_curves))) |
| { |
| if (!recycled_bd || buf != (hb_gpu_texel_t *) (void *) recycled_bd->buf) |
| hb_free (buf); |
| return nullptr; |
| } |
| |
| unsigned texel = curve_data_offset; |
| |
| for (unsigned i = 0; i < num_curves; i++) |
| { |
| bool contour_start = curves[i].contour_start; |
| |
| if (contour_start) { |
| s.curve_texel_offset.arrayZ[i] = texel; |
| buf[texel].r = quantize (curves[i].p1x); |
| buf[texel].g = quantize (curves[i].p1y); |
| buf[texel].b = quantize (curves[i].p2x); |
| buf[texel].a = quantize (curves[i].p2y); |
| texel++; |
| } else { |
| s.curve_texel_offset.arrayZ[i] = texel - 1; |
| } |
| |
| bool has_next = i + 1 < num_curves && |
| !curves[i + 1].contour_start; |
| |
| buf[texel].r = quantize (curves[i].p3x); |
| buf[texel].g = quantize (curves[i].p3y); |
| if (has_next) { |
| buf[texel].b = quantize (curves[i + 1].p2x); |
| buf[texel].a = quantize (curves[i + 1].p2y); |
| } else { |
| buf[texel].b = 0; |
| buf[texel].a = 0; |
| } |
| texel++; |
| } |
| |
| /* Pack band headers and curve indices */ |
| unsigned index_offset = header_len + band_headers_len; |
| |
| for (unsigned b = 0; b < num_hbands; b++) |
| { |
| int16_t hband_split; |
| { |
| unsigned off = s.hband_offsets.arrayZ[b]; |
| unsigned n = s.hband_curve_counts.arrayZ[b]; |
| unsigned best_worst = n; |
| double best_split = (min_x + max_x) * 0.5; |
| unsigned left_count = n; |
| for (unsigned ci = 0; ci < n; ci++) { |
| double split = s.curve_infos.arrayZ[s.hband_curves.arrayZ[off + ci]].max_x; |
| unsigned right_count = ci + 1; |
| while (left_count && |
| s.curve_infos.arrayZ[s.hband_curves_asc.arrayZ[off + left_count - 1]].min_x > split) |
| left_count--; |
| unsigned worst = hb_max (right_count, left_count); |
| if (worst < best_worst) { |
| best_worst = worst; |
| best_split = split; |
| } |
| } |
| hband_split = quantize (best_split); |
| } |
| unsigned hdr = header_len + b; |
| unsigned desc_off = index_offset; |
| |
| for (unsigned ci = 0; ci < s.hband_curve_counts.arrayZ[b]; ci++) { |
| buf[index_offset].r = encode_offset (s.curve_texel_offset.arrayZ[s.hband_curves.arrayZ[s.hband_offsets.arrayZ[b] + ci]]); |
| buf[index_offset].g = 0; |
| buf[index_offset].b = 0; |
| buf[index_offset].a = 0; |
| index_offset++; |
| } |
| |
| unsigned asc_off = index_offset; |
| |
| for (unsigned ci = 0; ci < s.hband_curve_counts.arrayZ[b]; ci++) { |
| buf[index_offset].r = encode_offset (s.curve_texel_offset.arrayZ[s.hband_curves_asc.arrayZ[s.hband_offsets.arrayZ[b] + ci]]); |
| buf[index_offset].g = 0; |
| buf[index_offset].b = 0; |
| buf[index_offset].a = 0; |
| index_offset++; |
| } |
| |
| buf[hdr].r = (int16_t) s.hband_curve_counts.arrayZ[b]; |
| buf[hdr].g = encode_offset (desc_off); |
| buf[hdr].b = encode_offset (asc_off); |
| buf[hdr].a = hband_split; |
| } |
| |
| for (unsigned b = 0; b < num_vbands; b++) |
| { |
| int16_t vband_split; |
| { |
| unsigned off = s.vband_offsets.arrayZ[b]; |
| unsigned n = s.vband_curve_counts.arrayZ[b]; |
| unsigned best_worst = n; |
| double best_split = (min_y + max_y) * 0.5; |
| unsigned left_count = n; |
| for (unsigned ci = 0; ci < n; ci++) { |
| double split = s.curve_infos.arrayZ[s.vband_curves.arrayZ[off + ci]].max_y; |
| unsigned right_count = ci + 1; |
| while (left_count && |
| s.curve_infos.arrayZ[s.vband_curves_asc.arrayZ[off + left_count - 1]].min_y > split) |
| left_count--; |
| unsigned worst = hb_max (right_count, left_count); |
| if (worst < best_worst) { |
| best_worst = worst; |
| best_split = split; |
| } |
| } |
| vband_split = quantize (best_split); |
| } |
| |
| unsigned hdr = header_len + num_hbands + b; |
| unsigned desc_off = index_offset; |
| |
| for (unsigned ci = 0; ci < s.vband_curve_counts.arrayZ[b]; ci++) { |
| buf[index_offset].r = encode_offset (s.curve_texel_offset.arrayZ[s.vband_curves.arrayZ[s.vband_offsets.arrayZ[b] + ci]]); |
| buf[index_offset].g = 0; |
| buf[index_offset].b = 0; |
| buf[index_offset].a = 0; |
| index_offset++; |
| } |
| |
| unsigned asc_off = index_offset; |
| |
| for (unsigned ci = 0; ci < s.vband_curve_counts.arrayZ[b]; ci++) { |
| buf[index_offset].r = encode_offset (s.curve_texel_offset.arrayZ[s.vband_curves_asc.arrayZ[s.vband_offsets.arrayZ[b] + ci]]); |
| buf[index_offset].g = 0; |
| buf[index_offset].b = 0; |
| buf[index_offset].a = 0; |
| index_offset++; |
| } |
| |
| buf[hdr].r = (int16_t) s.vband_curve_counts.arrayZ[b]; |
| buf[hdr].g = encode_offset (desc_off); |
| buf[hdr].b = encode_offset (asc_off); |
| buf[hdr].a = vband_split; |
| } |
| |
| if (draw->recycled_blob) |
| { |
| hb_blob_t *blob = draw->recycled_blob; |
| draw->recycled_blob = nullptr; |
| |
| if (blob->destroy == _hb_gpu_blob_data_destroy) |
| { |
| /* Our blob — update the closure's buf pointer (may have been realloc'd). */ |
| auto *bd = (hb_gpu_blob_data_t *) blob->user_data; |
| if (replaced_recycled_buf && |
| replaced_recycled_buf != (char *) buf) |
| hb_free (replaced_recycled_buf); |
| bd->buf = (char *) buf; |
| bd->capacity = buf_capacity; |
| blob->data = (const char *) buf; |
| blob->length = needed_bytes; |
| return blob; |
| } |
| |
| /* Foreign blob — can't reuse buffer, replace entirely. */ |
| blob->replace_buffer ((const char *) buf, needed_bytes, |
| HB_MEMORY_MODE_WRITABLE, |
| buf, free); |
| return blob; |
| } |
| |
| /* No recycled blob — create new one with closure. */ |
| hb_gpu_blob_data_t *bd = (hb_gpu_blob_data_t *) hb_malloc (sizeof (*bd)); |
| if (unlikely (!bd)) |
| { |
| hb_free (buf); |
| return nullptr; |
| } |
| bd->buf = (char *) buf; |
| bd->capacity = buf_capacity; |
| |
| return hb_blob_create ((const char *) buf, needed_bytes, |
| HB_MEMORY_MODE_WRITABLE, |
| bd, _hb_gpu_blob_data_destroy); |
| } |
| |
| |
| /* ---- Public API ---- */ |
| |
| /** |
| * hb_gpu_draw_create_or_fail: |
| * |
| * Creates a new GPU shape encoder. |
| * |
| * Return value: (transfer full): |
| * A newly allocated #hb_gpu_draw_t, or `NULL` on allocation failure. |
| * |
| * Since: 14.0.0 |
| **/ |
| hb_gpu_draw_t * |
| hb_gpu_draw_create_or_fail (void) |
| { |
| return hb_object_create<hb_gpu_draw_t> (); |
| } |
| |
| /** |
| * hb_gpu_draw_reference: (skip) |
| * @draw: a GPU shape encoder |
| * |
| * Increases the reference count on @draw by one. |
| * |
| * Return value: (transfer full): |
| * The referenced #hb_gpu_draw_t. |
| * |
| * Since: 14.0.0 |
| **/ |
| hb_gpu_draw_t * |
| hb_gpu_draw_reference (hb_gpu_draw_t *draw) |
| { |
| return hb_object_reference (draw); |
| } |
| |
| /** |
| * hb_gpu_draw_destroy: (skip) |
| * @draw: a GPU shape encoder |
| * |
| * Decreases the reference count on @draw by one. When the |
| * reference count reaches zero, the encoder is freed. |
| * |
| * Since: 14.0.0 |
| **/ |
| void |
| hb_gpu_draw_destroy (hb_gpu_draw_t *draw) |
| { |
| if (!hb_object_destroy (draw)) return; |
| hb_free (draw); |
| } |
| |
| /** |
| * hb_gpu_draw_set_user_data: (skip) |
| * @draw: a GPU shape encoder |
| * @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 user data to @draw. |
| * |
| * Return value: `true` if success, `false` otherwise |
| * |
| * Since: 14.0.0 |
| **/ |
| hb_bool_t |
| hb_gpu_draw_set_user_data (hb_gpu_draw_t *draw, |
| hb_user_data_key_t *key, |
| void *data, |
| hb_destroy_func_t destroy, |
| hb_bool_t replace) |
| { |
| return hb_object_set_user_data (draw, key, data, destroy, replace); |
| } |
| |
| /** |
| * hb_gpu_draw_get_user_data: (skip) |
| * @draw: a GPU shape encoder |
| * @key: the user-data key |
| * |
| * Fetches the user-data associated with the specified key. |
| * |
| * Return value: (transfer none): |
| * A pointer to the user data |
| * |
| * Since: 14.0.0 |
| **/ |
| void * |
| hb_gpu_draw_get_user_data (const hb_gpu_draw_t *draw, |
| hb_user_data_key_t *key) |
| { |
| return hb_object_get_user_data (draw, key); |
| } |
| |
| /** |
| * hb_gpu_draw_get_funcs: |
| * |
| * Fetches the singleton #hb_draw_funcs_t that feeds outline data |
| * into an #hb_gpu_draw_t. Pass the #hb_gpu_draw_t as the |
| * @draw_data argument when calling the draw functions. |
| * |
| * Return value: (transfer none): |
| * The GPU draw functions |
| * |
| * Since: 14.0.0 |
| **/ |
| hb_draw_funcs_t * |
| hb_gpu_draw_get_funcs (void) |
| { |
| return static_gpu_draw_funcs.get_unconst (); |
| } |
| |
| /** |
| * hb_gpu_draw_set_scale: |
| * @draw: a GPU shape encoder |
| * @x_scale: horizontal scale (typically from hb_font_get_scale()) |
| * @y_scale: vertical scale |
| * |
| * Sets the font scale so the encoded blob can embed it for |
| * shader use (e.g. computing pixels-per-em). Called |
| * automatically by hb_gpu_draw_glyph(). |
| * |
| * Since: 14.1.0 |
| **/ |
| void |
| hb_gpu_draw_set_scale (hb_gpu_draw_t *draw, |
| int x_scale, |
| int y_scale) |
| { |
| draw->x_scale = x_scale; |
| draw->y_scale = y_scale; |
| } |
| |
| /** |
| * hb_gpu_draw_get_scale: |
| * @draw: a GPU shape encoder |
| * @x_scale: (out): horizontal scale |
| * @y_scale: (out): vertical scale |
| * |
| * Gets the font scale previously set via hb_gpu_draw_set_scale() or |
| * hb_gpu_draw_glyph(). |
| * |
| * XSince: REPLACEME |
| **/ |
| void |
| hb_gpu_draw_get_scale (const hb_gpu_draw_t *draw, |
| int *x_scale, |
| int *y_scale) |
| { |
| if (x_scale) *x_scale = draw->x_scale; |
| if (y_scale) *y_scale = draw->y_scale; |
| } |
| |
| /** |
| * hb_gpu_draw_glyph: |
| * @draw: a GPU shape encoder |
| * @font: font to draw from |
| * @glyph: glyph ID to draw |
| * |
| * Convenience wrapper that draws a single glyph outline into the |
| * encoder using hb_font_draw_glyph(). Also sets the font scale |
| * on the encoder. |
| * |
| * Return value: `true` if the glyph was drawn, `false` if the font |
| * has no outlines for @glyph. |
| * |
| * Since: 14.0.0 |
| **/ |
| hb_bool_t |
| hb_gpu_draw_glyph (hb_gpu_draw_t *draw, |
| hb_font_t *font, |
| hb_codepoint_t glyph) |
| { |
| int x_scale, y_scale; |
| hb_font_get_scale (font, &x_scale, &y_scale); |
| hb_gpu_draw_set_scale (draw, x_scale, y_scale); |
| |
| return hb_font_draw_glyph_or_fail (font, glyph, |
| hb_gpu_draw_get_funcs (), |
| draw); |
| } |
| |
| static void |
| _hb_gpu_draw_get_extents (hb_gpu_draw_t *draw, |
| hb_glyph_extents_t *extents) |
| { |
| if (unlikely (!draw->success) || |
| draw->num_curves == 0 || |
| draw->ext_min_x == HUGE_VAL) |
| { |
| extents->x_bearing = 0; |
| extents->y_bearing = 0; |
| extents->width = 0; |
| extents->height = 0; |
| return; |
| } |
| |
| double min_x = floor (draw->ext_min_x); |
| double min_y = floor (draw->ext_min_y); |
| double max_x = ceil (draw->ext_max_x); |
| double max_y = ceil (draw->ext_max_y); |
| |
| if (unlikely (!std::isfinite (min_x) || |
| !std::isfinite (min_y) || |
| !std::isfinite (max_x) || |
| !std::isfinite (max_y))) |
| { |
| extents->x_bearing = 0; |
| extents->y_bearing = 0; |
| extents->width = 0; |
| extents->height = 0; |
| return; |
| } |
| |
| extents->x_bearing = clamp_to_hb_position (min_x); |
| extents->y_bearing = clamp_to_hb_position (max_y); |
| extents->width = clamp_to_hb_position (max_x - min_x); |
| extents->height = clamp_to_hb_position (min_y - max_y); |
| } |
| |
| /** |
| * hb_gpu_draw_clear: |
| * @draw: a GPU shape encoder |
| * |
| * Discards accumulated outlines so @draw can be reused for another |
| * encode. User configuration (font scale) is preserved. Call |
| * hb_gpu_draw_reset() to also reset user configuration to defaults. |
| * |
| * XSince: REPLACEME |
| **/ |
| void |
| hb_gpu_draw_clear (hb_gpu_draw_t *draw) |
| { |
| draw->start_x = draw->start_y = 0; |
| draw->current_x = draw->current_y = 0; |
| draw->need_moveto = true; |
| draw->num_curves = 0; |
| draw->success = true; |
| draw->curves.reset (); |
| |
| draw->ext_min_x = HUGE_VAL; |
| draw->ext_min_y = HUGE_VAL; |
| draw->ext_max_x = -HUGE_VAL; |
| draw->ext_max_y = -HUGE_VAL; |
| } |
| |
| /** |
| * hb_gpu_draw_reset: |
| * @draw: a GPU shape encoder |
| * |
| * Resets the encoder, discarding all accumulated outlines and |
| * resetting user configuration to defaults. |
| * |
| * Since: 14.0.0 |
| **/ |
| void |
| hb_gpu_draw_reset (hb_gpu_draw_t *draw) |
| { |
| draw->x_scale = 0; |
| draw->y_scale = 0; |
| hb_gpu_draw_clear (draw); |
| } |
| |
| /** |
| * hb_gpu_draw_recycle_blob: |
| * @draw: a GPU shape encoder |
| * @blob: (transfer full): a blob previously returned by hb_gpu_draw_encode() |
| * |
| * Returns a blob to the encoder for potential reuse. |
| * The caller transfers ownership of @blob. |
| * |
| * Currently this simply destroys the blob. A future version |
| * may reclaim the underlying buffer to avoid allocation on the |
| * next hb_gpu_draw_encode() call. |
| * |
| * Since: 14.0.0 |
| **/ |
| void |
| hb_gpu_draw_recycle_blob (hb_gpu_draw_t *draw, |
| hb_blob_t *blob) |
| { |
| hb_blob_destroy (draw->recycled_blob); |
| draw->recycled_blob = nullptr; |
| if (!blob || blob == hb_blob_get_empty ()) |
| return; |
| draw->recycled_blob = blob; |
| } |