/* * 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-face.hh" #include "hb-vector-svg-subset.hh" #include "OT/Color/svg/svg.hh" #include "hb-vector-svg-utils.hh" #include "hb-map.hh" #include "hb-ot-color.h" #include #include #include static bool hb_svg_append_with_prefix (hb_vector_t *out, const char *s, unsigned n, const char *prefix, unsigned prefix_len) { unsigned i = 0; while (i < n) { if (i + 4 <= n && !memcmp (s + i, "id=\"", 4)) { if (!hb_svg_append_len (out, s + i, 4)) return false; i += 4; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '"') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 4 <= n && !memcmp (s + i, "id='", 4)) { if (!hb_svg_append_len (out, s + i, 4)) return false; i += 4; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '\'') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 7 <= n && !memcmp (s + i, "href=\"#", 7)) { if (!hb_svg_append_len (out, s + i, 7)) return false; i += 7; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '"') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 7 <= n && !memcmp (s + i, "href='#", 7)) { if (!hb_svg_append_len (out, s + i, 7)) return false; i += 7; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '\'') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 13 <= n && !memcmp (s + i, "xlink:href=\"#", 13)) { if (!hb_svg_append_len (out, s + i, 13)) return false; i += 13; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '"') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 13 <= n && !memcmp (s + i, "xlink:href='#", 13)) { if (!hb_svg_append_len (out, s + i, 13)) return false; i += 13; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '\'') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 5 <= n && !memcmp (s + i, "url(#", 5)) { if (!hb_svg_append_len (out, s + i, 5)) return false; i += 5; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != ')') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 6 <= n && !memcmp (s + i, "url(\"#", 6)) { if (!hb_svg_append_len (out, s + i, 6)) return false; i += 6; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '"') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (i + 6 <= n && !memcmp (s + i, "url('#", 6)) { if (!hb_svg_append_len (out, s + i, 6)) return false; i += 6; if (!hb_svg_append_len (out, prefix, prefix_len)) return false; while (i < n && s[i] != '\'') { if (!hb_svg_append_c (out, s[i])) return false; i++; } continue; } if (!hb_svg_append_c (out, s[i])) return false; i++; } return true; } static bool hb_svg_add_unique_id (hb_vector_t *v, hb_hashmap_t *seen_ids, const char *p, unsigned n) { if (!n) return false; OT::SVG::svg_id_span_t key = {p, n}; if (seen_ids->has (key)) return false; if (unlikely (!seen_ids->set (key, true))) return false; auto *slot = v->push (); if (!slot) return false; *slot = key; return true; } static bool hb_svg_collect_refs (const char *s, unsigned n, hb_vector_t *ids, hb_hashmap_t *seen_ids) { unsigned i = 0; while (i < n) { if (i + 7 <= n && !memcmp (s + i, "href=\"#", 7)) { i += 7; unsigned b = i; while (i < n && s[i] != '"' && s[i] != '\'' && s[i] != ' ' && s[i] != '>') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 7 <= n && !memcmp (s + i, "href='#", 7)) { i += 7; unsigned b = i; while (i < n && s[i] != '\'' && s[i] != '"' && s[i] != ' ' && s[i] != '>') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 13 <= n && !memcmp (s + i, "xlink:href=\"#", 13)) { i += 13; unsigned b = i; while (i < n && s[i] != '"' && s[i] != '\'' && s[i] != ' ' && s[i] != '>') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 13 <= n && !memcmp (s + i, "xlink:href='#", 13)) { i += 13; unsigned b = i; while (i < n && s[i] != '\'' && s[i] != '"' && s[i] != ' ' && s[i] != '>') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 5 <= n && !memcmp (s + i, "url(#", 5)) { i += 5; unsigned b = i; while (i < n && s[i] != ')') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 6 <= n && !memcmp (s + i, "url(\"#", 6)) { i += 6; unsigned b = i; while (i < n && s[i] != '"') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } if (i + 6 <= n && !memcmp (s + i, "url('#", 6)) { i += 6; unsigned b = i; while (i < n && s[i] != '\'') i++; if (i > b && unlikely (!hb_svg_add_unique_id (ids, seen_ids, s + b, i - b))) return false; continue; } i++; } return true; } bool hb_svg_subset_glyph_image (hb_face_t *face, hb_blob_t *image, hb_codepoint_t glyph, unsigned *image_counter, hb_vector_t *defs_dst, hb_vector_t *body_dst) { if (glyph == HB_CODEPOINT_INVALID || !image_counter || !defs_dst || !body_dst) return false; unsigned len; const char *svg = hb_blob_get_data (image, &len); if (!svg || !len) return false; unsigned doc_index = 0; if (!hb_ot_color_glyph_get_svg_document_index (face, glyph, &doc_index)) return false; hb_codepoint_t start_glyph = HB_CODEPOINT_INVALID; hb_codepoint_t end_glyph = HB_CODEPOINT_INVALID; if (!hb_ot_color_get_svg_document_glyph_range (face, doc_index, &start_glyph, &end_glyph)) return false; auto *doc_cache = face->table.SVG->get_or_create_doc_cache (image, svg, len, doc_index, start_glyph, end_glyph); if (!doc_cache) return false; svg = face->table.SVG->doc_cache_get_svg (doc_cache, &len); unsigned glyph_start = 0, glyph_end = 0; if (!face->table.SVG->doc_cache_get_glyph_span (doc_cache, glyph, &glyph_start, &glyph_end)) return false; auto *defs_entries = face->table.SVG->doc_cache_get_defs_entries (doc_cache); hb_vector_t needed_ids; needed_ids.alloc (16); hb_hashmap_t needed_ids_set; if (!hb_svg_collect_refs (svg + glyph_start, glyph_end - glyph_start, &needed_ids, &needed_ids_set)) return false; hb_vector_t chosen_defs; chosen_defs.alloc (16); hb_vector_t chosen_def_marks; if (unlikely (!chosen_def_marks.resize ((int) defs_entries->length))) return false; hb_memset (chosen_def_marks.arrayZ, 0, defs_entries->length); for (unsigned qi = 0; qi < needed_ids.length; qi++) { const auto &need = needed_ids.arrayZ[qi]; for (unsigned i = 0; i < defs_entries->length; i++) { const auto &e = defs_entries->arrayZ[i]; if (e.id.len == need.len && !memcmp (e.id.p, need.p, need.len)) { if (!chosen_def_marks.arrayZ[i]) { chosen_def_marks.arrayZ[i] = 1; if (unlikely (!chosen_defs.push (i))) return false; if (!hb_svg_collect_refs (svg + e.start, e.end - e.start, &needed_ids, &needed_ids_set)) return false; } break; } } } char prefix[32]; int prefix_len = snprintf (prefix, sizeof (prefix), "hbimg%u_", (*image_counter)++); if (prefix_len <= 0 || (unsigned) prefix_len >= sizeof (prefix)) return false; body_dst->alloc (body_dst->length + (glyph_end - glyph_start) + (unsigned) prefix_len + 32); for (unsigned i = 0; i < chosen_defs.length; i++) { const auto &e = defs_entries->arrayZ[chosen_defs.arrayZ[i]]; if (!hb_svg_append_with_prefix (defs_dst, svg + e.start, e.end - e.start, prefix, (unsigned) prefix_len)) return false; if (!hb_svg_append_c (defs_dst, '\n')) return false; } return hb_svg_append_with_prefix (body_dst, svg + glyph_start, glyph_end - glyph_start, prefix, (unsigned) prefix_len); }