blob: ef11ffa6f1646cbe65deab85bae1f97ba395ff7e [file] [log] [blame] [edit]
/*
* Copyright © 2026 Google, Inc.
*
* 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.
*/
#ifndef HB_VECTOR_OUTPUT_HH
#define HB_VECTOR_OUTPUT_HH
#include <algorithm>
#include <math.h>
#include <string>
#include <vector>
#include "output-options.hh"
#include "view-options.hh"
#include "hb-vector.h"
#include "hb-ot.h"
struct vector_output_t : output_options_t<>, view_options_t
{
static const bool repeat_shape = false;
~vector_output_t ()
{
hb_font_destroy (font);
hb_font_destroy (upem_font);
}
void add_options (option_parser_t *parser)
{
parser->set_summary ("Draw text with given font.");
parser->set_description ("Shows shaped glyph outlines as SVG.");
output_options_t::add_options (parser);
view_options_t::add_options (parser);
GOptionEntry entries[] =
{
{"flat", 0, 0, G_OPTION_ARG_NONE, &this->flat, "Flatten geometry and disable reuse", nullptr},
{"precision", 0, 0, G_OPTION_ARG_INT, &this->precision, "Decimal precision (default: 2)", "N"},
{nullptr}
};
parser->add_group (entries,
"vector",
"Vector options:",
"Options for vector output",
this);
}
void post_parse (GError **error)
{
view_options_t::post_parse (error);
if (error && *error)
return;
if (precision < 0)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"precision must be non-negative");
return;
}
foreground = HB_COLOR (foreground_color.b,
foreground_color.g,
foreground_color.r,
foreground_color.a);
background = HB_COLOR (background_color.b,
background_color.g,
background_color.r,
background_color.a);
load_custom_palette_overrides_from_view ();
}
template <typename app_t>
void init (hb_buffer_t *buffer HB_UNUSED, const app_t *font_opts)
{
lines.clear ();
direction = HB_DIRECTION_INVALID;
font = hb_font_reference (font_opts->font);
subpixel_bits = font_opts->subpixel_bits;
hb_font_get_scale (font, &x_scale, &y_scale);
upem = hb_face_get_upem (hb_font_get_face (font));
normalized_stroke_width = get_normalized_stroke_width ();
upem_font = hb_font_create (hb_font_get_face (font));
hb_font_set_scale (upem_font, upem, upem);
#ifndef HB_NO_VAR
unsigned int coords_length = 0;
const float *coords = hb_font_get_var_coords_design (font, &coords_length);
if (coords_length)
hb_font_set_var_coords_design (upem_font, coords, coords_length);
#endif
}
void new_line ()
{
lines.emplace_back ();
}
void consume_text (hb_buffer_t * HB_UNUSED,
const char * HB_UNUSED,
unsigned int HB_UNUSED,
hb_bool_t HB_UNUSED)
{}
void error (const char *message)
{
g_printerr ("%s: %s\n", g_get_prgname (), message);
}
void consume_glyphs (hb_buffer_t *buffer,
const char * HB_UNUSED,
unsigned int HB_UNUSED,
hb_bool_t HB_UNUSED)
{
if (lines.empty ())
lines.emplace_back ();
line_t &line = lines.back ();
hb_direction_t line_direction = hb_buffer_get_direction (buffer);
if (line_direction == HB_DIRECTION_INVALID)
line_direction = HB_DIRECTION_LTR;
if (direction == HB_DIRECTION_INVALID)
direction = line_direction;
unsigned int count = 0;
const hb_glyph_info_t *infos = hb_buffer_get_glyph_infos (buffer, &count);
const hb_glyph_position_t *positions = hb_buffer_get_glyph_positions (buffer, &count);
hb_position_t pen_x = 0;
hb_position_t pen_y = 0;
line.glyphs.reserve (line.glyphs.size () + count);
for (unsigned i = 0; i < count; i++)
{
line.glyphs.push_back ({
infos[i].codepoint,
scale_to_upem_x (pen_x + positions[i].x_offset),
scale_to_upem_y (pen_y + positions[i].y_offset),
scale_to_upem_x (positions[i].x_advance),
scale_to_upem_y (positions[i].y_advance),
});
pen_x += positions[i].x_advance;
pen_y += positions[i].y_advance;
}
line.advance_x = scale_to_upem_x (pen_x);
line.advance_y = scale_to_upem_y (pen_y);
}
template <typename app_t>
void finish (hb_buffer_t *buffer HB_UNUSED, const app_t *app HB_UNUSED)
{
hb_vector_extents_t extents = {};
if (!compute_extents (&extents))
return;
final_extents = extents;
hb_vector_draw_t *draw = hb_vector_draw_create_or_fail (HB_VECTOR_FORMAT_SVG);
hb_vector_paint_t *paint = hb_vector_paint_create_or_fail (HB_VECTOR_FORMAT_SVG);
hb_vector_draw_set_scale_factor (draw, 1.f, 1.f);
hb_vector_draw_set_extents (draw, &extents);
hb_vector_svg_set_precision (draw, precision);
hb_vector_svg_set_flat (draw, flat);
hb_vector_paint_set_scale_factor (paint, 1.f, 1.f);
hb_vector_paint_set_extents (paint, &extents);
hb_vector_paint_set_foreground (paint, foreground);
hb_vector_paint_set_palette (paint, this->palette);
apply_custom_palette (paint);
init_palette_color_cache ();
hb_vector_svg_paint_set_precision (paint, precision);
hb_vector_svg_paint_set_flat (paint, flat);
bool had_draw = false;
bool had_paint = false;
hb_vector_extents_mode_t extents_mode = HB_VECTOR_EXTENTS_MODE_NONE;
const bool use_foreground_palette =
foreground_use_palette && foreground_palette && foreground_palette->len;
unsigned palette_glyph_index = 0;
hb_direction_t dir = direction;
if (dir == HB_DIRECTION_INVALID)
dir = HB_DIRECTION_LTR;
bool vertical = HB_DIRECTION_IS_VERTICAL (dir);
float asc = 0.f, desc = 0.f, gap = 0.f;
get_line_metrics (dir, &asc, &desc, &gap);
float step = fabsf (asc - desc + gap + (float) line_space);
if (!(step > 0.f))
step = (float) upem;
line_step = step;
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
for (const auto &g : lines[li].glyphs)
{
float pen_x = g.x + off_x;
float pen_y = g.y + off_y;
if (use_foreground_palette)
{
const rgba_color_t &c =
g_array_index (foreground_palette, rgba_color_t,
palette_glyph_index++ % foreground_palette->len);
hb_vector_paint_set_foreground (paint, HB_COLOR (c.b, c.g, c.r, c.a));
}
hb_vector_paint_set_transform (paint, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f);
if (hb_vector_paint_glyph (paint, upem_font, g.gid, pen_x, pen_y,
extents_mode))
{
had_paint = true;
continue;
}
if (hb_vector_draw_glyph (draw, upem_font, g.gid, pen_x, pen_y,
extents_mode))
had_draw = true;
}
}
hb_blob_t *draw_blob = had_draw ? hb_vector_draw_render (draw) : nullptr;
hb_blob_t *paint_blob = had_paint ? hb_vector_paint_render (paint) : nullptr;
if (draw_blob && draw_blob != hb_blob_get_empty () &&
paint_blob && paint_blob != hb_blob_get_empty ())
write_combined_svg (draw_blob, paint_blob);
else if (paint_blob && paint_blob != hb_blob_get_empty ())
write_blob (paint_blob, false);
else if (draw_blob && draw_blob != hb_blob_get_empty ())
write_blob (draw_blob, true);
hb_blob_destroy (draw_blob);
hb_blob_destroy (paint_blob);
hb_vector_draw_destroy (draw);
hb_vector_paint_destroy (paint);
hb_font_destroy (upem_font);
upem_font = nullptr;
hb_font_destroy (font);
font = nullptr;
lines.clear ();
}
private:
struct glyph_instance_t
{
hb_codepoint_t gid;
float x;
float y;
float ax;
float ay;
};
struct line_t
{
float advance_x = 0.f;
float advance_y = 0.f;
std::vector<glyph_instance_t> glyphs;
};
struct slice_t
{
const char *p = nullptr;
unsigned len = 0;
};
bool compute_extents (hb_vector_extents_t *extents)
{
if (lines.empty ())
{
extents->x = 0;
extents->y = 0;
extents->width = 1;
extents->height = 1;
return true;
}
hb_direction_t dir = direction;
if (dir == HB_DIRECTION_INVALID)
dir = HB_DIRECTION_LTR;
bool vertical = HB_DIRECTION_IS_VERTICAL (dir);
float asc = 0.f, desc = 0.f, gap = 0.f;
get_line_metrics (dir, &asc, &desc, &gap);
float step = fabsf (asc - desc + gap + (float) line_space);
if (!(step > 0.f))
step = (float) upem;
bool valid = false;
float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
bool include_logical = include_logical_extents ();
bool include_ink = include_ink_extents ();
auto include_point = [&] (float x, float y)
{
y = -y;
if (!valid)
{
x1 = x2 = x;
y1 = y2 = y;
valid = true;
return;
}
x1 = hb_min (x1, x);
y1 = hb_min (y1, y);
x2 = hb_max (x2, x);
y2 = hb_max (y2, y);
};
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
const line_t &line = lines[li];
if (include_logical)
{
float log_min = hb_min (desc, asc);
float log_max = hb_max (desc, asc);
if (vertical)
{
include_point (off_x + log_min, hb_min (0.f, line.advance_y));
include_point (off_x + log_max, hb_max (0.f, line.advance_y));
}
else
{
include_point (hb_min (0.f, line.advance_x), off_y + log_min);
include_point (hb_max (0.f, line.advance_x), off_y + log_max);
}
}
if (include_ink)
for (const auto &g : line.glyphs)
{
hb_glyph_extents_t ge;
if (!hb_font_get_glyph_extents (upem_font, g.gid, &ge))
continue;
float gx = g.x + off_x;
float gy = g.y + off_y;
float xa = gx + ge.x_bearing;
float ya = gy + ge.y_bearing;
float xb = xa + ge.width;
float yb = ya + ge.height;
include_point (xa, ya);
include_point (xb, yb);
}
}
if (!valid)
{
extents->x = 0;
extents->y = 0;
extents->width = 1;
extents->height = 1;
return true;
}
if (x2 <= x1)
x2 = x1 + 1;
if (y2 <= y1)
y2 = y1 + 1;
x1 -= (float) margin.l;
y1 -= (float) margin.t;
x2 += (float) margin.r;
y2 += (float) margin.b;
extents->x = x1;
extents->y = y1;
extents->width = x2 - x1;
extents->height = y2 - y1;
return true;
}
float scale_to_upem_x (hb_position_t v) const
{
return (float) v * upem / x_scale;
}
float scale_to_upem_y (hb_position_t v) const
{
return (float) v * upem / y_scale;
}
static bool slice_svg (const char *data, unsigned len,
slice_t *defs, slice_t *body)
{
defs->p = nullptr;
defs->len = 0;
body->p = nullptr;
body->len = 0;
const char *data_end = data + len;
const char *start = (const char *) memchr (data, '>', len);
if (!start)
return false;
start += 1;
if (len < 6)
return false;
const char *end = nullptr;
for (const char *p = data_end - 6; p >= data; p--)
{
if (memcmp (p, "</svg>", 6) == 0)
{
end = p;
break;
}
if (p == data)
break;
}
if (!end || end <= start)
return false;
const char *defs_start = start;
while (defs_start < end &&
(*defs_start == ' ' || *defs_start == '\t' ||
*defs_start == '\r' || *defs_start == '\n'))
defs_start++;
if (!(defs_start + 6 <= end && memcmp (defs_start, "<defs>", 6) == 0))
defs_start = nullptr;
const char *defs_end = nullptr;
if (defs_start)
for (const char *p = defs_start + 6; p + 7 <= end; p++)
if (memcmp (p, "</defs>", 7) == 0)
{
defs_end = p;
break;
}
if (defs_start && defs_end && defs_start < end && defs_end < end)
{
defs->p = defs_start + strlen ("<defs>");
defs->len = (unsigned) (defs_end - defs->p);
const char *body_start = defs_end + strlen ("</defs>");
if (body_start < end)
{
body->p = body_start;
body->len = (unsigned) (end - body_start);
}
}
else
{
body->p = start;
body->len = (unsigned) (end - start);
}
return true;
}
void emit_fill_group_open ()
{
const bool has_fill_override = fore || foreground_use_palette;
const bool has_stroke = stroke_enabled && stroke_width > 0.;
if (!has_fill_override && !has_stroke)
return;
fputs ("<g", out_fp);
if (has_fill_override)
{
unsigned r = hb_color_get_red (foreground);
unsigned g = hb_color_get_green (foreground);
unsigned b = hb_color_get_blue (foreground);
unsigned a = hb_color_get_alpha (foreground);
fprintf (out_fp, " fill=\"#%02X%02X%02X\"", r, g, b);
if (a != 255)
fprintf (out_fp, " fill-opacity=\"%.3f\"", (double) a / 255.);
}
else
fputs (" fill=\"none\"", out_fp);
if (has_stroke)
emit_stroke_attrs ();
fprintf (out_fp, ">\n");
}
void emit_fill_group_close ()
{
if (!(fore || foreground_use_palette || (stroke_enabled && stroke_width > 0.)))
return;
fputs ("</g>\n", out_fp);
}
void emit_fill_stroke_attrs_for_color (hb_color_t color)
{
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);
fprintf (out_fp, " fill=\"#%02X%02X%02X\"", r, g, b);
if (a != 255)
fprintf (out_fp, " fill-opacity=\"%.3f\"", (double) a / 255.);
}
void emit_draw_body_with_palette (slice_t body)
{
if (!foreground_palette || !foreground_palette->len || !body.p || !body.len)
return;
const char *p = body.p;
const char *end = body.p + body.len;
unsigned palette_i = 0;
const bool has_stroke = stroke_enabled && stroke_width > 0.;
if (has_stroke)
{
fputs ("<g", out_fp);
emit_stroke_attrs ();
fputs (">\n", out_fp);
}
while (p < end)
{
const char *line_end = (const char *) memchr (p, '\n', (size_t) (end - p));
if (!line_end)
line_end = end;
const char *s = p;
while (s < line_end && (*s == ' ' || *s == '\t' || *s == '\r'))
s++;
bool is_elem = s < line_end && *s == '<' && (s + 1 < line_end && s[1] != '/');
if (is_elem)
{
const rgba_color_t &c =
g_array_index (foreground_palette, rgba_color_t,
palette_i++ % foreground_palette->len);
hb_color_t col = HB_COLOR (c.b, c.g, c.r, c.a);
fputs ("<g", out_fp);
emit_fill_stroke_attrs_for_color (col);
fputs (">", out_fp);
fwrite (p, 1, (size_t) (line_end - p), out_fp);
fputs ("</g>\n", out_fp);
}
else
{
fwrite (p, 1, (size_t) (line_end - p), out_fp);
fputc ('\n', out_fp);
}
p = line_end;
if (p < end && *p == '\n')
p++;
}
if (has_stroke)
fputs ("</g>\n", out_fp);
}
void emit_stroke_attrs ()
{
fprintf (out_fp, " stroke=\"#%02X%02X%02X\"",
stroke_color.r, stroke_color.g, stroke_color.b);
if (stroke_color.a != 255)
fprintf (out_fp, " stroke-opacity=\"%.3f\"", (double) stroke_color.a / 255.);
fprintf (out_fp, " stroke-width=\"%.*g\" stroke-linecap=\"round\" stroke-linejoin=\"round\"",
precision + 4, normalized_stroke_width);
}
double get_normalized_stroke_width () const
{
if (!stroke_enabled || stroke_width <= 0. || !upem || !x_scale || !y_scale)
return stroke_width;
/* hb-vector emits geometry in upem space; convert user stroke width
* from the requested font scale back into that normalized coordinate system.
*/
double norm_x = stroke_width * scalbn ((double) upem, (int) subpixel_bits) / fabs ((double) x_scale);
double norm_y = stroke_width * scalbn ((double) upem, (int) subpixel_bits) / fabs ((double) y_scale);
return 0.5 * (norm_x + norm_y);
}
bool lookup_palette_color (unsigned idx, hb_color_t *color) const
{
if (idx < custom_palette_has_value.size () && custom_palette_has_value[idx])
{
*color = custom_palette_values[idx];
return true;
}
if (!palette_lookup_enabled)
return false;
if (palette_cache_state.size () <= idx)
{
palette_cache_state.resize (idx + 1, 0);
palette_cache_values.resize (idx + 1, HB_COLOR (0, 0, 0, 255));
}
if (palette_cache_state[idx] == 1)
{
*color = palette_cache_values[idx];
return true;
}
if (palette_cache_state[idx] == 2)
return false;
hb_color_t fetched = HB_COLOR (0, 0, 0, 255);
unsigned n = 1;
if (hb_ot_color_palette_get_colors (palette_face, palette_lookup_index,
idx, &n, &fetched) && n)
{
palette_cache_values[idx] = fetched;
palette_cache_state[idx] = 1;
*color = fetched;
return true;
}
palette_cache_state[idx] = 2;
return false;
}
static inline const char *
skip_ascii_ws (const char *p, const char *end)
{
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'))
p++;
return p;
}
static inline const char *
trim_ascii_ws_right (const char *begin, const char *end)
{
while (end > begin &&
(end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r'))
end--;
return end;
}
struct parsed_var_color_expr_t
{
const char *expr_end = nullptr;
const char *fallback_begin = nullptr;
const char *fallback_end = nullptr;
unsigned idx = 0;
bool parse_ok = false;
bool have_digits = false;
bool has_fallback = false;
};
static inline const char *
find_next_var_expr (const char *p, const char *end)
{
for (const char *q = p; q + 4 <= end; q++)
if (memcmp (q, "var(", 4) == 0)
return q;
return nullptr;
}
static inline const char *
find_matching_paren (const char *inside, const char *end)
{
unsigned depth = 1;
for (const char *q = inside; q < end; q++)
{
if (*q == '(') depth++;
else if (*q == ')')
{
depth--;
if (!depth)
return q;
}
}
return nullptr;
}
static inline parsed_var_color_expr_t
parse_var_color_expr (const char *match, const char *end)
{
parsed_var_color_expr_t parsed;
const char *expr_close = find_matching_paren (match + 4, end);
if (!expr_close)
return parsed;
parsed.expr_end = expr_close + 1;
const char *inside = match + 4;
const char *inside_end = expr_close;
const char *q = skip_ascii_ws (inside, inside_end);
parsed.parse_ok = true;
if (!(q + 7 <= inside_end && memcmp (q, "--color", 7) == 0))
parsed.parse_ok = false;
if (parsed.parse_ok)
q += 7;
while (parsed.parse_ok && q < inside_end && *q >= '0' && *q <= '9')
{
parsed.have_digits = true;
parsed.idx = parsed.idx * 10 + (unsigned) (*q - '0');
q++;
}
q = skip_ascii_ws (q, inside_end);
if (parsed.parse_ok && q < inside_end && *q == ',')
{
parsed.has_fallback = true;
q++;
parsed.fallback_begin = skip_ascii_ws (q, inside_end);
parsed.fallback_end = trim_ascii_ws_right (parsed.fallback_begin, inside_end);
q = inside_end;
}
if (parsed.parse_ok && q != inside_end)
parsed.parse_ok = false;
return parsed;
}
void emit_css_color (hb_color_t color)
{
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);
if (a == 255)
fprintf (out_fp, "#%02X%02X%02X", r, g, b);
else
fprintf (out_fp, "rgba(%u,%u,%u,%.6g)", r, g, b, (double) a / 255.);
}
void write_slice_resolving_palette_vars (slice_t s)
{
if (!s.p || !s.len)
return;
const char *p = s.p;
const char *end = s.p + s.len;
while (p < end)
{
const char *match = find_next_var_expr (p, end);
if (!match)
{
fwrite (p, 1, (size_t) (end - p), out_fp);
return;
}
if (match > p)
fwrite (p, 1, (size_t) (match - p), out_fp);
parsed_var_color_expr_t parsed = parse_var_color_expr (match, end);
if (!parsed.expr_end)
{
fwrite (match, 1, (size_t) (end - match), out_fp);
return;
}
hb_color_t color;
if (parsed.parse_ok && parsed.have_digits && lookup_palette_color (parsed.idx, &color))
{
emit_css_color (color);
}
else if (parsed.has_fallback && parsed.fallback_begin &&
parsed.fallback_end >= parsed.fallback_begin)
{
fwrite (parsed.fallback_begin, 1,
(size_t) (parsed.fallback_end - parsed.fallback_begin), out_fp);
}
else
{
fwrite (match, 1, (size_t) (parsed.expr_end - match), out_fp);
}
p = parsed.expr_end;
}
}
void write_blob (hb_blob_t *blob,
hb_bool_t is_draw_blob)
{
unsigned in_len = 0;
const char *in_data = hb_blob_get_data (blob, &in_len);
if (!in_data || !in_len)
return;
const char *hdr_end = (const char *) memchr (in_data, '>', in_len);
if (!hdr_end)
return;
slice_t defs, body;
if (!slice_svg (in_data, in_len, &defs, &body))
return;
fwrite (in_data, 1, (hdr_end - in_data + 1), out_fp);
fputc ('\n', out_fp);
if (defs.len)
{
fputs ("<defs>\n", out_fp);
write_slice_resolving_palette_vars (defs);
fputs ("</defs>\n", out_fp);
}
emit_background_rect ();
if (is_draw_blob && foreground_use_palette && foreground_palette && foreground_palette->len)
emit_draw_body_with_palette (body);
else if (is_draw_blob)
emit_fill_group_open ();
if (body.len && !(is_draw_blob && foreground_use_palette && foreground_palette && foreground_palette->len))
write_slice_resolving_palette_vars (body);
if (is_draw_blob && !(foreground_use_palette && foreground_palette && foreground_palette->len))
emit_fill_group_close ();
if (show_extents)
emit_extents_overlay (direction == HB_DIRECTION_INVALID ? HB_DIRECTION_LTR : direction, line_step);
fputs ("</svg>\n", out_fp);
}
void write_combined_svg (hb_blob_t *draw_blob,
hb_blob_t *paint_blob)
{
unsigned draw_len = 0, paint_len = 0;
const char *draw_data = hb_blob_get_data (draw_blob, &draw_len);
const char *paint_data = hb_blob_get_data (paint_blob, &paint_len);
if (!draw_data || !paint_data)
return;
const char *draw_hdr_end = (const char *) memchr (draw_data, '>', draw_len);
if (!draw_hdr_end)
return;
slice_t draw_defs, draw_body;
slice_t paint_defs, paint_body;
if (!slice_svg (draw_data, draw_len, &draw_defs, &draw_body) ||
!slice_svg (paint_data, paint_len, &paint_defs, &paint_body))
return;
fwrite (draw_data, 1, (draw_hdr_end - draw_data + 1), out_fp);
fputc ('\n', out_fp);
if (draw_defs.len || paint_defs.len)
{
fputs ("<defs>\n", out_fp);
if (draw_defs.len)
write_slice_resolving_palette_vars (draw_defs);
if (paint_defs.len)
write_slice_resolving_palette_vars (paint_defs);
fputs ("</defs>\n", out_fp);
}
emit_background_rect ();
if (foreground_use_palette && foreground_palette && foreground_palette->len)
emit_draw_body_with_palette (draw_body);
else
{
emit_fill_group_open ();
if (draw_body.len)
fwrite (draw_body.p, 1, draw_body.len, out_fp);
emit_fill_group_close ();
}
if (paint_body.len)
write_slice_resolving_palette_vars (paint_body);
if (show_extents)
emit_extents_overlay (direction == HB_DIRECTION_INVALID ? HB_DIRECTION_LTR : direction, line_step);
fputs ("</svg>\n", out_fp);
}
void emit_background_rect ()
{
if (!has_background)
return;
unsigned r = hb_color_get_red (background);
unsigned g = hb_color_get_green (background);
unsigned b = hb_color_get_blue (background);
unsigned a = hb_color_get_alpha (background);
fprintf (out_fp, "<rect x=\"%.*g\" y=\"%.*g\" width=\"%.*g\" height=\"%.*g\" fill=\"#%02X%02X%02X\"",
precision + 4, (double) final_extents.x,
precision + 4, (double) final_extents.y,
precision + 4, (double) final_extents.width,
precision + 4, (double) final_extents.height,
r, g, b);
if (a != 255)
fprintf (out_fp, " fill-opacity=\"%.3f\"", (double) a / 255.);
fputs ("/>\n", out_fp);
}
void apply_custom_palette (hb_vector_paint_t *paint)
{
hb_vector_paint_clear_custom_palette_colors (paint);
for (unsigned idx = 0; idx < custom_palette_values.size (); idx++)
if (idx < custom_palette_has_value.size () && custom_palette_has_value[idx])
hb_vector_paint_set_custom_palette_color (paint, idx, custom_palette_values[idx]);
}
void init_palette_color_cache ()
{
palette_cache_values.clear ();
palette_cache_state.clear ();
palette_face = nullptr;
palette_lookup_index = 0;
palette_lookup_enabled = false;
hb_face_t *face = hb_font_get_face (upem_font ? upem_font : font);
if (!face)
return;
unsigned palette_count = hb_ot_color_palette_get_count (face);
if (!palette_count)
return;
unsigned palette_index = palette >= 0 ? (unsigned) palette : 0u;
if (palette_index >= palette_count)
palette_index = 0;
palette_face = face;
palette_lookup_index = palette_index;
palette_lookup_enabled = true;
}
void load_custom_palette_overrides_from_view ()
{
custom_palette_values.clear ();
custom_palette_has_value.clear ();
if (!custom_palette_entries)
return;
for (unsigned i = 0; i < custom_palette_entries->len; i++)
{
const custom_palette_entry_t &entry =
g_array_index (custom_palette_entries, custom_palette_entry_t, i);
unsigned idx = entry.index;
if (custom_palette_values.size () <= idx)
{
custom_palette_values.resize (idx + 1, HB_COLOR (0, 0, 0, 255));
custom_palette_has_value.resize (idx + 1, false);
}
custom_palette_values[idx] = HB_COLOR (entry.color.b,
entry.color.g,
entry.color.r,
entry.color.a);
custom_palette_has_value[idx] = true;
}
}
void emit_extents_overlay (hb_direction_t dir, float step)
{
const bool vertical = HB_DIRECTION_IS_VERTICAL (dir);
const float dot_r = hb_max (1.f, step * 0.01f);
fputs ("<g fill=\"#FF0000\" fill-opacity=\"0.502\" stroke=\"none\">\n", out_fp);
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
const line_t &line = lines[li];
for (const auto &g : line.glyphs)
{
fprintf (out_fp, "<circle cx=\"%.*g\" cy=\"%.*g\" r=\"%.*g\"/>\n",
precision + 4, (double) (g.x + off_x),
precision + 4, (double) (-(g.y + off_y)),
precision + 4, (double) dot_r);
}
}
fputs ("</g>\n", out_fp);
fprintf (out_fp, "<g fill=\"none\" stroke=\"#FF00FF\" stroke-opacity=\"0.502\" stroke-width=\"%.*g\">\n",
precision + 4, normalized_stroke_width);
for (unsigned li = 0; li < lines.size (); li++)
{
float off_x = vertical ? -(step * (float) li) : 0.f;
float off_y = vertical ? 0.f : -(step * (float) li);
const line_t &line = lines[li];
for (const auto &g : line.glyphs)
{
hb_glyph_extents_t ge;
if (!hb_font_get_glyph_extents (upem_font, g.gid, &ge))
continue;
float x = g.x + off_x + ge.x_bearing;
float y = g.y + off_y + ge.y_bearing;
float w = ge.width;
float h = ge.height;
if (!w || !h)
continue;
fprintf (out_fp, "<rect x=\"%.*g\" y=\"%.*g\" width=\"%.*g\" height=\"%.*g\"/>\n",
precision + 4, (double) x,
precision + 4, (double) (-y),
precision + 4, (double) w,
precision + 4, (double) (-h));
}
}
fputs ("</g>\n", out_fp);
}
void get_line_metrics (hb_direction_t dir, float *asc, float *desc, float *gap) const
{
if (have_font_extents)
{
if (asc) *asc = (float) font_extents.ascent;
if (desc) *desc = (float) (-font_extents.descent);
if (gap) *gap = (float) font_extents.line_gap;
return;
}
hb_font_extents_t fext = {0, 0, 0};
hb_font_get_extents_for_direction (upem_font, dir, &fext);
if (asc) *asc = (float) fext.ascender;
if (desc) *desc = (float) fext.descender;
if (gap) *gap = (float) fext.line_gap;
}
hb_bool_t flat = false;
int precision = 2;
hb_color_t background = HB_COLOR (255, 255, 255, 255);
hb_color_t foreground = HB_COLOR (0, 0, 0, 255);
hb_font_t *font = nullptr;
hb_font_t *upem_font = nullptr;
std::vector<line_t> lines;
hb_direction_t direction = HB_DIRECTION_INVALID;
int x_scale = 0;
int y_scale = 0;
unsigned upem = 0;
unsigned subpixel_bits = 0;
double normalized_stroke_width = DEFAULT_STROKE_WIDTH;
float line_step = 0.f;
hb_vector_extents_t final_extents = {0, 0, 1, 1};
std::vector<hb_color_t> custom_palette_values;
std::vector<bool> custom_palette_has_value;
mutable std::vector<hb_color_t> palette_cache_values;
mutable std::vector<unsigned char> palette_cache_state;
hb_face_t *palette_face = nullptr;
unsigned palette_lookup_index = 0;
bool palette_lookup_enabled = false;
};
#endif