| /* | |
| * 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_VECTOR_BUF_HH | |
| #define HB_VECTOR_BUF_HH | |
| #include "hb.hh" | |
| #include "hb-limits.hh" | |
| #include "hb-vector.hh" | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| HB_INTERNAL const char * | |
| hb_vector_decimal_point_get (void); | |
| static const double _hb_vector_buf_pow10[13] = {1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, | |
| 1e7, 1e8, 1e9, 1e10, 1e11, 1e12}; | |
| struct hb_vector_buf_t : hb_vector_t<char> | |
| { | |
| unsigned precision = 2; | |
| /* Sticky flag set once the serialized document would exceed | |
| * HB_VECTOR_MAX_DOCUMENT_SIZE. Folded into in_error() so every emission | |
| * site that already checks it -- path commands, gradient meshes -- stops | |
| * cleanly. Bounds output that the outline work budget cannot see, most | |
| * importantly sweep-gradient meshes and embedded bitmaps. */ | |
| bool overflowed = false; | |
| /* Hides (does not override -- the base method is not virtual) the base | |
| * in_error(); every emission site uses hb_vector_buf_t directly, so the | |
| * cap is seen everywhere. clear() resets the sticky flag so a context | |
| * reused across renders recovers after one over-cap glyph. */ | |
| bool in_error () const | |
| { return hb_vector_t<char>::in_error () || overflowed; } | |
| void clear () | |
| { | |
| overflowed = false; | |
| hb_vector_t<char>::clear (); | |
| } | |
| unsigned scale_precision () const | |
| { return precision < 7 ? 7 : precision; } | |
| bool would_overflow (unsigned add) | |
| { | |
| if (unlikely (in_error ())) | |
| return true; | |
| unsigned new_len; | |
| if (unlikely (hb_unsigned_add_overflows (length, add, &new_len) || | |
| new_len > HB_VECTOR_MAX_DOCUMENT_SIZE)) | |
| { | |
| overflowed = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool append_len (const char *s, unsigned l) | |
| { | |
| if (unlikely (would_overflow (l))) | |
| return false; | |
| unsigned old_len = length; | |
| unsigned new_len; | |
| if (unlikely (hb_unsigned_add_overflows (old_len, l, &new_len) || | |
| !resize_dirty ((int) new_len))) | |
| return false; | |
| hb_memcpy (arrayZ + old_len, s, l); | |
| return true; | |
| } | |
| bool append_c (char ch) | |
| { | |
| if (unlikely (would_overflow (1))) | |
| return false; | |
| return push_or_fail (ch); | |
| } | |
| bool append_str (const char *s) | |
| { return append_len (s, (unsigned) strlen (s)); } | |
| bool append_unsigned (unsigned v) | |
| { | |
| char tmp[16]; | |
| snprintf (tmp, sizeof (tmp), "%u", v); | |
| return append_len (tmp, (unsigned) strlen (tmp)); | |
| } | |
| bool append_hex_byte (unsigned v) | |
| { | |
| char tmp[2] = {"0123456789ABCDEF"[(v >> 4) & 15], | |
| "0123456789ABCDEF"[v & 15]}; | |
| return append_len (tmp, 2); | |
| } | |
| void append_num (float v) | |
| { append_num (v, precision); } | |
| void append_num (float v, unsigned p) | |
| { | |
| if (p > 12) p = 12; | |
| float rounded_zero_threshold = 0.5f; | |
| for (unsigned i = 0; i < p; i++) | |
| rounded_zero_threshold *= 0.1f; | |
| if (fabsf (v) < rounded_zero_threshold) | |
| v = 0.f; | |
| if (!(v == v) || !std::isfinite (v)) | |
| { | |
| append_c ('0'); | |
| return; | |
| } | |
| /* Fast path: fixed-point decimal via integer math. @v has a | |
| * 24-bit significand and 5^12 has 28 bits, so v*10^p is exact in | |
| * a double for p <= 12; rounding it half-to-even therefore | |
| * matches snprintf "%.pf" output digit-for-digit, while skipping | |
| * the (much slower) printf floating-point formatting and any | |
| * locale decimal-point fix-up. */ | |
| double scaled = (double) v * _hb_vector_buf_pow10[p]; | |
| if (likely (fabs (scaled) < 9.0e18)) | |
| { | |
| double fl = floor (scaled); | |
| double diff = scaled - fl; | |
| int64_t n = (int64_t) fl; | |
| if (diff > 0.5 || (diff == 0.5 && (n & 1))) | |
| n++; | |
| uint64_t u = (uint64_t) (n < 0 ? -n : n); | |
| unsigned frac = p; | |
| while (frac && !(u % 10)) | |
| { | |
| u /= 10; | |
| frac--; | |
| } | |
| char tmp[24]; | |
| char *end = tmp + sizeof (tmp); | |
| char *s = end; | |
| unsigned d = 0; | |
| do | |
| { | |
| *--s = (char) ('0' + (char) (u % 10)); | |
| u /= 10; | |
| if (++d == frac) | |
| *--s = '.'; | |
| } | |
| while (u || d < frac + 1); | |
| if (v < 0.f) | |
| *--s = '-'; | |
| append_len (s, (unsigned) (end - s)); | |
| return; | |
| } | |
| char fmt[6]; | |
| snprintf (fmt, sizeof (fmt), "%%.%uf", p); | |
| char out[128]; | |
| snprintf (out, sizeof (out), fmt, (double) v); | |
| const char *decimal_point = hb_vector_decimal_point_get (); | |
| if (decimal_point[0] != '.' || decimal_point[1] != '\0') | |
| { | |
| char *dp = strstr (out, decimal_point); | |
| if (dp) | |
| { | |
| unsigned dp_len = (unsigned) strlen (decimal_point); | |
| unsigned tail_len = (unsigned) strlen (dp + dp_len); | |
| memmove (dp + 1, dp + dp_len, tail_len + 1); | |
| *dp = '.'; | |
| } | |
| } | |
| char *dot = strchr (out, '.'); | |
| if (dot) | |
| { | |
| char *end = out + strlen (out) - 1; | |
| while (end > dot && *end == '0') | |
| *end-- = '\0'; | |
| if (end == dot) | |
| *end = '\0'; | |
| } | |
| append_len (out, (unsigned) strlen (out)); | |
| } | |
| void append_svg_color (hb_color_t color, bool with_alpha) | |
| { | |
| unsigned r = hb_color_get_red (color); | |
| unsigned g = hb_color_get_green (color); | |
| unsigned b = hb_color_get_blue (color); | |
| unsigned a = hb_color_get_alpha (color); | |
| append_c ('#'); | |
| if (((r >> 4) == (r & 0xF)) && | |
| ((g >> 4) == (g & 0xF)) && | |
| ((b >> 4) == (b & 0xF))) | |
| { | |
| append_c ("0123456789ABCDEF"[r & 0xF]); | |
| append_c ("0123456789ABCDEF"[g & 0xF]); | |
| append_c ("0123456789ABCDEF"[b & 0xF]); | |
| } | |
| else | |
| { | |
| append_hex_byte (r); | |
| append_hex_byte (g); | |
| append_hex_byte (b); | |
| } | |
| if (with_alpha && a != 255) | |
| { | |
| append_str ("\" fill-opacity=\""); | |
| append_num (a / 255.f, 4); | |
| } | |
| } | |
| bool append_base64 (const uint8_t *data, unsigned len) | |
| { | |
| /* Output is 4 bytes per 3 input bytes; compute without overflowing. */ | |
| unsigned ngroups = len / 3 + (len % 3 != 0); | |
| unsigned out_len; | |
| unsigned old_len = length; | |
| unsigned new_len; | |
| if (unlikely (hb_unsigned_mul_overflows (ngroups, 4, &out_len) || | |
| would_overflow (out_len) || | |
| hb_unsigned_add_overflows (old_len, out_len, &new_len) || | |
| !resize_dirty ((int) new_len))) | |
| return false; | |
| char *dst = arrayZ + old_len; | |
| unsigned di = 0; | |
| unsigned i = 0; | |
| while (i + 2 < len) | |
| { | |
| unsigned v = ((unsigned) data[i] << 16) | | |
| ((unsigned) data[i + 1] << 8) | | |
| ((unsigned) data[i + 2]); | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 18) & 63]; | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 12) & 63]; | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 6) & 63]; | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[v & 63]; | |
| i += 3; | |
| } | |
| if (i < len) | |
| { | |
| unsigned v = (unsigned) data[i] << 16; | |
| if (i + 1 < len) | |
| v |= (unsigned) data[i + 1] << 8; | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 18) & 63]; | |
| dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 12) & 63]; | |
| dst[di++] = (i + 1 < len) ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 6) & 63] : '='; | |
| dst[di++] = '='; | |
| } | |
| return true; | |
| } | |
| }; | |
| #endif /* HB_VECTOR_BUF_HH */ |