| /* |
| * 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_SVG_PARSE_HH |
| #define HB_RASTER_SVG_PARSE_HH |
| |
| #include "hb.hh" |
| |
| #include "hb-raster-svg-base.hh" |
| #include "hb-draw.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| enum hb_svg_token_type_t |
| { |
| SVG_TOKEN_OPEN_TAG, |
| SVG_TOKEN_CLOSE_TAG, |
| SVG_TOKEN_SELF_CLOSE_TAG, |
| SVG_TOKEN_TEXT, |
| SVG_TOKEN_EOF |
| }; |
| |
| struct hb_svg_attr_t |
| { |
| hb_svg_str_t name; |
| hb_svg_str_t value; |
| }; |
| |
| struct hb_svg_xml_parser_t |
| { |
| enum { SVG_MAX_ATTRS_PER_TAG = 256 }; |
| |
| const char *p; |
| const char *end; |
| const char *tag_start = nullptr; |
| |
| hb_svg_str_t tag_name; |
| hb_vector_t<hb_svg_attr_t> attrs; |
| bool self_closing; |
| |
| hb_svg_xml_parser_t (const char *data, unsigned len) |
| : p (data), end (data + len), self_closing (false) {} |
| |
| void skip_ws () |
| { |
| while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) |
| p++; |
| } |
| |
| hb_svg_str_t read_name () |
| { |
| const char *start = p; |
| while (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r' && |
| *p != '>' && *p != '/' && *p != '=') |
| p++; |
| return {start, (unsigned) (p - start)}; |
| } |
| |
| hb_svg_str_t read_attr_value () |
| { |
| if (p >= end) return {}; |
| char quote = *p; |
| if (quote != '"' && quote != '\'') return {}; |
| p++; |
| const char *start = p; |
| while (p < end && *p != quote) |
| p++; |
| hb_svg_str_t val = {start, (unsigned) (p - start)}; |
| if (p < end) p++; /* skip closing quote */ |
| return val; |
| } |
| |
| void parse_attrs () |
| { |
| attrs.clear (); |
| while (p < end) |
| { |
| skip_ws (); |
| if (p >= end || *p == '>' || *p == '/') break; |
| hb_svg_str_t name = read_name (); |
| if (!name.len) { p++; continue; } |
| skip_ws (); |
| if (p < end && *p == '=') |
| { |
| p++; |
| skip_ws (); |
| hb_svg_str_t val = read_attr_value (); |
| if (attrs.length < SVG_MAX_ATTRS_PER_TAG) |
| { |
| hb_svg_attr_t attr = {name, val}; |
| attrs.push (attr); |
| } |
| } |
| else |
| { |
| if (attrs.length < SVG_MAX_ATTRS_PER_TAG) |
| { |
| hb_svg_attr_t attr = {name, {}}; |
| attrs.push (attr); |
| } |
| } |
| } |
| } |
| |
| hb_svg_token_type_t next () |
| { |
| while (p < end) |
| { |
| if (*p != '<') |
| { |
| while (p < end && *p != '<') p++; |
| continue; |
| } |
| |
| tag_start = p; |
| p++; /* skip '<' */ |
| if (p >= end) return SVG_TOKEN_EOF; |
| |
| if (*p == '!') |
| { |
| if (p + 2 < end && p[1] == '-' && p[2] == '-') |
| { |
| p += 3; |
| while (p + 2 < end && !(p[0] == '-' && p[1] == '-' && p[2] == '>')) |
| p++; |
| if (p + 2 < end) p += 3; |
| } |
| else |
| { |
| while (p < end && *p != '>') p++; |
| if (p < end) p++; |
| } |
| continue; |
| } |
| if (*p == '?') |
| { |
| while (p + 1 < end && !(p[0] == '?' && p[1] == '>')) |
| p++; |
| if (p + 1 < end) p += 2; |
| continue; |
| } |
| |
| if (*p == '/') |
| { |
| p++; |
| tag_name = read_name (); |
| while (p < end && *p != '>') p++; |
| if (p < end) p++; |
| attrs.clear (); |
| self_closing = false; |
| return SVG_TOKEN_CLOSE_TAG; |
| } |
| |
| tag_name = read_name (); |
| parse_attrs (); |
| |
| self_closing = false; |
| skip_ws (); |
| if (p < end && *p == '/') |
| { |
| self_closing = true; |
| p++; |
| } |
| if (p < end && *p == '>') |
| p++; |
| |
| return self_closing ? SVG_TOKEN_SELF_CLOSE_TAG : SVG_TOKEN_OPEN_TAG; |
| } |
| return SVG_TOKEN_EOF; |
| } |
| |
| hb_svg_str_t find_attr (const char *name) const |
| { |
| for (unsigned i = 0; i < attrs.length; i++) |
| if (attrs[i].name.eq (name)) |
| return attrs[i].value; |
| return {}; |
| } |
| }; |
| |
| static inline void |
| svg_skip_subtree (hb_svg_xml_parser_t &parser) |
| { |
| int depth = 1; |
| while (depth > 0) |
| { |
| hb_svg_token_type_t tok = parser.next (); |
| if (tok == SVG_TOKEN_EOF) break; |
| if (tok == SVG_TOKEN_CLOSE_TAG) depth--; |
| else if (tok == SVG_TOKEN_OPEN_TAG) depth++; |
| } |
| } |
| |
| static inline hb_svg_str_t |
| svg_pick_attr_or_style (const hb_svg_xml_parser_t &parser, |
| hb_svg_str_t style_value, |
| const char *attr_name) |
| { |
| return style_value.is_null () ? parser.find_attr (attr_name) : style_value; |
| } |
| |
| struct hb_svg_attr_view_t |
| { |
| const hb_svg_xml_parser_t &parser; |
| enum { CACHE_SIZE = 16 }; |
| struct entry_t |
| { |
| const char *name = nullptr; |
| hb_svg_str_t value; |
| }; |
| entry_t cache[CACHE_SIZE]; |
| unsigned cache_len = 0; |
| |
| hb_svg_attr_view_t (const hb_svg_xml_parser_t &p) : parser (p) {} |
| |
| hb_svg_str_t get (const char *name) |
| { |
| for (unsigned i = 0; i < cache_len; i++) |
| if (strcmp (cache[i].name, name) == 0) |
| return cache[i].value; |
| |
| hb_svg_str_t value = parser.find_attr (name); |
| if (cache_len < CACHE_SIZE) |
| { |
| cache[cache_len].name = name; |
| cache[cache_len].value = value; |
| cache_len++; |
| } |
| return value; |
| } |
| }; |
| |
| struct hb_svg_transform_t |
| { |
| float xx = 1, yx = 0, xy = 0, yy = 1, dx = 0, dy = 0; |
| |
| void multiply (const hb_svg_transform_t &other) |
| { |
| float nxx = xx * other.xx + xy * other.yx; |
| float nyx = yx * other.xx + yy * other.yx; |
| float nxy = xx * other.xy + xy * other.yy; |
| float nyy = yx * other.xy + yy * other.yy; |
| float ndx = xx * other.dx + xy * other.dy + dx; |
| float ndy = yx * other.dx + yy * other.dy + dy; |
| xx = nxx; yx = nyx; xy = nxy; yy = nyy; dx = ndx; dy = ndy; |
| } |
| }; |
| |
| struct hb_svg_float_parser_t |
| { |
| const char *p; |
| const char *end; |
| |
| hb_svg_float_parser_t (hb_svg_str_t s) : p (s.data), end (s.data + s.len) {} |
| |
| void skip_ws_comma () |
| { |
| while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')) |
| p++; |
| } |
| |
| bool has_more () const { return p < end; } |
| |
| float next_float () |
| { |
| skip_ws_comma (); |
| if (p >= end) return 0.f; |
| const char *start = p; |
| char buf[64]; |
| unsigned n = 0; |
| bool has_digit = false; |
| if (p < end && (*p == '-' || *p == '+')) |
| buf[n++] = *p++; |
| while (p < end && n < sizeof (buf) - 1 && *p >= '0' && *p <= '9') |
| { |
| buf[n++] = *p++; |
| has_digit = true; |
| } |
| if (p < end && n < sizeof (buf) - 1 && *p == '.') |
| { |
| buf[n++] = *p++; |
| while (p < end && n < sizeof (buf) - 1 && *p >= '0' && *p <= '9') |
| { |
| buf[n++] = *p++; |
| has_digit = true; |
| } |
| } |
| if (p < end && n < sizeof (buf) - 1 && (*p == 'e' || *p == 'E')) |
| { |
| buf[n++] = *p++; |
| if (p < end && n < sizeof (buf) - 1 && (*p == '+' || *p == '-')) |
| buf[n++] = *p++; |
| bool has_exp_digit = false; |
| while (p < end && n < sizeof (buf) - 1 && *p >= '0' && *p <= '9') |
| { |
| buf[n++] = *p++; |
| has_exp_digit = true; |
| } |
| if (!has_exp_digit) |
| { |
| p = start; |
| has_digit = false; |
| } |
| } |
| if (!has_digit) |
| { |
| if (p < end) p++; |
| return 0.f; |
| } |
| buf[n] = '\0'; |
| float v = strtof (buf, nullptr); |
| return std::isfinite (v) ? v : 0.f; |
| } |
| |
| bool next_flag () |
| { |
| skip_ws_comma (); |
| if (p >= end) return false; |
| bool v = *p != '0'; |
| p++; |
| return v; |
| } |
| }; |
| |
| static inline float |
| svg_parse_float (hb_svg_str_t s) |
| { |
| return s.to_float (); |
| } |
| |
| struct hb_svg_shape_emit_data_t |
| { |
| enum { SHAPE_PATH, SHAPE_RECT, SHAPE_CIRCLE, SHAPE_ELLIPSE, |
| SHAPE_LINE, SHAPE_POLYLINE, SHAPE_POLYGON } type; |
| hb_svg_str_t str_data; |
| float params[6]; |
| }; |
| |
| HB_INTERNAL bool hb_raster_svg_parse_transform (hb_svg_str_t s, hb_svg_transform_t *out); |
| HB_INTERNAL void hb_raster_svg_parse_path_data (hb_svg_str_t d, hb_draw_funcs_t *dfuncs, void *draw_data); |
| HB_INTERNAL void hb_raster_svg_shape_path_emit (hb_draw_funcs_t *dfuncs, void *draw_data, void *user_data); |
| HB_INTERNAL bool hb_raster_svg_parse_shape_tag (hb_svg_xml_parser_t &parser, hb_svg_shape_emit_data_t *shape); |
| |
| #endif /* HB_RASTER_SVG_PARSE_HH */ |