[cairo-commit] 12 commits - configure.ac .gitlab-ci.yml meson.build meson-cc-tests/fuzzer.c src/cairo-analysis-surface.c src/cairo.c src/cairo-ft-font.c src/cairo-ft-private.h src/cairoint.h src/cairo-matrix.c src/cairo-pattern.c src/cairo-pdf-surface.c src/cairo-png.c src/cairo-recording-surface.c src/cairo-svg-glyph-render.c src/cairo-user-font.c src/Makefile.sources src/meson.build test/cairo-logo-font.ttx test/cairo-svg-test-doc.ttx test/cairo-svg-test-fill.ttx test/cairo-svg-test-gradient.ttx test/cairo-svg-test-path.ttx test/cairo-svg-test-shapes.ttx test/cairo-svg-test-stroke.ttx test/cairo-svg-test-transform.ttx test/cairo-test.c test/cairo-test.h test/ft-svg-cairo-logo.c test/ft-svg-color-font.c test/ft-svg-render.c test/Makefile.am test/Makefile.sources test/meson.build test/reference test/svg
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Sat Dec 31 09:28:00 UTC 2022
.gitlab-ci.yml | 1
configure.ac | 3
meson-cc-tests/fuzzer.c | 7
meson.build | 8
src/Makefile.sources | 2
src/cairo-analysis-surface.c | 36
src/cairo-ft-font.c | 870 +++++-
src/cairo-ft-private.h | 15
src/cairo-matrix.c | 1
src/cairo-pattern.c | 5
src/cairo-pdf-surface.c | 2
src/cairo-png.c | 1
src/cairo-recording-surface.c | 1
src/cairo-svg-glyph-render.c | 3225 +++++++++++++++++++++++++
src/cairo-user-font.c | 2
src/cairo.c | 17
src/cairoint.h | 101
src/meson.build | 1
test/Makefile.am | 3
test/Makefile.sources | 3
test/cairo-logo-font.ttx | 539 ++++
test/cairo-svg-test-doc.ttx | 689 +++++
test/cairo-svg-test-fill.ttx | 365 ++
test/cairo-svg-test-gradient.ttx | 441 +++
test/cairo-svg-test-path.ttx | 281 ++
test/cairo-svg-test-shapes.ttx | 333 ++
test/cairo-svg-test-stroke.ttx | 608 ++++
test/cairo-svg-test-transform.ttx | 403 +++
test/cairo-test.c | 67
test/cairo-test.h | 5
test/ft-svg-cairo-logo.c | 66
test/ft-svg-color-font.c | 146 +
test/ft-svg-render.c | 159 +
test/meson.build | 46
test/reference/ft-svg-cairo-logo.ref.png |binary
test/reference/ft-svg-color-font.ref.png |binary
test/reference/ft-svg-render-doc.ref.png |binary
test/reference/ft-svg-render-fill.ref.png |binary
test/reference/ft-svg-render-gradient.ref.png |binary
test/reference/ft-svg-render-path.ref.png |binary
test/reference/ft-svg-render-shapes.ref.png |binary
test/reference/ft-svg-render-stroke.ref.png |binary
test/reference/ft-svg-render-transform.ref.png |binary
test/svg/README | 22
test/svg/build_ttx_fonts.py | 161 +
test/svg/doc.0.viewBox1.svg | 4
test/svg/doc.1.viewBox2.svg | 4
test/svg/doc.2.image.svg | 121
test/svg/doc.3.image-transform.svg | 122
test/svg/doc.4.clip-user.svg | 9
test/svg/doc.5.clip-object.svg | 10
test/svg/doc.6.clip-user2.svg | 10
test/svg/doc.7.clip-object2.svg | 11
test/svg/doc.8.clip-user3.svg | 15
test/svg/doc.9.clip-object3.svg | 15
test/svg/doc.A.g.svg | 14
test/svg/fill.0.name.svg | 4
test/svg/fill.1.hex6.svg | 4
test/svg/fill.2.hex3.svg | 4
test/svg/fill.3.rgb.svg | 4
test/svg/fill.4.current-color.svg | 4
test/svg/fill.5.palette.svg | 4
test/svg/fill.6.opacity.svg | 4
test/svg/fill.7.color.svg | 5
test/svg/fill.8.rule.svg | 8
test/svg/fuzzer/README | 19
test/svg/fuzzer/meson.build | 14
test/svg/fuzzer/svg-render-fuzzer.c | 57
test/svg/gradient.0.lin-pad.svg | 10
test/svg/gradient.1.lin-reflect.svg | 11
test/svg/gradient.2.lin-repeat.svg | 11
test/svg/gradient.3.lin-user.svg | 11
test/svg/gradient.4.lin-transform.svg | 10
test/svg/gradient.5.rad-pad.svg | 10
test/svg/gradient.6.rad-reflect.svg | 11
test/svg/gradient.7.rad-repeat.svg | 11
test/svg/gradient.8.rad-user.svg | 11
test/svg/gradient.9.rad-transform.svg | 11
test/svg/meson.build | 9
test/svg/path.0.line.svg | 8
test/svg/path.1.curve.svg | 9
test/svg/path.2.quad.svg | 8
test/svg/path.3.arc.svg | 7
test/svg/shapes.0.rect.svg | 3
test/svg/shapes.1.rounded-rect.svg | 3
test/svg/shapes.2.circle.svg | 3
test/svg/shapes.3.ellipse.svg | 3
test/svg/shapes.4.line.svg | 3
test/svg/shapes.5.polyline.svg | 12
test/svg/shapes.6.polygon.svg | 12
test/svg/stroke.0.name.svg | 6
test/svg/stroke.1.hex6.svg | 6
test/svg/stroke.2.hex3.svg | 6
test/svg/stroke.3.rgb.svg | 6
test/svg/stroke.4.current-color.svg | 6
test/svg/stroke.5.palette.svg | 6
test/svg/stroke.6.opacity.svg | 6
test/svg/stroke.7.color.svg | 7
test/svg/stroke.8.width.svg | 14
test/svg/stroke.9.cap.svg | 17
test/svg/stroke.A.dash.svg | 27
test/svg/stroke.B.dash-offset.svg | 31
test/svg/stroke.C.miter.svg | 10
test/svg/stroke.D.round.svg | 10
test/svg/stroke.E.bevel.svg | 10
test/svg/stroke.F.miter-limit.svg | 11
test/svg/svg-font-template.ttx | 190 +
test/svg/svg-render.c | 308 ++
test/svg/transform.0.translate.svg | 8
test/svg/transform.1.scale.svg | 10
test/svg/transform.2.rotate.svg | 10
test/svg/transform.3.skewX.svg | 10
test/svg/transform.4.skewY.svg | 10
test/svg/transform.5.matrix.svg | 10
test/svg/transform.6.multiple.svg | 16
test/svg/transform.7.stroke.svg | 20
116 files changed, 9837 insertions(+), 222 deletions(-)
New commits:
commit 3a60f6e138942af739b5998c521527e691ffeba4
Merge: 8c983c0d5 3e8b9a7cf
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Sat Dec 31 09:27:57 2022 +0000
Merge branch 'ft-svg-fonts' into 'master'
Support SVG fonts in FT backend
See merge request cairo/cairo!319
commit 3e8b9a7cf4417d862917ec3c50fe8f0f58142e46
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Wed Dec 28 14:00:33 2022 +1030
Add some missing slim_hidden entries
diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c
index f3cf684c9..cb49adcbe 100644
--- a/src/cairo-matrix.c
+++ b/src/cairo-matrix.c
@@ -304,6 +304,7 @@ cairo_matrix_rotate (cairo_matrix_t *matrix, double radians)
cairo_matrix_multiply (matrix, &tmp, matrix);
}
+slim_hidden_def (cairo_matrix_rotate);
/**
* cairo_matrix_multiply:
diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c
index e969f0a17..2cbd52b2c 100644
--- a/src/cairo-pattern.c
+++ b/src/cairo-pattern.c
@@ -812,6 +812,7 @@ cairo_pattern_create_linear (double x0, double y0, double x1, double y1)
return &pattern->base.base;
}
+slim_hidden_def (cairo_pattern_create_linear);
/**
* cairo_pattern_create_radial:
@@ -866,6 +867,7 @@ cairo_pattern_create_radial (double cx0, double cy0, double radius0,
return &pattern->base.base;
}
+slim_hidden_def (cairo_pattern_create_radial);
/* This order is specified in the diagram in the documentation for
* cairo_pattern_create_mesh() */
@@ -1093,6 +1095,7 @@ cairo_pattern_get_type (cairo_pattern_t *pattern)
{
return pattern->type;
}
+slim_hidden_def (cairo_pattern_get_type);
/**
* cairo_pattern_status:
@@ -2117,6 +2120,7 @@ cairo_pattern_set_extend (cairo_pattern_t *pattern, cairo_extend_t extend)
pattern->extend = extend;
_cairo_pattern_notify_observers (pattern, CAIRO_PATTERN_NOTIFY_EXTEND);
}
+slim_hidden_def (cairo_pattern_set_extend);
/**
* cairo_pattern_get_extend:
@@ -4197,6 +4201,7 @@ cairo_pattern_get_rgba (cairo_pattern_t *pattern,
return CAIRO_STATUS_SUCCESS;
}
+slim_hidden_def (cairo_pattern_get_rgba);
/**
* cairo_pattern_get_surface:
diff --git a/src/cairo-png.c b/src/cairo-png.c
index 4b7c34081..5b9c58447 100644
--- a/src/cairo-png.c
+++ b/src/cairo-png.c
@@ -987,3 +987,4 @@ cairo_image_surface_create_from_png_stream (cairo_read_func_t read_func,
return read_png (&png_closure);
}
+slim_hidden_def (cairo_image_surface_create_from_png_stream);
diff --git a/src/cairo-recording-surface.c b/src/cairo-recording-surface.c
index 065e62c46..d6b6ab337 100644
--- a/src/cairo-recording-surface.c
+++ b/src/cairo-recording-surface.c
@@ -2309,6 +2309,7 @@ DONE:
if (height)
*height = _cairo_fixed_to_double (bbox.p2.y - bbox.p1.y);
}
+slim_hidden_def (cairo_recording_surface_ink_extents);
cairo_status_t
_cairo_recording_surface_get_bbox (cairo_recording_surface_t *surface,
diff --git a/src/cairo.c b/src/cairo.c
index e9c4bb9a8..7bfc6a143 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -706,6 +706,7 @@ cairo_push_group (cairo_t *cr)
{
cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
}
+slim_hidden_def (cairo_push_group);
/**
* cairo_push_group_with_content:
@@ -813,6 +814,7 @@ cairo_pop_group_to_source (cairo_t *cr)
cairo_set_source (cr, group_pattern);
cairo_pattern_destroy (group_pattern);
}
+slim_hidden_def (cairo_pop_group_to_source);
/**
* cairo_set_operator:
@@ -939,6 +941,7 @@ cairo_set_source_rgba (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_set_source_rgba);
/**
* cairo_set_source_surface:
@@ -1052,6 +1055,7 @@ cairo_get_source (cairo_t *cr)
return cr->backend->get_source (cr);
}
+slim_hidden_def (cairo_get_source);
/**
* cairo_set_tolerance:
@@ -1340,6 +1344,7 @@ cairo_set_dash (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_set_dash);
/**
* cairo_get_dash_count:
@@ -1514,6 +1519,7 @@ cairo_rotate (cairo_t *cr, double angle)
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_rotate);
/**
* cairo_transform:
@@ -1903,6 +1909,7 @@ cairo_arc (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_arc);
/**
* cairo_arc_negative:
@@ -1948,6 +1955,7 @@ cairo_arc_negative (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_arc_negative);
/* XXX: NYI
void
@@ -2130,6 +2138,7 @@ cairo_rectangle (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_rectangle);
#if 0
/* XXX: NYI */
@@ -2290,6 +2299,7 @@ cairo_paint_with_alpha (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_paint_with_alpha);
/**
* cairo_mask:
@@ -2465,6 +2475,7 @@ cairo_fill (cairo_t *cr)
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_fill);
/**
* cairo_fill_preserve:
@@ -2756,6 +2767,7 @@ cairo_clip (cairo_t *cr)
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_clip);
/**
* cairo_clip_preserve:
@@ -4006,6 +4018,7 @@ cairo_has_current_point (cairo_t *cr)
return cr->backend->has_current_point (cr);
}
+slim_hidden_def (cairo_has_current_point);
/**
* cairo_get_current_point:
@@ -4076,6 +4089,7 @@ cairo_get_fill_rule (cairo_t *cr)
return cr->backend->get_fill_rule (cr);
}
+slim_hidden_def (cairo_set_fill_rule);
/**
* cairo_get_line_width:
@@ -4176,6 +4190,7 @@ cairo_get_miter_limit (cairo_t *cr)
return cr->backend->get_miter_limit (cr);
}
+slim_hidden_def (cairo_set_miter_limit);
/**
* cairo_get_matrix:
@@ -4291,6 +4306,7 @@ cairo_copy_path (cairo_t *cr)
return cr->backend->copy_path (cr);
}
+slim_hidden_def (cairo_copy_path);
/**
* cairo_copy_path_flat:
@@ -4385,6 +4401,7 @@ cairo_append_path (cairo_t *cr,
if (unlikely (status))
_cairo_set_error (cr, status);
}
+slim_hidden_def (cairo_append_path);
/**
* cairo_status:
diff --git a/src/cairoint.h b/src/cairoint.h
index 987bf9a58..af0f0fadc 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1939,11 +1939,18 @@ cairo_private cairo_status_t
_cairo_fopen (const char *filename, const char *mode, FILE **file_out);
/* Avoid unnecessary PLT entries. */
+slim_hidden_proto (cairo_append_path);
+slim_hidden_proto (cairo_arc);
+slim_hidden_proto (cairo_arc_negative);
+slim_hidden_proto (cairo_clip);
slim_hidden_proto (cairo_clip_preserve);
slim_hidden_proto (cairo_close_path);
+slim_hidden_proto (cairo_copy_path);
slim_hidden_proto (cairo_create);
slim_hidden_proto (cairo_curve_to);
slim_hidden_proto (cairo_destroy);
+slim_hidden_proto (cairo_device_to_user);
+slim_hidden_proto (cairo_fill);
slim_hidden_proto (cairo_fill_preserve);
slim_hidden_proto (cairo_font_face_destroy);
slim_hidden_proto (cairo_font_face_get_user_data);
@@ -1959,14 +1966,16 @@ slim_hidden_proto (cairo_font_options_set_subpixel_order);
slim_hidden_proto (cairo_font_options_status);
slim_hidden_proto (cairo_format_stride_for_width);
slim_hidden_proto (cairo_get_current_point);
-slim_hidden_proto (cairo_get_line_width);
slim_hidden_proto (cairo_get_hairline);
+slim_hidden_proto (cairo_get_line_width);
slim_hidden_proto (cairo_get_matrix);
slim_hidden_proto (cairo_get_scaled_font);
+slim_hidden_proto (cairo_get_source);
slim_hidden_proto (cairo_get_target);
slim_hidden_proto (cairo_get_tolerance);
slim_hidden_proto (cairo_glyph_allocate);
slim_hidden_proto (cairo_glyph_free);
+slim_hidden_proto (cairo_has_current_point);
slim_hidden_proto (cairo_image_surface_create);
slim_hidden_proto (cairo_image_surface_create_for_data);
slim_hidden_proto (cairo_image_surface_get_data);
@@ -1983,35 +1992,70 @@ slim_hidden_proto (cairo_matrix_init_scale);
slim_hidden_proto (cairo_matrix_init_translate);
slim_hidden_proto (cairo_matrix_invert);
slim_hidden_proto (cairo_matrix_multiply);
+slim_hidden_proto (cairo_matrix_rotate);
slim_hidden_proto (cairo_matrix_scale);
slim_hidden_proto (cairo_matrix_transform_distance);
slim_hidden_proto (cairo_matrix_transform_point);
slim_hidden_proto (cairo_matrix_translate);
+slim_hidden_proto (cairo_mesh_pattern_curve_to);
+slim_hidden_proto (cairo_mesh_pattern_get_control_point);
+slim_hidden_proto (cairo_mesh_pattern_get_corner_color_rgba);
+slim_hidden_proto (cairo_mesh_pattern_get_patch_count);
+slim_hidden_proto (cairo_mesh_pattern_get_path);
+slim_hidden_proto (cairo_mesh_pattern_line_to);
+slim_hidden_proto (cairo_mesh_pattern_move_to);
+slim_hidden_proto (cairo_mesh_pattern_set_corner_color_rgba);
slim_hidden_proto (cairo_move_to);
slim_hidden_proto (cairo_new_path);
slim_hidden_proto (cairo_paint);
+slim_hidden_proto (cairo_paint_with_alpha);
+slim_hidden_proto_no_warn (cairo_path_destroy);
slim_hidden_proto (cairo_pattern_add_color_stop_rgba);
slim_hidden_proto (cairo_pattern_create_for_surface);
+slim_hidden_proto (cairo_pattern_create_linear);
+slim_hidden_proto (cairo_pattern_create_radial);
slim_hidden_proto (cairo_pattern_create_rgb);
slim_hidden_proto (cairo_pattern_create_rgba);
slim_hidden_proto (cairo_pattern_destroy);
slim_hidden_proto (cairo_pattern_get_extend);
-slim_hidden_proto (cairo_mesh_pattern_curve_to);
-slim_hidden_proto (cairo_mesh_pattern_get_control_point);
-slim_hidden_proto (cairo_mesh_pattern_get_corner_color_rgba);
-slim_hidden_proto (cairo_mesh_pattern_get_patch_count);
-slim_hidden_proto (cairo_mesh_pattern_get_path);
-slim_hidden_proto (cairo_mesh_pattern_line_to);
-slim_hidden_proto (cairo_mesh_pattern_move_to);
-slim_hidden_proto (cairo_mesh_pattern_set_corner_color_rgba);
+slim_hidden_proto (cairo_pattern_get_rgba);
+slim_hidden_proto (cairo_pattern_get_type);
slim_hidden_proto_no_warn (cairo_pattern_reference);
+slim_hidden_proto (cairo_pattern_set_extend);
slim_hidden_proto (cairo_pattern_set_matrix);
slim_hidden_proto (cairo_pop_group);
+slim_hidden_proto (cairo_pop_group_to_source);
+slim_hidden_proto (cairo_push_group);
slim_hidden_proto (cairo_push_group_with_content);
-slim_hidden_proto_no_warn (cairo_path_destroy);
slim_hidden_proto (cairo_recording_surface_create);
+slim_hidden_proto (cairo_recording_surface_ink_extents);
+slim_hidden_proto (cairo_rectangle);
+slim_hidden_proto (cairo_region_contains_point);
+slim_hidden_proto (cairo_region_contains_rectangle);
+slim_hidden_proto (cairo_region_copy);
+slim_hidden_proto (cairo_region_create);
+slim_hidden_proto (cairo_region_create_rectangle);
+slim_hidden_proto (cairo_region_create_rectangles);
+slim_hidden_proto (cairo_region_destroy);
+slim_hidden_proto (cairo_region_equal);
+slim_hidden_proto (cairo_region_get_extents);
+slim_hidden_proto (cairo_region_get_rectangle);
+slim_hidden_proto (cairo_region_intersect);
+slim_hidden_proto (cairo_region_intersect_rectangle);
+slim_hidden_proto (cairo_region_is_empty);
+slim_hidden_proto (cairo_region_num_rectangles);
+slim_hidden_proto (cairo_region_reference);
+slim_hidden_proto (cairo_region_status);
+slim_hidden_proto (cairo_region_subtract);
+slim_hidden_proto (cairo_region_subtract_rectangle);
+slim_hidden_proto (cairo_region_translate);
+slim_hidden_proto (cairo_region_union);
+slim_hidden_proto (cairo_region_union_rectangle);
+slim_hidden_proto (cairo_region_xor);
+slim_hidden_proto (cairo_region_xor_rectangle);
slim_hidden_proto (cairo_rel_line_to);
slim_hidden_proto (cairo_restore);
+slim_hidden_proto (cairo_rotate);
slim_hidden_proto (cairo_save);
slim_hidden_proto (cairo_scale);
slim_hidden_proto (cairo_scaled_font_create);
@@ -2021,23 +2065,27 @@ slim_hidden_proto (cairo_scaled_font_get_ctm);
slim_hidden_proto (cairo_scaled_font_get_font_face);
slim_hidden_proto (cairo_scaled_font_get_font_matrix);
slim_hidden_proto (cairo_scaled_font_get_font_options);
+slim_hidden_proto (cairo_scaled_font_get_user_data);
slim_hidden_proto (cairo_scaled_font_glyph_extents);
slim_hidden_proto_no_warn (cairo_scaled_font_reference);
-slim_hidden_proto (cairo_scaled_font_status);
-slim_hidden_proto (cairo_scaled_font_get_user_data);
slim_hidden_proto (cairo_scaled_font_set_user_data);
+slim_hidden_proto (cairo_scaled_font_status);
slim_hidden_proto (cairo_scaled_font_text_to_glyphs);
+slim_hidden_proto (cairo_set_dash);
+slim_hidden_proto (cairo_set_fill_rule);
slim_hidden_proto (cairo_set_font_matrix);
slim_hidden_proto (cairo_set_font_options);
slim_hidden_proto (cairo_set_font_size);
+slim_hidden_proto (cairo_set_hairline);
slim_hidden_proto (cairo_set_line_cap);
slim_hidden_proto (cairo_set_line_join);
slim_hidden_proto (cairo_set_line_width);
-slim_hidden_proto (cairo_set_hairline);
slim_hidden_proto (cairo_set_matrix);
+slim_hidden_proto (cairo_set_miter_limit);
slim_hidden_proto (cairo_set_operator);
slim_hidden_proto (cairo_set_source);
slim_hidden_proto (cairo_set_source_rgb);
+slim_hidden_proto (cairo_set_source_rgba);
slim_hidden_proto (cairo_set_source_surface);
slim_hidden_proto (cairo_set_tolerance);
slim_hidden_proto (cairo_status);
@@ -2068,43 +2116,20 @@ slim_hidden_proto (cairo_text_cluster_free);
slim_hidden_proto (cairo_toy_font_face_create);
slim_hidden_proto (cairo_toy_font_face_get_slant);
slim_hidden_proto (cairo_toy_font_face_get_weight);
-slim_hidden_proto (cairo_translate);
slim_hidden_proto (cairo_transform);
+slim_hidden_proto (cairo_translate);
slim_hidden_proto (cairo_user_font_face_create);
slim_hidden_proto (cairo_user_font_face_set_init_func);
slim_hidden_proto (cairo_user_font_face_set_render_color_glyph_func);
slim_hidden_proto (cairo_user_font_face_set_render_glyph_func);
slim_hidden_proto (cairo_user_font_face_set_unicode_to_glyph_func);
-slim_hidden_proto (cairo_device_to_user);
slim_hidden_proto (cairo_user_to_device);
slim_hidden_proto (cairo_user_to_device_distance);
slim_hidden_proto (cairo_version_string);
-slim_hidden_proto (cairo_region_create);
-slim_hidden_proto (cairo_region_create_rectangle);
-slim_hidden_proto (cairo_region_create_rectangles);
-slim_hidden_proto (cairo_region_copy);
-slim_hidden_proto (cairo_region_reference);
-slim_hidden_proto (cairo_region_destroy);
-slim_hidden_proto (cairo_region_equal);
-slim_hidden_proto (cairo_region_status);
-slim_hidden_proto (cairo_region_get_extents);
-slim_hidden_proto (cairo_region_num_rectangles);
-slim_hidden_proto (cairo_region_get_rectangle);
-slim_hidden_proto (cairo_region_is_empty);
-slim_hidden_proto (cairo_region_contains_rectangle);
-slim_hidden_proto (cairo_region_contains_point);
-slim_hidden_proto (cairo_region_translate);
-slim_hidden_proto (cairo_region_subtract);
-slim_hidden_proto (cairo_region_subtract_rectangle);
-slim_hidden_proto (cairo_region_intersect);
-slim_hidden_proto (cairo_region_intersect_rectangle);
-slim_hidden_proto (cairo_region_union);
-slim_hidden_proto (cairo_region_union_rectangle);
-slim_hidden_proto (cairo_region_xor);
-slim_hidden_proto (cairo_region_xor_rectangle);
#if CAIRO_HAS_PNG_FUNCTIONS
+slim_hidden_proto (cairo_image_surface_create_from_png_stream);
slim_hidden_proto (cairo_surface_write_to_png_stream);
#endif
commit 1ba3e40d9c4a51d9697b6d9ff90a75bb419dd43e
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Wed Dec 28 12:44:46 2022 +1030
FT: support COLRv0 recording surface
diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c
index c27800b00..100f839d7 100644
--- a/src/cairo-ft-font.c
+++ b/src/cairo-ft-font.c
@@ -44,6 +44,8 @@
#include "cairo-error-private.h"
#include "cairo-image-surface-private.h"
#include "cairo-ft-private.h"
+#include "cairo-list-inline.h"
+#include "cairo-path-private.h"
#include "cairo-pattern-private.h"
#include "cairo-pixman-private.h"
#include "cairo-recording-surface-private.h"
@@ -2491,6 +2493,99 @@ _cairo_ft_scaled_glyph_load_glyph (cairo_ft_scaled_font_t *scaled_font,
return CAIRO_STATUS_SUCCESS;
}
+typedef enum {
+ CAIRO_FT_GLYPH_TYPE_BITMAP,
+ CAIRO_FT_GLYPH_TYPE_OUTLINE,
+ CAIRO_FT_GLYPH_TYPE_SVG,
+ CAIRO_FT_GLYPH_TYPE_COLR_V0,
+} cairo_ft_glyph_format_t;
+
+typedef struct {
+ cairo_scaled_glyph_private_t base;
+
+ cairo_ft_glyph_format_t format;
+} cairo_ft_glyph_private_t;
+
+static void
+_cairo_ft_glyph_fini (cairo_scaled_glyph_private_t *glyph_private,
+ cairo_scaled_glyph_t *glyph,
+ cairo_scaled_font_t *font)
+{
+ cairo_list_del (&glyph_private->link);
+ free (glyph_private);
+}
+
+
+#ifdef HAVE_FT_PALETTE_SELECT
+static void
+_cairo_ft_scaled_glyph_set_palette (cairo_ft_scaled_font_t *scaled_font,
+ FT_Face face,
+ unsigned int *num_entries_ret,
+ FT_Color **entries_ret)
+{
+ FT_Palette_Data palette_data;
+ unsigned int num_entries;
+ FT_Color *entries;
+
+ num_entries = 0;
+ entries = NULL;
+ if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) {
+ FT_UShort palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
+ if (scaled_font->base.options.palette_index < palette_data.num_palettes)
+ palette_index = scaled_font->base.options.palette_index;
+
+ num_entries = palette_data.num_palettes;
+ if (FT_Palette_Select (face, palette_index, &entries) != 0) {
+ num_entries = 0;
+ entries = NULL;
+ }
+ }
+ if (num_entries_ret)
+ *num_entries_ret = num_entries;
+
+ if (entries_ret)
+ *entries_ret = entries;
+}
+#endif
+
+/* returns TRUE if foreground color used */
+static cairo_bool_t
+_cairo_ft_scaled_glyph_set_foreground_color (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face,
+ const cairo_color_t *foreground_color)
+{
+ cairo_bool_t uses_foreground_color = FALSE;
+#ifdef HAVE_FT_PALETTE_SELECT
+ FT_LayerIterator iterator;
+ FT_UInt layer_glyph_index;
+ FT_UInt layer_color_index;
+ FT_Color color;
+
+ /* Check if there is a layer that uses the foreground color */
+ iterator.p = NULL;
+ while (FT_Get_Color_Glyph_Layer(face,
+ _cairo_scaled_glyph_index (scaled_glyph),
+ &layer_glyph_index,
+ &layer_color_index,
+ &iterator)) {
+ if (layer_color_index == 0xFFFF) {
+ uses_foreground_color = TRUE;
+ break;
+ }
+ }
+
+ if (uses_foreground_color) {
+ color.red = (FT_Byte)(foreground_color->red * 0xFF);
+ color.green = (FT_Byte)(foreground_color->green * 0xFF);
+ color.blue = (FT_Byte)(foreground_color->blue * 0xFF);
+ color.alpha = (FT_Byte)(foreground_color->alpha * 0xFF);
+ FT_Palette_Set_Foreground_Color (face, color);
+ }
+#endif
+ return uses_foreground_color;
+}
+
static cairo_int_status_t
_cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
cairo_scaled_glyph_t *scaled_glyph,
@@ -2516,41 +2611,12 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
return CAIRO_INT_STATUS_UNSUPPORTED;
}
+ uses_foreground_color = _cairo_ft_scaled_glyph_set_foreground_color (scaled_font,
+ scaled_glyph,
+ face,
+ foreground_color);
#ifdef HAVE_FT_PALETTE_SELECT
- FT_LayerIterator iterator;
- FT_UInt layer_glyph_index;
- FT_UInt layer_color_index;
- FT_Color color;
- FT_Palette_Data palette_data;
-
- /* Check if there is a layer that uses the foreground color */
- iterator.p = NULL;
- while (FT_Get_Color_Glyph_Layer(face,
- _cairo_scaled_glyph_index(scaled_glyph),
- &layer_glyph_index,
- &layer_color_index,
- &iterator)) {
- if (layer_color_index == 0xFFFF) {
- uses_foreground_color = TRUE;
- break;
- }
- }
-
- if (uses_foreground_color) {
- color.red = (FT_Byte)(foreground_color->red * 0xFF);
- color.green = (FT_Byte)(foreground_color->green * 0xFF);
- color.blue = (FT_Byte)(foreground_color->blue * 0xFF);
- color.alpha = (FT_Byte)(foreground_color->alpha * 0xFF);
- FT_Palette_Set_Foreground_Color (face, color);
- }
-
- if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) {
- FT_UShort palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
- if (scaled_font->base.options.palette_index < palette_data.num_palettes)
- palette_index = scaled_font->base.options.palette_index;
-
- FT_Palette_Select (face, palette_index, NULL);
- }
+ _cairo_ft_scaled_glyph_set_palette (scaled_font, face, NULL, NULL);
#endif
load_flags &= ~FT_LOAD_MONOCHROME;
@@ -2626,6 +2692,100 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
return status;
}
+#ifdef HAVE_FT_PALETTE_SELECT
+static cairo_int_status_t
+_cairo_ft_scaled_glyph_init_record_colr_v0_glyph (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face,
+ cairo_bool_t vertical_layout,
+ int load_flags)
+{
+ cairo_surface_t *recording_surface;
+ cairo_t *cr;
+ cairo_status_t status;
+ FT_Color *palette;
+ unsigned int num_palette_entries;
+ FT_LayerIterator iterator;
+ FT_UInt layer_glyph_index;
+ FT_UInt layer_color_index;
+ cairo_path_fixed_t *path_fixed;
+ cairo_path_t *path;
+
+ _cairo_ft_scaled_glyph_set_palette (scaled_font, face, &num_palette_entries, &palette);
+
+ load_flags &= ~FT_LOAD_MONOCHROME;
+ /* clear load target mode */
+ load_flags &= ~(FT_LOAD_TARGET_(FT_LOAD_TARGET_MODE(load_flags)));
+ load_flags |= FT_LOAD_TARGET_NORMAL;
+ load_flags |= FT_LOAD_COLOR;
+
+ recording_surface =
+ cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+
+ cr = cairo_create (recording_surface);
+
+ if (!_cairo_matrix_is_scale_0 (&scaled_font->base.scale)) {
+ cairo_matrix_t scale;
+ scale = scaled_font->base.scale;
+ scale.x0 = scale.y0 = 0.;
+ cairo_set_matrix (cr, &scale);
+ }
+
+ iterator.p = NULL;
+ while (FT_Get_Color_Glyph_Layer(face,
+ _cairo_scaled_glyph_index (scaled_glyph),
+ &layer_glyph_index,
+ &layer_color_index,
+ &iterator))
+ {
+ cairo_pattern_t *pattern;
+ if (layer_color_index == 0xFFFF) {
+ pattern = cairo_pattern_create_rgb (0, 0, 0);
+ pattern->is_userfont_foreground = TRUE;
+ } else {
+ double r = 0, g = 0, b = 0, a = 1;
+ if (layer_color_index < num_palette_entries) {
+ FT_Color *color = &palette[layer_color_index];
+ r = color->red / 255.0;
+ g = color->green/ 255.0;
+ b = color->blue / 255.0;
+ a = color->alpha / 255.0;
+ }
+ pattern = cairo_pattern_create_rgba (r, g, b, a);
+ }
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+
+ if (FT_Load_Glyph (face, layer_glyph_index, load_flags) != 0) {
+ status = CAIRO_INT_STATUS_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ status = _decompose_glyph_outline (face, &scaled_font->ft_options.base, &path_fixed);
+ if (unlikely (status))
+ return status;
+
+ path = _cairo_path_create (path_fixed, cr);
+ _cairo_path_fixed_destroy (path_fixed);
+ cairo_append_path(cr, path);
+ cairo_fill (cr);
+ }
+
+ cleanup:
+ cairo_destroy (cr);
+
+ if (status) {
+ cairo_surface_destroy (recording_surface);
+ return status;
+ }
+
+ _cairo_scaled_glyph_set_recording_surface (scaled_glyph,
+ &scaled_font->base,
+ recording_surface);
+ return status;
+}
+#endif
+
#if HAVE_FT_SVG_DOCUMENT
static cairo_int_status_t
_cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_font,
@@ -2639,8 +2799,8 @@ _cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_fon
cairo_pattern_t *pattern;
FT_SVG_Document svg_doc = face->glyph->other;
char *svg_document;
- FT_Color* palette;
- FT_Palette_Data palette_data;
+ FT_Color *palette;
+ unsigned int num_palette_entries;
/* Create NULL terminated SVG document */
svg_document = strndup((const char*)svg_doc->svg_document, svg_doc->svg_document_length);
@@ -2670,15 +2830,7 @@ _cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_fon
extents->width = DOUBLE_FROM_26_6(face->bbox.xMax) - extents->x_bearing;
extents->height = DOUBLE_FROM_26_6(face->bbox.yMax) - extents->y_bearing;
- palette = NULL;
- if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) {
- FT_UShort palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
- if (scaled_font->base.options.palette_index < palette_data.num_palettes)
- palette_index = scaled_font->base.options.palette_index;
-
- if (FT_Palette_Select (face, palette_index, &palette) != 0)
- palette = NULL;
- }
+ _cairo_ft_scaled_glyph_set_palette (scaled_font, face, &num_palette_entries, &palette);
if (!_cairo_matrix_is_scale_0 (&scaled_font->base.scale)) {
status = _cairo_render_svg_glyph (svg_document,
@@ -2687,7 +2839,7 @@ _cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_fon
_cairo_scaled_glyph_index(scaled_glyph),
svg_doc->units_per_EM,
palette,
- palette ? palette_data.num_palette_entries : 0,
+ num_palette_entries,
cr);
if (status == CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED)
status = CAIRO_INT_STATUS_UNSUPPORTED;
@@ -2927,53 +3079,33 @@ _cairo_ft_scaled_glyph_get_metrics (cairo_ft_scaled_font_t *scaled_font,
}
}
-static cairo_int_status_t
-_cairo_ft_scaled_glyph_init_metrics_svg_glyph (cairo_ft_scaled_font_t *scaled_font,
- cairo_scaled_glyph_t *scaled_glyph,
- FT_Face face,
- cairo_bool_t vertical_layout,
- int load_flags)
-{
- cairo_int_status_t status = CAIRO_INT_STATUS_UNSUPPORTED;
-#if HAVE_FT_SVG_DOCUMENT
- cairo_text_extents_t fs_metrics;
- cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
-
- if (scaled_font->base.options.color_mode == CAIRO_COLOR_MODE_NO_COLOR)
- return CAIRO_INT_STATUS_UNSUPPORTED;
-
- status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
- scaled_glyph,
- face,
- load_flags | FT_LOAD_COLOR,
- !hint_metrics,
- vertical_layout);
- if (unlikely (status))
- return status;
-
- if (face->glyph->format != FT_GLYPH_FORMAT_SVG)
- return CAIRO_INT_STATUS_UNSUPPORTED;
-
- /* Get the advance. The other metrics are ignored */
- _cairo_ft_scaled_glyph_get_metrics (scaled_font,
- face,
- vertical_layout,
- load_flags,
- &fs_metrics);
+static cairo_bool_t
+_cairo_ft_scaled_glyph_is_colr_v0 (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face)
- status = (cairo_int_status_t)_cairo_ft_scaled_glyph_init_record_svg_glyph (scaled_font,
- scaled_glyph,
- face,
- &fs_metrics);
- if (status == CAIRO_INT_STATUS_SUCCESS) {
- _cairo_scaled_glyph_set_metrics (scaled_glyph,
- &scaled_font->base,
- &fs_metrics);
+{
+#ifdef HAVE_FT_PALETTE_SELECT
+ FT_LayerIterator iterator;
+ FT_UInt layer_glyph_index;
+ FT_UInt layer_color_index;
+
+ iterator.p = NULL;
+ if (FT_Get_Color_Glyph_Layer(face,
+ _cairo_scaled_glyph_index (scaled_glyph),
+ &layer_glyph_index,
+ &layer_color_index,
+ &iterator))
+ {
+ return TRUE;
}
#endif
- return status;
+
+ return FALSE;
}
+static const int ft_glyph_private_key;
+
static cairo_int_status_t
_cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
cairo_scaled_glyph_t *scaled_glyph,
@@ -2983,32 +3115,25 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
{
cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
cairo_text_extents_t fs_metrics;
+ cairo_ft_glyph_private_t *glyph_priv;
cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
/* _cairo_ft_scaled_glyph_init_metrics() is called once the first
- * time a cairo_scaled_glyph_t is created. We first check if this
- * is an SVG glyph as SVG glyphs require the bounding box to be
- * obtained from the ink extents of the SVG rendering.
- *
- * If an SVG glyph is found and succesfully rendered to a
- * recording surface, the presence of
- * CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE in the
- * cairo_scaled_glyph_t indicates that this is an SVG glyph.
+ * time a cairo_scaled_glyph_t is created. We first allocate the
+ * cairo_ft_glyph_private_t struct and determine the glyph type.
*/
- status = _cairo_ft_scaled_glyph_init_metrics_svg_glyph (scaled_font,
- scaled_glyph,
- face,
- vertical_layout,
- load_flags);
- if (status != CAIRO_INT_STATUS_UNSUPPORTED)
- return status;
- /* The font metrics for color glyphs should be the same as the
- * outline glyphs. But just in case there aren't, request the
- * color or outline metrics based on the font option and if the
- * font has color.
- */
+ glyph_priv = _cairo_malloc (sizeof (*glyph_priv));
+ if (unlikely (glyph_priv == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ _cairo_scaled_glyph_attach_private (scaled_glyph, &glyph_priv->base,
+ &ft_glyph_private_key,
+ _cairo_ft_glyph_fini);
+ scaled_glyph->dev_private = glyph_priv;
+
+ /* We need to load color to determine if this is a color format. */
int color_flag = 0;
#ifdef FT_LOAD_COLOR
if (scaled_font->unscaled->have_color && scaled_font->base.options.color_mode != CAIRO_COLOR_MODE_NO_COLOR)
@@ -3023,12 +3148,45 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
if (unlikely (status))
return status;
+ cairo_bool_t is_svg_format = FALSE;
+#if HAVE_FT_SVG_DOCUMENT
+ if (face->glyph->format == FT_GLYPH_FORMAT_SVG)
+ is_svg_format = TRUE;
+#endif
+
+ if (is_svg_format) {
+ glyph_priv->format = CAIRO_FT_GLYPH_TYPE_SVG;
+ } else if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
+ if (_cairo_ft_scaled_glyph_is_colr_v0 (scaled_font, scaled_glyph, face))
+ glyph_priv->format = CAIRO_FT_GLYPH_TYPE_COLR_V0;
+ else
+ glyph_priv->format = CAIRO_FT_GLYPH_TYPE_OUTLINE;
+ } else {
+ /* For anything else we let FreeType render a bitmap. */
+ glyph_priv->format = CAIRO_FT_GLYPH_TYPE_BITMAP;
+ }
+
_cairo_ft_scaled_glyph_get_metrics (scaled_font,
face,
vertical_layout,
load_flags,
&fs_metrics);
+#if HAVE_FT_SVG_DOCUMENT
+ if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_SVG) {
+ /* SVG glyphs require the bounding box to be obtained from the
+ * ink extents of the SVG rendering. We need to render the SVG
+ * to a recording surface to obtain these extents. But we also
+ * need the advance from _cairo_ft_scaled_glyph_get_metrics()
+ * before calling this function.
+ */
+ status = (cairo_int_status_t)_cairo_ft_scaled_glyph_init_record_svg_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ &fs_metrics);
+ }
+#endif
+
_cairo_scaled_glyph_set_metrics (scaled_glyph,
&scaled_font->base,
&fs_metrics);
@@ -3049,6 +3207,7 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
cairo_bool_t vertical_layout = FALSE;
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_bool_t scaled_glyph_loaded = FALSE;
+ cairo_ft_glyph_private_t *glyph_priv;
face = _cairo_ft_unscaled_font_lock_face (unscaled);
if (!face)
@@ -3082,13 +3241,39 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
goto FAIL;
}
+ /* scaled_glyph->dev_private is intialized by _cairo_ft_scaled_glyph_init_metrics() */
+ glyph_priv = scaled_glyph->dev_private;
+ assert (glyph_priv != NULL);
+
+ if (info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) {
+ switch (glyph_priv->format) {
+ case CAIRO_FT_GLYPH_TYPE_BITMAP:
+ case CAIRO_FT_GLYPH_TYPE_OUTLINE:
+ status = CAIRO_INT_STATUS_UNSUPPORTED;
+ break;
+ case CAIRO_FT_GLYPH_TYPE_SVG:
+ /* The SVG recording surface is initialized in _cairo_ft_scaled_glyph_init_metrics() */
+ status = CAIRO_STATUS_SUCCESS;
+ break;
+ case CAIRO_FT_GLYPH_TYPE_COLR_V0:
+#ifdef HAVE_FT_PALETTE_SELECT
+ status = _cairo_ft_scaled_glyph_init_record_colr_v0_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ vertical_layout,
+ load_flags);
+#endif
+ break;
+ }
+ if (status)
+ goto FAIL;
+ }
+
if (info & CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE) {
- if (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) {
+ if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_SVG) {
status = _cairo_ft_scaled_glyph_init_surface_svg_glyph (scaled_font,
scaled_glyph,
foreground_color);
- if (unlikely (status))
- goto FAIL;
} else {
status = _cairo_ft_scaled_glyph_init_surface (scaled_font,
scaled_glyph,
@@ -3097,9 +3282,9 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
foreground_color,
vertical_layout,
load_flags);
- if (unlikely (status))
- goto FAIL;
}
+ if (unlikely (status))
+ goto FAIL;
}
if (info & CAIRO_SCALED_GLYPH_INFO_SURFACE) {
commit 4f9b6371238a8b9c88ad4644159ec5d581386968
Author: Jonathan Kew <jfkthame at googlemail.com>
Date: Mon Jun 27 12:36:25 2022 +0000
Fix x/y typo in _cairo_pdf_surface_analyze_operation
This can result in spuriously returning UNSUPPORTED and generating rasterized output in cases where this isn't actually necessary.
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 9b2b93252..bbd81c7b2 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -7630,7 +7630,7 @@ _cairo_pdf_surface_analyze_operation (cairo_pdf_surface_t *surface,
if (_cairo_surface_get_extents (surface_pattern->surface, &rec_extents)) {
if (_cairo_fixed_integer_ceil(box.p1.x) < rec_extents.x ||
_cairo_fixed_integer_ceil(box.p1.y) < rec_extents.y ||
- _cairo_fixed_integer_floor(box.p2.y) > rec_extents.x + rec_extents.width ||
+ _cairo_fixed_integer_floor(box.p2.x) > rec_extents.x + rec_extents.width ||
_cairo_fixed_integer_floor(box.p2.y) > rec_extents.y + rec_extents.height)
{
return CAIRO_INT_STATUS_UNSUPPORTED;
commit 063f9db67e2adc83c1be981de5fc2a6c78d101c2
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Sun Jun 19 16:48:39 2022 +0930
Fuzzer
diff --git a/meson-cc-tests/fuzzer.c b/meson-cc-tests/fuzzer.c
new file mode 100644
index 000000000..0ae4a3101
--- /dev/null
+++ b/meson-cc-tests/fuzzer.c
@@ -0,0 +1,7 @@
+#include <stddef.h>
+#include <stdint.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ return 0;
+}
diff --git a/test/svg/fuzzer/README b/test/svg/fuzzer/README
new file mode 100644
index 000000000..4bb8d9654
--- /dev/null
+++ b/test/svg/fuzzer/README
@@ -0,0 +1,19 @@
+libFuzzer based fuzzing for cairo-svg-glyph-render.c
+====================================================
+
+Build
+-----
+CC=clang CFLAGS="-DDEBUG_SVG_RENDER -g -fsanitize=fuzzer-no-link,address" meson -Db_lundef=false bld-fuzzer
+ninja -C bld-fuzzer
+
+
+Test
+----
+ ./bld-fuzzer/test/svg/fuzzer/svg-render-fuzzer <CORPUS DIR>
+
+where <CORPUS DIR> is a directory containing SVG files.
+
+If the fuzzer crashes, a crash-* file will be written. Run the
+fuzzer with the crash file to reproduce the crash.
+
+ ./bld-fuzzer/test/svg/fuzzer/svg-render-fuzzer <crash-file>
diff --git a/test/svg/fuzzer/meson.build b/test/svg/fuzzer/meson.build
new file mode 100644
index 000000000..37e23a474
--- /dev/null
+++ b/test/svg/fuzzer/meson.build
@@ -0,0 +1,14 @@
+fuzz_targets = [
+ 'svg-render-fuzzer'
+]
+
+fuzz_args = ['-fsanitize=fuzzer,address']
+
+foreach target_name : fuzz_targets
+ exe = executable(target_name, [target_name + '.c'],
+ include_directories: [incbase, incsrc],
+ c_args: [fuzz_args, '-DHAVE_CONFIG_H'],
+ link_with: [libcairo],
+ link_args: [fuzz_args, extra_link_args],
+ dependencies: [deps, test_deps])
+endforeach
diff --git a/test/svg/fuzzer/svg-render-fuzzer.c b/test/svg/fuzzer/svg-render-fuzzer.c
new file mode 100644
index 000000000..08eb79dd8
--- /dev/null
+++ b/test/svg/fuzzer/svg-render-fuzzer.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2022 Uli Schlachter
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Uli Schlachter <psychon at znc.in>
+ */
+
+#include <cairo.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+cairo_bool_t
+_cairo_debug_svg_render (cairo_t *cr,
+ const char *svg_document,
+ const char *element,
+ double units_per_em,
+ int debug_level);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ cairo_surface_t *s = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+ cairo_t *cr = cairo_create(s);
+
+ /* Get us a zero terminated string */
+ const char *svg_document = strndup ((const char *) data, size);
+
+ _cairo_debug_svg_render (cr,
+ svg_document,
+ NULL,
+ 1000,
+ 0);
+ free (svg_document);
+ cairo_destroy (cr);
+ cairo_surface_destroy (s);
+ return 0;
+}
diff --git a/test/svg/meson.build b/test/svg/meson.build
index b2b017d79..858e9d9cc 100644
--- a/test/svg/meson.build
+++ b/test/svg/meson.build
@@ -3,3 +3,7 @@ if librsvg_dep.found()
'svg-render.c',
dependencies: [libcairo_dep, librsvg_dep])
endif
+
+if cc.links(files(meson.project_source_root() / 'meson-cc-tests/fuzzer.c'), args: '-fsanitize=fuzzer,address')
+ subdir('fuzzer')
+endif
commit 8233c6362bbe2ca2ac4d3ef3cfc79f15ee79bc40
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Fri Jun 24 06:44:30 2022 +0930
ft-svg-render test
diff --git a/test/cairo-svg-test-doc.ttx b/test/cairo-svg-test-doc.ttx
new file mode 100644
index 000000000..c2cdb2817
--- /dev/null
+++ b/test/cairo-svg-test-doc.ttx
@@ -0,0 +1,689 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ <GlyphID id="8" name="seven"/>
+ <GlyphID id="9" name="eight"/>
+ <GlyphID id="10" name="nine"/>
+ <GlyphID id="11" name="A"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x6d925ed5"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:40 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="12"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="A" width="1100" lsb="0"/>
+ <mtx name="eight" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="nine" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="seven" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ <map code="0x39" name="nine"/><!-- DIGIT NINE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="A" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="eight" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="nine" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="seven" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Doc
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Doc Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="-90" width="80" height="80"
+ fill="none" stroke="black" stroke-width="10"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg viewBox="-5 -5 10 10" xmlns="http://www.w3.org/2000/svg">
+ <rect x="-4" y="-14" width="8" height="8"
+ fill="none" stroke="black" stroke-width="1"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <image x="0" y="-1000" width="1000" height="1000"
+ xlink:href="data:image/png;base64,
+iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9
+kT1Iw0AYht+m1opUHOwgopChOlkQFXXUKhShQqkVWnUwufQPmjQkKS6OgmvBwZ/FqoOLs64OroIg
++APi6OSk6CIlfpcUWsR4x3EP733vy913gFAvM9XsGANUzTJS8ZiYya6KwVd0YggBmtMSM/W5ZDIB
+z/F1Dx/f76I8y7vuz9Gj5EwG+ETiWaYbFvEG8dSmpXPeJw6zoqQQnxOPGnRB4keuyy6/cS44LPDM
+sJFOzROHicVCG8ttzIqGSjxJHFFUjfKFjMsK5y3OarnKmvfkLwzltJVlrtMaRByLWEISImRUUUIZ
+FqK0a6SYSNF5zMM/4PiT5JLJVQIjxwIqUCE5fvA/+N1bMz8x7iaFYkDgxbY/hoHgLtCo2fb3sW03
+TgD/M3CltfyVOjDzSXqtpUWOgN5t4OK6pcl7wOUO0P+kS4bkSH5aQj4PvJ/RN2WBvluge83tW/Mc
+pw9AmnqVuAEODoGRAmWve7y7q71v/9Y0+/cDaP1yo4gSHTAAAAAGYktHRAD/AP8A/6C9p5MAAAAJ
+cEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfmBh0FBzE5rnzLAAAAGXRFWHRDb21tZW50AENyZWF0
+ZWQgd2l0aCBHSU1QV4EOFwAAF45JREFUaN5NmnmPZOd13n/vdrdau6p6X2cjw5FIarMjAYmDwP7H
+Buz4K/ib8IMFQaAgtiVLoRRSociZIWc4Pb1Xda237vIu+eNWD91AoYHuu9R7luc85zlHfPyzvwxC
+CJRSzUdKpJAIKVBSIZVESoGUGqkkQggIASkE2miMiTGRQWmNkAqlNVobBMDmPgApwVmHrWu894QQ
+CCHgvcMHh6stdVVh6xpra5xzeO/xLry/NtBcj5AIIRFSEbzFe49+OIQQAgDnPSiBbL4KUki0Nmhj
+EFIhmz8jCGhtMFHzP6kNSjbPkVJuDNC88OGLSCnRujlYAELwBB/w3uOURWlFXSlUrTYHCVhrsdbi
+nYMQmpcTCMGDbyxklELnqxVZlhEnMe00RmtNXlT4zT1CiM2XUkitUUo1X5RAYwSNVM0hpJSNR1Tj
+GQDvHMH7H7wYxSitQIBznrquCD6g/A/etNbi6sYrVVUhpaSuKpxrjOGdJwgAj6Ixnp7f3xOcY7uX
+koiSSHq2tmIqJ5nmDgSEjSWEEO89h5CNbRrT/uAJuTm0kCBEY3sB3nmEkGjTeLDxkCd4R+0tbO5H
+GYRonqOsbV4QAhiNEDThJj3Ob8LNe4IQ6LquSSJBMb+DJEbLDqv5HEFgf2vAfSFwm9AQgBQCAc0D
+mtMhnGvCSza5JVXjPSEEMki8dwQNUor3BxZC4JxDSYXXAS003nlq6sZwm+u0VoSgYRMB3nu89yjv
+sbbJI2stuixLYi2JtCB4j/OerNXG2pp8MeNkZ4/7UlA4scmaTXw/eEgqpNKbsFIo80P4KaXe3+G9
+33jQN/GNQAiIohgjIkIIOGubCPAe4SRBBqTSGEBKiXNNoldVSV1VgMOHJo908BatBFpKUAofwPqA
+1IY4Sbi6vODw6JhZrcitINBY6n1ia4UypvGA1Jtc2RxISgQNmDjnmuOH5lCB0ACB0htLO5QySKUJ
+/gHRNogZNO1EESlQQuCQfPXqbYN4zuKcQ7MJF6M1QRkCAakU7U6XVtYijlMuLy94/PgJN+tAXoEy
+5j3SSalQ2qB0hNIKIR5gXDWhhAAhiIxBCEHw7j38eu+xwRMCTV7JxstJ6pBCYG1NL1KIakm5XiDi
+mKJyWA9pnJDna4Jv7tEASkmM1mBiRJSCVAQfEErT7Q+RAi4vLzk9OeWm1NRe8IDDSjfJ+YDryugm
+R4RAbpI2iAYctNIELwnBbzzUWDiEJokfwjGKElItSOqcYrHAC0Wr08V7qOoCrSDa1C4fAjiHbGqC
+IIkNWRqhdESSddAmoi4rAoJWp4fSEefn33O8FZHGGi3VxitgjEZrjRQCfIMkG5gjCFBKNcWMQBAC
+ROO5wMYAQqC1xhiDkpJRS7DFgipfESUt+oMRUZxS1RZb1yhtqF1A6wit9cPHgJBEJiJNIlZWEJuI
+yGjanS7tdpe6LljM5tzf3fP2u1f86NOf8tXFErEpeloJpGxCFATOe0QIiOAJtoFv5wLONcgFzWGt
+d3jvfvCcr9lSBevxFfm6Imv1iJIMtKGoAx5JCJbaB1br4n2RDSGgIxNTWY8yMVJKtjoJ66pExm28
+k2iTEJAcnD4ijiPOv39D+vVXPPvwYy6XnkBjTUkDpw+1QkqBtxYTxzjvCIALHufY5IoHIQk4rHfs
+dWKicsLlu7fErT7HHzwj7XSxtSXPS2qviPMlaE0R9HvkZINoWinF/XxJGhuEaSGATqqxviROeoQQ
+iGNDvfaYTTH7/uKSk0dPuL+eouOMdqeLtTVCgKstaZq8x3wdpyRJRgAm9xOyrIVUEhU30FyVOU92
+2qwuX3F3fU2vNyLt9DFCUeUlJmkTqIi1opUYpG8xWVmUVCA9nuY5WghBWVm8MqRJipMGIQVtbTDU
+xKJiPZ8xn9xxP5kwnU4pqpKXL1+xv3vI9WxJvbQkaUZQEYaAK0ustSzXOXHWwtY1rU6HNElZLleb
+QgrClfzouM/tyy8Zj8cMBiN2Dk+R0lB7CEGwXMyJsERRwBkJnR7VbIzRDe+rbQM6OooilFJcXFxx
+cBhhIkeBwtUOQk2IPMFbnHWUZUlVV3gfuLy+JW31SJUgVoJgK7TSIAVKR9wsl0Qm4ur8Lds7O8zr
+mrqucOucxbrEuprjYZtqUvLq5UtGox2ybg+TtUmzLs5DsVqQhhVSwMpKtDLkziCFRKkG/bRSeEB1
+Or3PlJJ0OxmDwQC3nFBbx83tFdI7itJSV5YgoaprDo+PMUazWq+pqpp22iJKE9I0Y11WGAkhOG5u
+b2lnGb1uC7xltcxZr5cc7QwZdlv0WgnVcsLk8px3Fxc8OjxEKUmcthju7KKlJNaANORlwNclJoq4
+mJcUVUUIDoJAiA2r9iFQWUdZOySO8WxJ4RaUZcVUgI5bmCxj2O3SHu7g6wrrHM55ptN7autpLZc4
+F9je2WXQHTCezEhjTb5eEBvJ6ekJ08USgsOohnvNbs4RtmBrsMUTLSmqnJ7uIao1dr0k6/YpdMTa
+KtK0xlaeiY02sKtwLsKHGuEE4NFFsabVarNcLvAIFkVBXlr29w4Y7WwTxTHWVSgpGN/cYG2FkIok
+a1FaT1Hm6EJTVo502aKqSlpJhNYSay11XXBzfc79dMn29pDx7B5XFWhfo2NDnKT84oMPmU/GCJ2Q
+z2csxzcgFT4I2tqx9BXnF+dcsEUAlIkxG1JceY+UBm1tTVmWFDZjPJujTIJdTFgs7jk6O6XdH7Cc
+jnn93bfkec5ytaSqa7r9PtoYBr0+Uilmy5wojhhu9XF1TUdq4iRBiYaEvru44uZyTRJFrFZLcCW9
+TpvgPOvlmuNnz6nLgmK+YD65Z7UsSA3IakUlYF44fGbQRqG02iBohSkjnKtRaZp99sBiy9rSymKq
+suTkyTP6gyHju1tmiyXOeW5ubwhAK2sgd3s0xDpHu92h1e5SVTVb3TbgWeRrILAzGpDnBWkSs9Vt
+s7e7w93dLVtbA2bTOVoKiqpECkEaa/qDIc7VrGdTyvUauxxj6zU+2yIn2XSfDTnVWiOlIASPare7
+nyVZhlaGTq+D95bKOgajHebzBXd3dzhXM53OqF2g3+txdnKM957T00ebqhozGu3gPThviYxmvV6j
+pWBvMCBqtYmiiEgrdGTYHY3w3lFXFVVVNdcK6G0N6Q62aPeGvLt4y+W7c/Ki4ODpc+5KSWFBbIio
+EHLDrmn6JJMkRElKlCbsbG/T7XTZPzxECFiuc4IUSKVBabZ3dzg+OsQGSZS2yIuSbn9IXVmGwx5P
+Hh1T1Y40S7m5uWY2ueObV9/y5ZdfIkTARDE+NN5vt9t89NGHZO02zjUE9Ysvv+Ty4gohAov5HCEV
+O8enTArPJK9/0BWspSwLrK0b5m4MstPpkmUtojgmzVJMknF0ckbtAsZE5HlBkIr9/X263S5eaNZF
+xf7+EU+fPuN+MmU8mbAzHNLLYqy1dNKMrW6XTq9HnMScHu3x6GiP5WKOdZ4oikizDneTGds7O7R7
+PfJ1gVaal6++ZTqbk3b7YAwrJ3l5s8CHhik4a7GuJnjXkM2HRi6K40ZiAawXDHf2sNbhbM1qtaLT
+6zEajUizlE6nizYR1lqKokAISVXkeG/xdUmxXnJ7e0O+XJKvC+I44c33b3BViatL5vMZL168Ynt3
+j9HuPt2tEVmnx8nJKTpJGe3to6RmOp1ikgQrJPOiJjIxQj60uQ42bLmRsDTKGLSta6q6ot3ukLZb
+dHpdrs/PcR463S7tTgcQZGnGzu4OSimODvaZzqbYOqfMFzw63CXSithEHIz6VFXByeE+OjKkUUwW
+KSIJ88kdKsroDwacX92ymM0xCsqypNVqcXV923AoJNu7e0ynC7q9PlcFSCdxoekilWp0MqObFpsK
+tHOOJIpx1pLECYvplFanRxtPVVbsbO8RfM1oNOLo6AgtYTmd0sliBr0OnVZCGmuKfM79dMwnHz1D
+6ojz80t6/S6nhzsIW6OVRMnAwc4A7xzfvXjBsN+j085wzrNeF3RpxIbZdMZZf8Dh8SnLdUmgBOFR
+SiLUxhNCoFQjFnqt0UI0SBBFjToopcboCGMUjx7v0e20ubg4Zzq+RVRrXr/6LWmy4os/X/Hhk0/4
++PlHDIcj7u6nvP7+nJ9+8hPSNKXX67EzHDRs4eaK1XJBEhsePz4CV/Hk9IDeYEQSRdxcXbHK16Ak
+dW0J3jOe3NHK2lzOVljnUdIgRGi4nFJoJRAhNAVSKdT27t5nAogjw2g0QiIpigJrLVGSkOc5q+k9
+j/e3+OJ3v+Vv/lPCzWSKefZjnjx9gp19QXvwjJNHH/DyxUuGo20CgeFwQDttMZ1O6PW7fPrpx+wP
+O9ze3fL5//0NxfI1l2+vyVpdnPfMZ3MGo22q2tJptdC60bEupjlq0wlKKfEERAhoJTfXNH2QfIAv
+V1u8tUgpKMuCyeSOf/vNv/DqxZ/5+U8/4Wd/8R/Z2TmmOxiSxA3XYfUVx/spn//bb1kUFqTi8uoa
+KTXdThcdR8RxzLOPfkTtIK+/ZpG9pvXjH/PX/+UJP3oy5nf/+hu0EkRJgrUehcRvZM77+YLKBYRq
+FEylFMIHQnCb6GlUmqZTFBJvLSF4rq6vWa6WpGnC3e01MlhOjo/wCFaV5We//Av+9E3Ojz95znH9
+Lfu7I5ZrwcvzObUXdPtbxGlCUa6Z3t/j6oqqsgx3D7k8/4aTk4R8PMZe/G9qVqTdbc4eP+H08WOU
+gHfnbxnfXHH+/Rtm0ynzvMR5j3WNfPSgkTVCh9wIhs3fVK/X/0wpiRABrRSj7R0m41v6vQ5PHj9i
+sViQr5ZU64LHHzxDygFvvr+hnQlurhf8+ncVl1c3fPyTT7k4/55ev4+taiIpMFGE94Hto2P+z7/+
+gZ09wVYn4Xh/i6S1x6//+2v+89/8A8YY0sjQ63RYrXLa7RadTod5aVk7gZCNpEQIKCXRSqE3B7G2
+xvmADsFD8EihKaoSISVFkXN48IgkTWi125y/u+T65o6vX7zi5z/5hJ//4u9YlTV//uYlMvwzs/mM
+u6tLkiQjzws0gdIYTBTT6kjy2ZRvX7/mxZuc58+7BCJqb/nrf/wn4jTi3ZvXVEWJc56j0xMuzs9Z
+LJa4oBGqoSONZB1QUqKVRCIJwRGC+He61kZSCc4RxRFPHp8RxxF7e3sEPLfXl1gXcM7xzavvOH93
+yUcf/5hPfvIxq7ImGR0ymy+5v5/T73fo9PtUtmKd50ghuXz3jmxrl6dnR3z46ackWcLNxQXfvXrF
+1cUllXXsDgc8+w8fkSQR88ktN+N76mxIFBncpv8RG91ZblRO7wJBuAbVet3+Z0IEyrJECMn+4RFn
+Z8csp2PiNGV7e4dBv88f/vA51zfXGK0RSuGC4upmzHg65+76mtV8yt7+AdujEaPBFrvbA9ZFRVVW
+XF9eUaA5e3TG3s6Ir776E19/+Sdurm/QStGKIp48e8bR6Qm2rpHB4XXE3Kn3Cv+DOmm0QiuJ2IjZ
+1jnqukInaUK318X7RiVZTO9ZzLpYH4iNYb1aoaKYf/jH/8YfP/+c8XiMMobHaUpeW6qywkSaODFs
+7++TKEkaa5TUdDpdhFTc3N0xnc1YLNd8/vvPeX1+zv3tDVIKep02R4f7HB4fsVrMqYqCTneLarwm
+ULyfADTyKygpwHmss43M5DzBe1S/3/vMe0ddlmgJVVXQ7/e5H485f/ManOPq6pLF7J7BcMTN7d2G
+ERumk3t6W0Nur67I87zRiyNDN0tot7usiop35++4ur7Z9Cwd2qkh2BKjFc+envH4yRO2Bn20aurG
+anbP/XTBi8tGO5Abyi5EIDbNEOlBuHbObaZeHrV/ePhZbCK0bhqUOIp+GKAEy2q15OzxI7Z3hjx9
++hQhIF/l1NY2Ek8cc3p2wu3NNUJp+r0erazFfD7j5nbMYj7F1o7jsxOkaITtar1CS8FgOKCVRiym
+93zxxy+Yjm9YLuZc3M6ZrJvZoPUO5y1aSiJj0FJhbd2M47x/fyAdRQajDc5BZAxGKxaLBUfHh1y+
+fc3Hz59zdnZGq52hpGRvf588X5NXnjgyPHp8hneOJ49OubtfcPntKxa3V9TWQoBur8tf/vJXKK1Z
+zCYs7u85ODig3+tw+uQxs8kYt7VFVRaslguKouY2t7iHISgerRWYhkI9iN1CiM1wtMkVaXREp9sh
+jhPanTZJHINzWOs4ODjg6xcv+d3vf0++LqitByHobg1ZFyWrPOf25oadnW3OTo+5f/eKePySX23V
+/LJToe6+w5YF7W6HLEtIohjrPL1ej0cffIiR4n3NODl7RJpmZN0t8spuBPJmSqaEBNcMiKT8gcI3
+478GftXJ6elnSknW+Yq6rOj1+qzzFdZ7nK3ZHvRYTO+pqpI46+AC9HoDDvZ2kcEzm004PNhn5/CY
+k8dP+dPXrxFVSStOWe8+47/+3d/T63W5fvuWm5s78vUaISVZq0UUZ6yLNbfv3vLm9UuMifnucsLa
+gVSNOK2UQmvZKIuyGaIGAt55bO3eq5bq8eMnn9m62kyTPHe3N0RRBELSbreROuLs7BGd7oDVaoXz
+8MEHTynWCyKt2R5uoZUhTlNaWYuqrPgfv/4XcpHwq7/9ew729xChJk2a4dDh/jZbo23m8zlVsWI+
+uSafT7BVxar03C1LShcQUv4wVRYCrQ1s2G7wnrq2m6ruEYAOeKRUuFDhnKPVbuGcY2d/n939fYSz
+TOcL3nz7DfvHZ5w83uZufM9gMMTVNdY6TJKwWMwp8jX9wRbBRIyefYi3FdZVYD1vX7/BRDFJp0Or
+04Zqyer+gmpd4p2nN9xneTenqCZIodHGUJZlM3uUBu9BCI9sxi5oJfFaI72nrmt0CAJJM7fwQJRm
+nD56RL5cYmtLGhmyWNLfek6VL1iMr5lNbhlujzBa0e1uka/XBO+ZTWfc3tyg45i6toxvr8niBjK7
+w22wBUorFtNbynyBy+e0sgyRDKhEyvLdmEgrPArBhpV7h4p0M9YDlHgYk0ucD+ACwjnUzu7OZ943
+o6v21hbBB6hrBtu7fP2nP5K1OxweHWNEYDgaUeVLWt0es8mEqizJ12u8h/HknqqquHz7lt3hgKIq
+sSE0CocQ4GuSLGG9mGPLHOMK2qMDkt4eXqcsCsd3r99Q1jUog6dpgR+2JYw2TY5s9i6stbjNFNpa
+i272TmC0vcvt1SXaaOh2eP3i//Hs+SeMhkMWyzXbwz62XLF3csZqsaDXbqG0Il8tGK8WoGOG2/v8
+6q/+CrynnWUgAnVdcXf9PVmkWY0d2/tHFIslRC2K2pFkEeuipKwdelO1MXKjmDSVW0uJ180YPNCI
+ELXzVPUPhVEmSUwcxVycv6VYr6mrmqoseP7JT7Flwbu355R1zbffvUHomLIsieMILQWtWHOwM+J4
+u88gctjVlPl8jtQKExsIjvViiqtKymJN1t3CASbbwqkEGyR5UZF1+iAVi/EYpTVJ2oJN57dpmpol
+hc0GxkPteJjdCwK6KBqpppVlrJYrnK3ZPzri/O05CDg6Pubi7ffs7x9QW0d+e0cnizGy2R+x64Jg
+SzpZRtbrsrawXkwpVjNsXSG8RzbbL1TFGiEVrd4QW1ekWZegYmrrWS3mrNc5cdqC4DcTY0WSpiht
+QP6wnPPweb+AAOj5bEaaxqxWK+LY0N3q8+LrF0Ra8+jJI6aTCe1WxjpfkqZHpFpT5Qv6wz5VndNO
+U0wWEyUt4labWDQvtc7jfcDXFVKAwqG0bnRbExGlbWSUEIKgrNbcXl7Q6XbJPWBrImMgTZteXan3
+7e/Dz4PqGAL4EJp+xDmPNhqtIxbTBd5Zkm6LgODq4pzR7gGtVsY//6//yc9+8ilGSWrv6PSGSKVI
+kpg0zVBJRqpjXAhUdY0gEKxGbQQ1rTVJ1kLHMVIn1M7hPdzfj6mLHCEF1joiqXDOvl9KeL/+sYFe
+ABFoFnLqBr61c5bgJQhDZWvy5ZI0idneO+D1qxfsHx5z/uY7fvqLX9BODJfv3rG3PYTQwkQpcWQQ
+WmPiDGUSlNZY7xuq4z1qM3qWWiHY/JYaKSTW1qzzkuVsSl0WdLp98Ip8k8RShPdKifeBsBEa2Czd
+BNcsrHnv+f9Qc51Rfhz5VAAAAABJRU5ErkJggg=="/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <image x="0" y="-1000" width="1000" height="1000"
+ transform="scale(0.5) rotate(-45),translate(800,500)"
+ xlink:href="data:image/png;base64,
+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
+BNcsrHnv+f9Qc51Rfhz5VAAAAABJRU5ErkJggg=="/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ <rect x="100" y="-900" width="800" height="400"/>
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="8" startGlyphID="8">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ <rect x="0" y="0" width="1" height="0.5"/>
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="9" startGlyphID="9">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ </clipPath>
+
+ <clipPath id="clip2" clipPathUnits="userSpaceOnUse" >
+ <rect x="100" y="-900" width="800" height="400"/>
+ </clipPath>
+
+ <g clip-path="url(#clip)">
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip2)" />
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="10" startGlyphID="10">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ </clipPath>
+
+ <clipPath id="clip2" clipPathUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="0.5"/>
+ </clipPath>
+
+ <g clip-path="url(#clip)">
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip2)" />
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="11" startGlyphID="11">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" >
+ <defs>
+ <circle id="circle"
+ cx="0" cy="0" r="250" />
+ </defs>
+
+ <g opacity="0.5">
+ <use xlink:href="#circle" x="330" y="-500"
+ fill="red" />
+ <use xlink:href="#circle" x="670" y="-500"
+ fill="green" />
+ </g>
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-fill.ttx b/test/cairo-svg-test-fill.ttx
new file mode 100644
index 000000000..b6867ec8f
--- /dev/null
+++ b/test/cairo-svg-test-fill.ttx
@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ <GlyphID id="8" name="seven"/>
+ <GlyphID id="9" name="eight"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x71e2812"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:40 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="10"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="56"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="eight" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="seven" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="eight" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="seven" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Fill
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Fill Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="indigo" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="#AA55AA" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="#A5A" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="rgb (75, 0 , 130)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="currentColor" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="var(--color1, red)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="magenta" fill-opacity="0.5" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="8" startGlyphID="8">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ color="green" >
+ <rect x="100" y="-750" width="800" height="500"
+ fill="currentColor" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="9" startGlyphID="9">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <g transform="scale(5,5) translate(0,-150)">
+ <polygon fill-rule="nonzero"
+ points="50,0 21,90 98,35 2,35 79,90"/>
+ <polygon fill-rule="evenodd"
+ points="150,0 121,90 198,35 102,35 179,90"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-gradient.ttx b/test/cairo-svg-test-gradient.ttx
new file mode 100644
index 000000000..4a7a22175
--- /dev/null
+++ b/test/cairo-svg-test-gradient.ttx
@@ -0,0 +1,441 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ <GlyphID id="8" name="seven"/>
+ <GlyphID id="9" name="eight"/>
+ <GlyphID id="10" name="nine"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x4322c7de"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:39 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="11"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="57"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="eight" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="nine" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="seven" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ <map code="0x39" name="nine"/><!-- DIGIT NINE -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="eight" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="nine" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="seven" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Gradient
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Gradient Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x21="66%">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="0.5" />
+ </linearGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x2="66%"
+ spreadMethod="reflect">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x2="66%"
+ spreadMethod="repeat">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="300" y1="-500" x2="600" y2="-500"
+ gradientUnits="userSpaceOnUse">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" gradientTransform="rotate(45)">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="50%" cy="50%"
+ fx="0.75" fy="0.35" r="0.5">
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="75%" cy="25%" r="33%"
+ fx="0.64" fy="0.18" fr="0.17"
+ spreadMethod="reflect" >
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="8" startGlyphID="8">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="75%" cy="25%" r="33%"
+ fx="0.64" fy="0.18" fr="0.17"
+ spreadMethod="repeat" >
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="9" startGlyphID="9">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="450" cy="-550" r="400"
+ fx="600" fy="-400" fr="10"
+ gradientUnits="userSpaceOnUse" >
+ <stop offset="0%" stop-color="red" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="0.5" />
+ </radialGradient>
+ </defs>
+ <rect x="0" y="-1000" width="1000" height="1000" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="10" startGlyphID="10">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="50%" cy="25%"
+ fx="0.75" fy="0.35" r="0.5"
+ gradientTransform="scale(1, 2)">
+ <stop offset="0%" stop-color="red" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-path.ttx b/test/cairo-svg-test-path.ttx
new file mode 100644
index 000000000..9173a537d
--- /dev/null
+++ b/test/cairo-svg-test-path.ttx
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x20ff0b91"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:40 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="51"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Path
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Path Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M 200 -200
+ L 500 -500
+ H 800
+ V -200
+ " />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M200,-400
+ C200,-200
+ 500,-200
+ 500,-400
+ S800,-600
+ 800,-400" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M200,-300
+ Q400,-50
+ 600,-300
+ T1000,-300"
+ />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M500,-500
+ L 712 -712
+ A300,300 0 1,0 712, -288 z"
+ />
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-shapes.ttx b/test/cairo-svg-test-shapes.ttx
new file mode 100644
index 000000000..15a573a64
--- /dev/null
+++ b/test/cairo-svg-test-shapes.ttx
@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0xa8919a30"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:40 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="8"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="54"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Shapes
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Shapes Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500" rx="100" ry="100"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="500" cy="-500" r="400"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <ellipse cx="500" cy="-500" rx="400" ry="200"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="200" y1="-200" x2="800" y2="-800" stroke="black" stroke-width="20"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline fill="none" stroke="black" stroke-width="20"
+ points="100, -100,
+ 300, -100,
+ 300, -300,
+ 500, -300,
+ 500, -500,
+ 700, -500
+ 700, -700
+ 900, -700
+ 900, -900" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<polygon points="350, -75
+ 379, -161
+ 469, -161
+ 397, -215
+ 423, -301
+ 350, -250
+ 277, -301
+ 303, -215
+ 231, -161
+ 321, -161" />
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-stroke.ttx b/test/cairo-svg-test-stroke.ttx
new file mode 100644
index 000000000..a403da928
--- /dev/null
+++ b/test/cairo-svg-test-stroke.ttx
@@ -0,0 +1,608 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ <GlyphID id="8" name="seven"/>
+ <GlyphID id="9" name="eight"/>
+ <GlyphID id="10" name="nine"/>
+ <GlyphID id="11" name="A"/>
+ <GlyphID id="12" name="B"/>
+ <GlyphID id="13" name="C"/>
+ <GlyphID id="14" name="D"/>
+ <GlyphID id="15" name="E"/>
+ <GlyphID id="16" name="F"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x2d1b9ede"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:39 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="17"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="70"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="A" width="1100" lsb="0"/>
+ <mtx name="B" width="1100" lsb="0"/>
+ <mtx name="C" width="1100" lsb="0"/>
+ <mtx name="D" width="1100" lsb="0"/>
+ <mtx name="E" width="1100" lsb="0"/>
+ <mtx name="F" width="1100" lsb="0"/>
+ <mtx name="eight" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="nine" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="seven" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ <map code="0x39" name="nine"/><!-- DIGIT NINE -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->
+ <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="A" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="C" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="D" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="E" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="F" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="eight" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="nine" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="seven" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Stroke
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Stroke Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="indigo" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="#AA55AA" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="#A5A" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="rgb(75,0,130)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="currentColor" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="var(--color1, red)" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="magenta" stroke-opacity="0.5" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="8" startGlyphID="8">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ color="green" >
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="currentColor" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="9" startGlyphID="9">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-900" x2="900" y2="-900"
+ fill="none"
+ stroke-width="100"
+ stroke="black" />
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="200"
+ stroke="black" />
+ <line x1="100" y1="-100" x2="900" y2="-100"
+ fill="none"
+ stroke-width="300"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="10" startGlyphID="10">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-900" x2="900" y2="-900"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="butt"
+ stroke="black" />
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="round"
+ stroke="black" />
+ <line x1="100" y1="-100" x2="900" y2="-100"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="square"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="11" startGlyphID="11">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-950" x2="900" y2="-950"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="none"
+ stroke="black" />
+ <line x1="100" y1="-750" x2="900" y2="-750"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200"
+ stroke="black" />
+ <line x1="100" y1="-550" x2="900" y2="-550"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50"
+ stroke="black" />
+ <line x1="100" y1="-350" x2="900" y2="-350"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100"
+ stroke="black" />
+ <line x1="100" y1="-150" x2="900" y2="-150"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100 150"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="12" startGlyphID="12">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-950" x2="900" y2="-950"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="none"
+ stroke="black" />
+ <line x1="100" y1="-750" x2="900" y2="-750"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-550" x2="900" y2="-550"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-350" x2="900" y2="-350"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-150" x2="900" y2="-150"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100 150"
+ stroke-dashoffset="100"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="13" startGlyphID="13">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="miter"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="14" startGlyphID="14">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="round"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="15" startGlyphID="15">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="bevel"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="16" startGlyphID="16">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="miter"
+ stroke-miterlimit="2"
+ stroke="black" />
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-svg-test-transform.ttx b/test/cairo-svg-test-transform.ttx
new file mode 100644
index 000000000..1f07c05ad
--- /dev/null
+++ b/test/cairo-svg-test-transform.ttx
@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="zero"/>
+ <GlyphID id="2" name="one"/>
+ <GlyphID id="3" name="two"/>
+ <GlyphID id="4" name="three"/>
+ <GlyphID id="5" name="four"/>
+ <GlyphID id="6" name="five"/>
+ <GlyphID id="7" name="six"/>
+ <GlyphID id="8" name="seven"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x970fff1"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Fri Jul 1 06:21:39 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="100"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="9"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="48"/>
+ <usLastCharIndex value="55"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ <mtx name="five" width="1100" lsb="0"/>
+ <mtx name="four" width="1100" lsb="0"/>
+ <mtx name="one" width="1100" lsb="0"/>
+ <mtx name="seven" width="1100" lsb="0"/>
+ <mtx name="six" width="1100" lsb="0"/>
+ <mtx name="three" width="1100" lsb="0"/>
+ <mtx name="two" width="1100" lsb="0"/>
+ <mtx name="zero" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
+ <map code="0x31" name="one"/><!-- DIGIT ONE -->
+ <map code="0x32" name="two"/><!-- DIGIT TWO -->
+ <map code="0x33" name="three"/><!-- DIGIT THREE -->
+ <map code="0x34" name="four"/><!-- DIGIT FOUR -->
+ <map code="0x35" name="five"/><!-- DIGIT FIVE -->
+ <map code="0x36" name="six"/><!-- DIGIT SIX -->
+ <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="five" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="four" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="seven" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="six" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="three" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="two" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Transform
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Svg Test Transform Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <use xlink:href="#square" transform="translate(600, -400)"/>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="2" startGlyphID="2">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -400)">
+ <use xlink:href="#square" transform="scale(1.5, 0.5)"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="3" startGlyphID="3">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(600, -600)">
+ <use xlink:href="#square" transform="rotate(30)"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="4" startGlyphID="4">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -400)">
+ <use xlink:href="#square" transform="skewX(30)"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="5" startGlyphID="5">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -500)">
+ <use xlink:href="#square" transform="skewY(30)"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="6" startGlyphID="6">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(600, -600)">
+ <use xlink:href="#square" transform="matrix(1, 0.4, -0.6, 1.1, 50, -70)"/>
+ </g>
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="7" startGlyphID="7">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" />
+ </defs>
+ <use xlink:href="#heart"
+ transform="translate(10, -650)
+ rotate(-10 50 100)
+ translate(-166 125.5)
+ skewX(40)
+ scale(1 0.5),scale(8, 8)"
+ fill="grey"/>
+ <use xlink:href="#heart"
+ transform="translate(300, -800),scale(6, 6)"
+ fill="none" stroke="red" />
+</svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="8" startGlyphID="8">
+ <![CDATA[<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="c1"
+ cx="100" cy="100" r="80"
+ fill="none"
+ stroke="black"
+ stroke-width="20" />
+ <circle id="c2"
+ cx="100" cy="100" r="80"
+ fill="none"
+ stroke="black"
+ stroke-width="20"
+ transform="scale(3,3)" />
+ </defs>
+
+ <use xlink:href="#c1" x="100" y="-900"/>
+ <use xlink:href="#c2" x="300" y="-700"/>
+
+</svg>]]>
+ </svgDoc>
+ </SVG>
+
+</ttFont>
diff --git a/test/ft-svg-render.c b/test/ft-svg-render.c
new file mode 100644
index 000000000..9b0351b10
--- /dev/null
+++ b/test/ft-svg-render.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2022 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairo-test.h"
+#include <cairo-ft.h>
+
+#define GLYPH_SIZE 50
+#define PAD 5
+#define WIDTH (4*(GLYPH_SIZE + PAD) + PAD)
+#define HEIGHT WIDTH
+
+//#define CLIP 1
+#define LOG_EXTENTS 1
+
+static cairo_test_status_t
+draw_font (cairo_t *cr, int width, int height, const char *font_file)
+{
+ cairo_test_status_t result;
+ char buf[10];
+ cairo_text_extents_t extents;
+ cairo_font_options_t *font_options;
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ result = cairo_test_ft_select_font_from_file (cr, font_file);
+ if (result)
+ return result;
+
+ font_options = cairo_font_options_create ();
+ cairo_font_options_set_color_mode (font_options, CAIRO_COLOR_MODE_NO_COLOR);
+// cairo_set_font_options (cr, font_options);
+ cairo_font_options_destroy (font_options);
+
+ cairo_set_font_size (cr, GLYPH_SIZE);
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ int x = j * (GLYPH_SIZE + PAD) + PAD;
+ int y = i * (GLYPH_SIZE + PAD) + PAD;
+ int glyph_number = 4*i + j;
+ buf[0] = glyph_number < 10 ? '0' + glyph_number : 'A' + glyph_number - 10;
+ buf[1] = 0;
+ cairo_save (cr);
+ cairo_text_extents (cr, buf, &extents);
+#if LOG_EXTENTS
+ cairo_test_log (cairo_test_get_context (cr),
+ "Char '%c' extents: x_bearing: %f y_bearing: %f width: %f height: %f x_advance: %f y_advance: %f\n",
+ buf[0],
+ extents.x_bearing,
+ extents.y_bearing,
+ extents.width,
+ extents.height,
+ extents.x_advance,
+ extents.y_advance);
+#endif
+#if CLIP
+ cairo_rectangle (cr, x, y, GLYPH_SIZE, GLYPH_SIZE);
+ cairo_clip (cr);
+#endif
+ cairo_move_to (cr, x, y + GLYPH_SIZE);
+ cairo_show_text (cr, buf);
+ cairo_restore (cr);
+ if (cairo_status (cr)) {
+ cairo_test_log (cairo_test_get_context (cr),
+ "cairo_show_text() failed with \"%s\"\n",
+ buf);
+ return CAIRO_TEST_FAILURE;
+ }
+ }
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+#define DRAW_FUNC(name) \
+static cairo_test_status_t \
+draw_##name (cairo_t *cr, int width, int height) { \
+ return draw_font (cr, width, height, "cairo-svg-test-" #name ".ttf"); \
+}
+
+DRAW_FUNC(doc)
+CAIRO_TEST (ft_svg_render_doc,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_doc)
+
+DRAW_FUNC(fill)
+CAIRO_TEST (ft_svg_render_fill,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_fill)
+
+DRAW_FUNC(gradient)
+CAIRO_TEST (ft_svg_render_gradient,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_gradient)
+
+DRAW_FUNC(path)
+CAIRO_TEST (ft_svg_render_path,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_path)
+
+DRAW_FUNC(shapes)
+CAIRO_TEST (ft_svg_render_shapes,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_shapes)
+
+DRAW_FUNC(stroke)
+CAIRO_TEST (ft_svg_render_stroke,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_stroke)
+
+DRAW_FUNC(transform)
+CAIRO_TEST (ft_svg_render_transform,
+ "Test SVG glyph render",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw_transform)
diff --git a/test/meson.build b/test/meson.build
index 3071ac6ca..3606bfc27 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -431,6 +431,7 @@ test_ft_svg_font_sources = [
test_ft_svg_ttx_font_sources = [
'ft-svg-cairo-logo.c',
+ 'ft-svg-render.c'
]
test_gl_sources = [
@@ -525,6 +526,13 @@ ps2png_sources = [
test_ttx_fonts = [
'cairo-logo-font.ttx',
+ 'cairo-svg-test-doc.ttx',
+ 'cairo-svg-test-fill.ttx',
+ 'cairo-svg-test-gradient.ttx',
+ 'cairo-svg-test-path.ttx',
+ 'cairo-svg-test-shapes.ttx',
+ 'cairo-svg-test-stroke.ttx',
+ 'cairo-svg-test-transform.ttx',
]
build_any2ppm = false
@@ -681,3 +689,9 @@ test('cairo', exe,
suite: ['cairo-test-suite', 'slow'],
workdir: meson.current_build_dir(),
depends: test_depends)
+
+# The SVG renderer debug tools can only be built if the _cairo_debug_svg_render()
+# function has been exposed by defining DEBUG_SVG_RENDER
+if conf.get('HAVE_FT_SVG_DOCUMENT', 0) == 1 and cc.get_define('DEBUG_SVG_RENDER') != ''
+ subdir('svg')
+endif
diff --git a/test/reference/ft-svg-render-doc.ref.png b/test/reference/ft-svg-render-doc.ref.png
new file mode 100644
index 000000000..e7ecf7f1a
Binary files /dev/null and b/test/reference/ft-svg-render-doc.ref.png differ
diff --git a/test/reference/ft-svg-render-fill.ref.png b/test/reference/ft-svg-render-fill.ref.png
new file mode 100644
index 000000000..a72cdd1f2
Binary files /dev/null and b/test/reference/ft-svg-render-fill.ref.png differ
diff --git a/test/reference/ft-svg-render-gradient.ref.png b/test/reference/ft-svg-render-gradient.ref.png
new file mode 100644
index 000000000..d647dc06a
Binary files /dev/null and b/test/reference/ft-svg-render-gradient.ref.png differ
diff --git a/test/reference/ft-svg-render-path.ref.png b/test/reference/ft-svg-render-path.ref.png
new file mode 100644
index 000000000..2667059cc
Binary files /dev/null and b/test/reference/ft-svg-render-path.ref.png differ
diff --git a/test/reference/ft-svg-render-shapes.ref.png b/test/reference/ft-svg-render-shapes.ref.png
new file mode 100644
index 000000000..209e0ddb3
Binary files /dev/null and b/test/reference/ft-svg-render-shapes.ref.png differ
diff --git a/test/reference/ft-svg-render-stroke.ref.png b/test/reference/ft-svg-render-stroke.ref.png
new file mode 100644
index 000000000..86f1ab9b6
Binary files /dev/null and b/test/reference/ft-svg-render-stroke.ref.png differ
diff --git a/test/reference/ft-svg-render-transform.ref.png b/test/reference/ft-svg-render-transform.ref.png
new file mode 100644
index 000000000..dcc372aac
Binary files /dev/null and b/test/reference/ft-svg-render-transform.ref.png differ
diff --git a/test/svg/README b/test/svg/README
new file mode 100644
index 000000000..c4504dc9b
--- /dev/null
+++ b/test/svg/README
@@ -0,0 +1,22 @@
+build_ttx_fonts.py
+==================
+Is used to create the test/*.ttx files used for testing the SVG glyph renderer.
+
+build_ttx_fonts.py will look for files of the form
+
+<font-name>.<char>.<test-name>.svg
+
+in the input directory, and using svg-font-template.ttx, create <font-name>.ttx
+files that contain one glyph for each svg file. Each <font-name>.ttx will contain
+a glyph for each svg with the matching <font-name> prefix. Each glyphs will be mapped
+to the <char> i nthe svg filename. The <char> must be one of 0-9,A-F.
+
+svg-render.c
+============
+svg-render renders SVG files using both librsvg and cairo-svg-glyph-render.c.
+It is used for testing cairo-svg-glyph-render.c during development.
+
+To use svg-render, cairo must be built with CFLAGS="-DDEBUG_SVG_RENDER" to enable the
+_cairo_debug_svg_render() function.
+
+
diff --git a/test/svg/build_ttx_fonts.py b/test/svg/build_ttx_fonts.py
new file mode 100755
index 000000000..d7e7f5b8f
--- /dev/null
+++ b/test/svg/build_ttx_fonts.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+
+# Build the ttx cairo svg test fonts from svg files
+# The svg files use the naming convention
+# <font-name>.<char>.<test-name>.svg
+# eg "circle.A.cx_cy_r.svg"
+#
+# <font-name> is use to create the name of the ttx font.
+# <char> is a ascii hex character (uppercase) that the font fill map to the SVG description.
+# <test-name> is a descriptive name of the SVG file and is not used to build the font…
+#
+# This script looks for all files matching the above pattern and
+# creates one ttx font for each unique <font-name>. Each font will
+# contain up to 16 characters. The SVG description of each character
+# and the character that maps to the SVG description is obtained from
+# the SVG file beginning with <font-name>.<char>.
+
+import argparse
+import os
+import re
+import sys
+import xml.dom.minidom
+
+TEMPLATE_FILE="svg-font-template.ttx"
+
+glyph_names = {
+ '0': 'zero',
+ '1': 'one',
+ '2': 'two',
+ '3': 'three',
+ '4': 'four',
+ '5': 'five',
+ '6': 'six',
+ '7': 'seven',
+ '8': 'eight',
+ '9': 'nine',
+ 'A': 'A',
+ 'B': 'B',
+ 'C': 'C',
+ 'D': 'D',
+ 'E': 'E',
+ 'F': 'F'
+}
+
+# files is list of (char, filename)
+def build_font(font_name, files, in_dir, out_dir, no_reformat):
+ name = "cairo-svg-test-" + font_name
+ doc = xml.dom.minidom.parse(os.path.join(in_dir, TEMPLATE_FILE))
+ glyph_id = 1
+ text_nl = doc.createTextNode('\n\n')
+ for f in sorted(files):
+ glyph_name = glyph_names[f[0]]
+
+ glyph_order = doc.getElementsByTagName('GlyphOrder')[0]
+ glyph_id_elem = doc.createElement('GlyphID')
+ glyph_id_elem.setAttribute('id', str(glyph_id))
+ glyph_id_elem.setAttribute('name', glyph_name)
+ glyph_order.appendChild(glyph_id_elem)
+ glyph_order.appendChild(text_nl)
+
+ hmtx = doc.getElementsByTagName('hmtx')[0]
+ mtx = doc.createElement('mtx')
+ mtx.setAttribute('name', glyph_name)
+ mtx.setAttribute('width', '1100')
+ mtx.setAttribute('lsb', '0')
+ hmtx.appendChild(mtx)
+
+ cmap_format = doc.getElementsByTagName('cmap_format_4')[0]
+ map = doc.createElement('map')
+ map.setAttribute('code', hex(ord(f[0])))
+ map.setAttribute('name', glyph_name)
+ cmap_format.appendChild(map)
+
+ glyf = doc.getElementsByTagName('glyf')[0]
+ tt_glyph = doc.createElement('TTGlyph')
+ tt_glyph.setAttribute('name', glyph_name)
+ glyf.appendChild(tt_glyph)
+ contour = doc.createElement('contour')
+ tt_glyph.appendChild(contour)
+ pt = doc.createElement('pt')
+ pt.setAttribute('x', "0")
+ pt.setAttribute('y', "0")
+ pt.setAttribute('on', "1")
+ contour.appendChild(pt)
+ pt = doc.createElement('pt')
+ pt.setAttribute('x', "0")
+ pt.setAttribute('y', "1000")
+ pt.setAttribute('on', "1")
+ contour.appendChild(pt)
+ pt = doc.createElement('pt')
+ pt.setAttribute('x', "1000")
+ pt.setAttribute('y', "1000")
+ pt.setAttribute('on', "1")
+ contour.appendChild(pt)
+ pt = doc.createElement('pt')
+ pt.setAttribute('x', "1000")
+ pt.setAttribute('y', "0")
+ pt.setAttribute('on', "1")
+ contour.appendChild(pt)
+ instructions = doc.createElement('instructions')
+ tt_glyph.appendChild(instructions)
+
+ svg = doc.getElementsByTagName('SVG')[0]
+ svgdoc = doc.createElement('svgDoc')
+ svgdoc.setAttribute('startGlyphID', str(glyph_id))
+ svgdoc.setAttribute('endGlyphID', str(glyph_id))
+ with open(os.path.join(in_dir, f[1]), 'r') as svg_file:
+ svg_data = svg_file.read()
+ cdata = doc.createCDATASection(svg_data)
+ svgdoc.appendChild(cdata)
+ svg.appendChild(svgdoc)
+ glyph_id += 1
+
+ name_record = doc.getElementsByTagName('namerecord')[0]
+ name_record.firstChild.replaceWholeText(name.replace("-", " ").title())
+ name_record = doc.getElementsByTagName('namerecord')[2]
+ name_record.firstChild.replaceWholeText(name.replace("-", " ").title() + " Regular")
+
+ ttx_filename = os.path.join(out_dir, name + '.ttx')
+ ttf_filename = os.path.join(out_dir, name + '.ttf')
+ with open(ttx_filename, 'w') as ttx_file:
+ doc.writexml(ttx_file)
+
+ if not no_reformat:
+ # Convert to ttf and back to ttx. This reformats the ttx file
+ # which allows better quality diffs.
+ if os.path.exists(ttf_filename):
+ os.remove(ttf_filename)
+ os.system("ttx " + ttx_filename)
+ os.remove(ttx_filename)
+ os.system("ttx " + ttf_filename)
+ os.remove(ttf_filename)
+
+def build_file_list(in_dir):
+ dict = {}
+ regex_prog = re.compile(r"([^\.]+)\.(.)\.[^\.]+\.svg", re.ASCII)
+ files = os.listdir(in_dir)
+ for f in files:
+ match = regex_prog.fullmatch(f)
+ if match:
+ fontname = match.group(1)
+ character = match.group(2)
+ if (fontname not in dict):
+ dict[fontname] = [(character, f)];
+ else:
+ dict[fontname].append((character, f))
+ return dict
+
+if __name__=='__main__':
+ parser = argparse.ArgumentParser(description='Build ttx fonts.')
+ parser.add_argument("-i", nargs=1, metavar="indir", default=["."], help="Input directory")
+ parser.add_argument("-o", nargs=1, metavar="outdir", default=["."], help="Output directory")
+ parser.add_argument("-n", action='store_true', help="Don't reformat the output.")
+ args = parser.parse_args()
+ in_dir = args.i[0]
+ out_dir = args.o[0]
+ no_reformat = args.n
+ file_list = build_file_list(in_dir)
+ font_name = None
+ for key, value in file_list.items():
+ build_font(key, value, in_dir, out_dir, no_reformat)
diff --git a/test/svg/doc.0.viewBox1.svg b/test/svg/doc.0.viewBox1.svg
new file mode 100644
index 000000000..c1f55a4eb
--- /dev/null
+++ b/test/svg/doc.0.viewBox1.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="-90" width="80" height="80"
+ fill="none" stroke="black" stroke-width="10"/>
+</svg>
diff --git a/test/svg/doc.1.viewBox2.svg b/test/svg/doc.1.viewBox2.svg
new file mode 100644
index 000000000..b8f976ba9
--- /dev/null
+++ b/test/svg/doc.1.viewBox2.svg
@@ -0,0 +1,4 @@
+<svg viewBox="-5 -5 10 10" xmlns="http://www.w3.org/2000/svg">
+ <rect x="-4" y="-14" width="8" height="8"
+ fill="none" stroke="black" stroke-width="1"/>
+</svg>
diff --git a/test/svg/doc.2.image.svg b/test/svg/doc.2.image.svg
new file mode 100644
index 000000000..1d03842a9
--- /dev/null
+++ b/test/svg/doc.2.image.svg
@@ -0,0 +1,121 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <image x="0" y="-1000" width="1000" height="1000"
+ xlink:href="data:image/png;base64,
+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
+BNcsrHnv+f9Qc51Rfhz5VAAAAABJRU5ErkJggg=="/>
+</svg>
diff --git a/test/svg/doc.3.image-transform.svg b/test/svg/doc.3.image-transform.svg
new file mode 100644
index 000000000..90e5ecb1a
--- /dev/null
+++ b/test/svg/doc.3.image-transform.svg
@@ -0,0 +1,122 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <image x="0" y="-1000" width="1000" height="1000"
+ transform="scale(0.5) rotate(-45),translate(800,500)"
+ xlink:href="data:image/png;base64,
+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
+BNcsrHnv+f9Qc51Rfhz5VAAAAABJRU5ErkJggg=="/>
+</svg>
diff --git a/test/svg/doc.4.clip-user.svg b/test/svg/doc.4.clip-user.svg
new file mode 100644
index 000000000..26cdd818a
--- /dev/null
+++ b/test/svg/doc.4.clip-user.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+</svg>
diff --git a/test/svg/doc.5.clip-object.svg b/test/svg/doc.5.clip-object.svg
new file mode 100644
index 000000000..732911590
--- /dev/null
+++ b/test/svg/doc.5.clip-object.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+
+</svg>
diff --git a/test/svg/doc.6.clip-user2.svg b/test/svg/doc.6.clip-user2.svg
new file mode 100644
index 000000000..a68c1c42f
--- /dev/null
+++ b/test/svg/doc.6.clip-user2.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ <rect x="100" y="-900" width="800" height="400"/>
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+</svg>
diff --git a/test/svg/doc.7.clip-object2.svg b/test/svg/doc.7.clip-object2.svg
new file mode 100644
index 000000000..f5c153dd1
--- /dev/null
+++ b/test/svg/doc.7.clip-object2.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ <rect x="0" y="0" width="1" height="0.5"/>
+ </clipPath>
+
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip)" />
+
+</svg>
diff --git a/test/svg/doc.8.clip-user3.svg b/test/svg/doc.8.clip-user3.svg
new file mode 100644
index 000000000..8ff61d593
--- /dev/null
+++ b/test/svg/doc.8.clip-user3.svg
@@ -0,0 +1,15 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse" >
+ <circle cx="500" cy="-500" r="400" />
+ </clipPath>
+
+ <clipPath id="clip2" clipPathUnits="userSpaceOnUse" >
+ <rect x="100" y="-900" width="800" height="400"/>
+ </clipPath>
+
+ <g clip-path="url(#clip)">
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip2)" />
+ </g>
+</svg>
diff --git a/test/svg/doc.9.clip-object3.svg b/test/svg/doc.9.clip-object3.svg
new file mode 100644
index 000000000..b291df79f
--- /dev/null
+++ b/test/svg/doc.9.clip-object3.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
+ <clipPath id="clip" clipPathUnits="objectBoundingBox">
+ <circle cx=".5" cy=".5" r=".5" />
+ </clipPath>
+
+ <clipPath id="clip2" clipPathUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="0.5"/>
+ </clipPath>
+
+ <g clip-path="url(#clip)">
+ <rect x="100" y="-900" width="800" height="800"
+ stroke="green" stroke-width="100"
+ clip-path="url(#clip2)" />
+ </g>
+</svg>
diff --git a/test/svg/doc.A.g.svg b/test/svg/doc.A.g.svg
new file mode 100644
index 000000000..bdaade725
--- /dev/null
+++ b/test/svg/doc.A.g.svg
@@ -0,0 +1,14 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" >
+ <defs>
+ <circle id="circle"
+ cx="0" cy="0" r="250" />
+ </defs>
+
+ <g opacity="0.5">
+ <use xlink:href="#circle" x="330" y="-500"
+ fill="red" />
+ <use xlink:href="#circle" x="670" y="-500"
+ fill="green" />
+ </g>
+</svg>
diff --git a/test/svg/fill.0.name.svg b/test/svg/fill.0.name.svg
new file mode 100644
index 000000000..e377b8827
--- /dev/null
+++ b/test/svg/fill.0.name.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="indigo" />
+</svg>
diff --git a/test/svg/fill.1.hex6.svg b/test/svg/fill.1.hex6.svg
new file mode 100644
index 000000000..04759d78a
--- /dev/null
+++ b/test/svg/fill.1.hex6.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="#AA55AA" />
+</svg>
diff --git a/test/svg/fill.2.hex3.svg b/test/svg/fill.2.hex3.svg
new file mode 100644
index 000000000..9f9bce580
--- /dev/null
+++ b/test/svg/fill.2.hex3.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="#A5A" />
+</svg>
diff --git a/test/svg/fill.3.rgb.svg b/test/svg/fill.3.rgb.svg
new file mode 100644
index 000000000..c18b03bb5
--- /dev/null
+++ b/test/svg/fill.3.rgb.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="rgb (75, 0 , 130)" />
+</svg>
diff --git a/test/svg/fill.4.current-color.svg b/test/svg/fill.4.current-color.svg
new file mode 100644
index 000000000..cf77d65a6
--- /dev/null
+++ b/test/svg/fill.4.current-color.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="currentColor" />
+</svg>
diff --git a/test/svg/fill.5.palette.svg b/test/svg/fill.5.palette.svg
new file mode 100644
index 000000000..53ea97512
--- /dev/null
+++ b/test/svg/fill.5.palette.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="var(--color1, red)" />
+</svg>
diff --git a/test/svg/fill.6.opacity.svg b/test/svg/fill.6.opacity.svg
new file mode 100644
index 000000000..59b4eb41f
--- /dev/null
+++ b/test/svg/fill.6.opacity.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"
+ fill="magenta" fill-opacity="0.5" />
+</svg>
diff --git a/test/svg/fill.7.color.svg b/test/svg/fill.7.color.svg
new file mode 100644
index 000000000..745b2e881
--- /dev/null
+++ b/test/svg/fill.7.color.svg
@@ -0,0 +1,5 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ color="green" >
+ <rect x="100" y="-750" width="800" height="500"
+ fill="currentColor" />
+</svg>
diff --git a/test/svg/fill.8.rule.svg b/test/svg/fill.8.rule.svg
new file mode 100644
index 000000000..366ff6760
--- /dev/null
+++ b/test/svg/fill.8.rule.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <g transform="scale(5,5) translate(0,-150)">
+ <polygon fill-rule="nonzero"
+ points="50,0 21,90 98,35 2,35 79,90"/>
+ <polygon fill-rule="evenodd"
+ points="150,0 121,90 198,35 102,35 179,90"/>
+ </g>
+</svg>
diff --git a/test/svg/gradient.0.lin-pad.svg b/test/svg/gradient.0.lin-pad.svg
new file mode 100644
index 000000000..0ec54092f
--- /dev/null
+++ b/test/svg/gradient.0.lin-pad.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x21="66%">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="0.5" />
+ </linearGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
+
diff --git a/test/svg/gradient.1.lin-reflect.svg b/test/svg/gradient.1.lin-reflect.svg
new file mode 100644
index 000000000..9e3e50cb9
--- /dev/null
+++ b/test/svg/gradient.1.lin-reflect.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x2="66%"
+ spreadMethod="reflect">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>
+
diff --git a/test/svg/gradient.2.lin-repeat.svg b/test/svg/gradient.2.lin-repeat.svg
new file mode 100644
index 000000000..1f023f5e1
--- /dev/null
+++ b/test/svg/gradient.2.lin-repeat.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="33%" x2="66%"
+ spreadMethod="repeat">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>
+
diff --git a/test/svg/gradient.3.lin-user.svg b/test/svg/gradient.3.lin-user.svg
new file mode 100644
index 000000000..fc90328f0
--- /dev/null
+++ b/test/svg/gradient.3.lin-user.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" x1="300" y1="-500" x2="600" y2="-500"
+ gradientUnits="userSpaceOnUse">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="0" y="-900" width="1000" height="800" fill="url(#grad)" />
+</svg>
+
diff --git a/test/svg/gradient.4.lin-transform.svg b/test/svg/gradient.4.lin-transform.svg
new file mode 100644
index 000000000..bc8f5b622
--- /dev/null
+++ b/test/svg/gradient.4.lin-transform.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad" gradientTransform="rotate(45)">
+ <stop offset="0%" stop-color="blue" stop-opacity="1" />
+ <stop offset="100%" stop-color="red" stop-opacity="1" />
+ </linearGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
+
diff --git a/test/svg/gradient.5.rad-pad.svg b/test/svg/gradient.5.rad-pad.svg
new file mode 100644
index 000000000..b631ddc73
--- /dev/null
+++ b/test/svg/gradient.5.rad-pad.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="50%" cy="50%"
+ fx="0.75" fy="0.35" r="0.5">
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
diff --git a/test/svg/gradient.6.rad-reflect.svg b/test/svg/gradient.6.rad-reflect.svg
new file mode 100644
index 000000000..b6fea76be
--- /dev/null
+++ b/test/svg/gradient.6.rad-reflect.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="75%" cy="25%" r="33%"
+ fx="0.64" fy="0.18" fr="0.17"
+ spreadMethod="reflect" >
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
diff --git a/test/svg/gradient.7.rad-repeat.svg b/test/svg/gradient.7.rad-repeat.svg
new file mode 100644
index 000000000..63d138d2a
--- /dev/null
+++ b/test/svg/gradient.7.rad-repeat.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="75%" cy="25%" r="33%"
+ fx="0.64" fy="0.18" fr="0.17"
+ spreadMethod="repeat" >
+ <stop offset="0%" stop-color="white" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
diff --git a/test/svg/gradient.8.rad-user.svg b/test/svg/gradient.8.rad-user.svg
new file mode 100644
index 000000000..e1d9c61f0
--- /dev/null
+++ b/test/svg/gradient.8.rad-user.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="450" cy="-550" r="400"
+ fx="600" fy="-400" fr="10"
+ gradientUnits="userSpaceOnUse" >
+ <stop offset="0%" stop-color="red" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="0.5" />
+ </radialGradient>
+ </defs>
+ <rect x="0" y="-1000" width="1000" height="1000" fill="url(#grad)" />
+</svg>
diff --git a/test/svg/gradient.9.rad-transform.svg b/test/svg/gradient.9.rad-transform.svg
new file mode 100644
index 000000000..4b90aaaf6
--- /dev/null
+++ b/test/svg/gradient.9.rad-transform.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <radialGradient id="grad" cx="50%" cy="25%"
+ fx="0.75" fy="0.35" r="0.5"
+ gradientTransform="scale(1, 2)">
+ <stop offset="0%" stop-color="red" stop-opacity="1" />
+ <stop offset="100%" stop-color="green" stop-opacity="1" />
+ </radialGradient>
+ </defs>
+ <rect x="100" y="-900" width="800" height="800" fill="url(#grad)" />
+</svg>
diff --git a/test/svg/meson.build b/test/svg/meson.build
new file mode 100644
index 000000000..b2b017d79
--- /dev/null
+++ b/test/svg/meson.build
@@ -0,0 +1,5 @@
+if librsvg_dep.found()
+ executable('svg-render',
+ 'svg-render.c',
+ dependencies: [libcairo_dep, librsvg_dep])
+endif
diff --git a/test/svg/path.0.line.svg b/test/svg/path.0.line.svg
new file mode 100644
index 000000000..19d50914a
--- /dev/null
+++ b/test/svg/path.0.line.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M 200 -200
+ L 500 -500
+ H 800
+ V -200
+ " />
+</svg>
diff --git a/test/svg/path.1.curve.svg b/test/svg/path.1.curve.svg
new file mode 100644
index 000000000..49c252ac8
--- /dev/null
+++ b/test/svg/path.1.curve.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M200,-400
+ C200,-200
+ 500,-200
+ 500,-400
+ S800,-600
+ 800,-400" />
+</svg>
diff --git a/test/svg/path.2.quad.svg b/test/svg/path.2.quad.svg
new file mode 100644
index 000000000..00a0dde57
--- /dev/null
+++ b/test/svg/path.2.quad.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M200,-300
+ Q400,-50
+ 600,-300
+ T1000,-300"
+ />
+</svg>
diff --git a/test/svg/path.3.arc.svg b/test/svg/path.3.arc.svg
new file mode 100644
index 000000000..f177a72af
--- /dev/null
+++ b/test/svg/path.3.arc.svg
@@ -0,0 +1,7 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="black" stroke-width="20"
+ d="M500,-500
+ L 712 -712
+ A300,300 0 1,0 712, -288 z"
+ />
+</svg>
diff --git a/test/svg/shapes.0.rect.svg b/test/svg/shapes.0.rect.svg
new file mode 100644
index 000000000..17db34649
--- /dev/null
+++ b/test/svg/shapes.0.rect.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500"/>
+</svg>
diff --git a/test/svg/shapes.1.rounded-rect.svg b/test/svg/shapes.1.rounded-rect.svg
new file mode 100644
index 000000000..717470bee
--- /dev/null
+++ b/test/svg/shapes.1.rounded-rect.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <rect x="100" y="-750" width="800" height="500" rx="100" ry="100"/>
+</svg>
diff --git a/test/svg/shapes.2.circle.svg b/test/svg/shapes.2.circle.svg
new file mode 100644
index 000000000..3c07bad83
--- /dev/null
+++ b/test/svg/shapes.2.circle.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="500" cy="-500" r="400"/>
+</svg>
diff --git a/test/svg/shapes.3.ellipse.svg b/test/svg/shapes.3.ellipse.svg
new file mode 100644
index 000000000..67260a404
--- /dev/null
+++ b/test/svg/shapes.3.ellipse.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <ellipse cx="500" cy="-500" rx="400" ry="200"/>
+</svg>
diff --git a/test/svg/shapes.4.line.svg b/test/svg/shapes.4.line.svg
new file mode 100644
index 000000000..58de42947
--- /dev/null
+++ b/test/svg/shapes.4.line.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="200" y1="-200" x2="800" y2="-800" stroke="black" stroke-width="20"/>
+</svg>
diff --git a/test/svg/shapes.5.polyline.svg b/test/svg/shapes.5.polyline.svg
new file mode 100644
index 000000000..0cc5c383c
--- /dev/null
+++ b/test/svg/shapes.5.polyline.svg
@@ -0,0 +1,12 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline fill="none" stroke="black" stroke-width="20"
+ points="100, -100,
+ 300, -100,
+ 300, -300,
+ 500, -300,
+ 500, -500,
+ 700, -500
+ 700, -700
+ 900, -700
+ 900, -900" />
+</svg>
diff --git a/test/svg/shapes.6.polygon.svg b/test/svg/shapes.6.polygon.svg
new file mode 100644
index 000000000..56bda0705
--- /dev/null
+++ b/test/svg/shapes.6.polygon.svg
@@ -0,0 +1,12 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<polygon points="350, -75
+ 379, -161
+ 469, -161
+ 397, -215
+ 423, -301
+ 350, -250
+ 277, -301
+ 303, -215
+ 231, -161
+ 321, -161" />
+</svg>
diff --git a/test/svg/stroke.0.name.svg b/test/svg/stroke.0.name.svg
new file mode 100644
index 000000000..f2a926475
--- /dev/null
+++ b/test/svg/stroke.0.name.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="indigo" />
+</svg>
diff --git a/test/svg/stroke.1.hex6.svg b/test/svg/stroke.1.hex6.svg
new file mode 100644
index 000000000..89f2b1b38
--- /dev/null
+++ b/test/svg/stroke.1.hex6.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="#AA55AA" />
+</svg>
diff --git a/test/svg/stroke.2.hex3.svg b/test/svg/stroke.2.hex3.svg
new file mode 100644
index 000000000..a8565a7b6
--- /dev/null
+++ b/test/svg/stroke.2.hex3.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="#A5A" />
+</svg>
diff --git a/test/svg/stroke.3.rgb.svg b/test/svg/stroke.3.rgb.svg
new file mode 100644
index 000000000..42e7514e4
--- /dev/null
+++ b/test/svg/stroke.3.rgb.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="rgb(75,0,130)" />
+</svg>
diff --git a/test/svg/stroke.4.current-color.svg b/test/svg/stroke.4.current-color.svg
new file mode 100644
index 000000000..9d645b881
--- /dev/null
+++ b/test/svg/stroke.4.current-color.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="currentColor" />
+</svg>
diff --git a/test/svg/stroke.5.palette.svg b/test/svg/stroke.5.palette.svg
new file mode 100644
index 000000000..1262a7ba0
--- /dev/null
+++ b/test/svg/stroke.5.palette.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="var(--color1, red)" />
+</svg>
diff --git a/test/svg/stroke.6.opacity.svg b/test/svg/stroke.6.opacity.svg
new file mode 100644
index 000000000..b84d1bdc9
--- /dev/null
+++ b/test/svg/stroke.6.opacity.svg
@@ -0,0 +1,6 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="magenta" stroke-opacity="0.5" />
+</svg>
diff --git a/test/svg/stroke.7.color.svg b/test/svg/stroke.7.color.svg
new file mode 100644
index 000000000..952f48261
--- /dev/null
+++ b/test/svg/stroke.7.color.svg
@@ -0,0 +1,7 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ color="green" >
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="100"
+ stroke="currentColor" />
+</svg>
diff --git a/test/svg/stroke.8.width.svg b/test/svg/stroke.8.width.svg
new file mode 100644
index 000000000..bb9edea90
--- /dev/null
+++ b/test/svg/stroke.8.width.svg
@@ -0,0 +1,14 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-900" x2="900" y2="-900"
+ fill="none"
+ stroke-width="100"
+ stroke="black" />
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="200"
+ stroke="black" />
+ <line x1="100" y1="-100" x2="900" y2="-100"
+ fill="none"
+ stroke-width="300"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.9.cap.svg b/test/svg/stroke.9.cap.svg
new file mode 100644
index 000000000..35046446c
--- /dev/null
+++ b/test/svg/stroke.9.cap.svg
@@ -0,0 +1,17 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-900" x2="900" y2="-900"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="butt"
+ stroke="black" />
+ <line x1="100" y1="-500" x2="900" y2="-500"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="round"
+ stroke="black" />
+ <line x1="100" y1="-100" x2="900" y2="-100"
+ fill="none"
+ stroke-width="200"
+ stroke-linecap="square"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.A.dash.svg b/test/svg/stroke.A.dash.svg
new file mode 100644
index 000000000..2be5a1d90
--- /dev/null
+++ b/test/svg/stroke.A.dash.svg
@@ -0,0 +1,27 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-950" x2="900" y2="-950"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="none"
+ stroke="black" />
+ <line x1="100" y1="-750" x2="900" y2="-750"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200"
+ stroke="black" />
+ <line x1="100" y1="-550" x2="900" y2="-550"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50"
+ stroke="black" />
+ <line x1="100" y1="-350" x2="900" y2="-350"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100"
+ stroke="black" />
+ <line x1="100" y1="-150" x2="900" y2="-150"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100 150"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.B.dash-offset.svg b/test/svg/stroke.B.dash-offset.svg
new file mode 100644
index 000000000..f7623ea84
--- /dev/null
+++ b/test/svg/stroke.B.dash-offset.svg
@@ -0,0 +1,31 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <line x1="100" y1="-950" x2="900" y2="-950"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="none"
+ stroke="black" />
+ <line x1="100" y1="-750" x2="900" y2="-750"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-550" x2="900" y2="-550"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-350" x2="900" y2="-350"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100"
+ stroke-dashoffset="100"
+ stroke="black" />
+ <line x1="100" y1="-150" x2="900" y2="-150"
+ fill="none"
+ stroke-width="100"
+ stroke-dasharray="200 50 100 150"
+ stroke-dashoffset="100"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.C.miter.svg b/test/svg/stroke.C.miter.svg
new file mode 100644
index 000000000..1dc4ee6a7
--- /dev/null
+++ b/test/svg/stroke.C.miter.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="miter"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.D.round.svg b/test/svg/stroke.D.round.svg
new file mode 100644
index 000000000..3e4c6236f
--- /dev/null
+++ b/test/svg/stroke.D.round.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="round"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.E.bevel.svg b/test/svg/stroke.E.bevel.svg
new file mode 100644
index 000000000..25d9e3803
--- /dev/null
+++ b/test/svg/stroke.E.bevel.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="bevel"
+ stroke="black" />
+</svg>
diff --git a/test/svg/stroke.F.miter-limit.svg b/test/svg/stroke.F.miter-limit.svg
new file mode 100644
index 000000000..14357f0b4
--- /dev/null
+++ b/test/svg/stroke.F.miter-limit.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <polyline
+ points="100, -200
+ 900, -400
+ 100, -700"
+ fill="none"
+ stroke-width="200"
+ stroke-linejoin="miter"
+ stroke-miterlimit="2"
+ stroke="black" />
+</svg>
diff --git a/test/svg/svg-font-template.ttx b/test/svg/svg-font-template.ttx
new file mode 100644
index 000000000..715074dda
--- /dev/null
+++ b/test/svg/svg-font-template.ttx
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3e7355ef"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000001"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Wed Jun 15 00:00:00 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1100"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="2"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="2"/>
+ <maxPoints value="1"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="1"/>
+ <maxCompositeContours value="1"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="1000"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Font Template
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Font Template Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CPAL>
+ <version value="0"/>
+ <numPaletteEntries value="2"/>
+ <palette index="0">
+ <color index="0" value="#C5A1D7FF"/>
+ <color index="1" value="#80DFC8FF"/>
+ </palette>
+ <palette index="1">
+ <color index="0" value="#6392A9FF"/>
+ <color index="1" value="#7896B3FF"/>
+ </palette>
+ </CPAL>
+
+ <SVG>
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <colorPalettes>
+ </colorPalettes>
+ </SVG>
+
+</ttFont>
diff --git a/test/svg/svg-render.c b/test/svg/svg-render.c
new file mode 100644
index 000000000..8f67ebdc2
--- /dev/null
+++ b/test/svg/svg-render.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright © 2016 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson at redneon.com>
+ */
+
+/* Compilation:
+ * Build cairo with -DDEBUG_SVG_RENDER
+ * gcc -o svg-render svg-render.c `pkg-config --cflags --libs cairo librsvg`
+ */
+
+/* svg-render - render SVG files using both the cairo glyph renderer and librsvg.
+ *
+ * This allows testing the cairo SVG test cases before assembling them into an SVG font.
+ * Usage:
+ * svg-render [-b] [-s] [-g <id>] [-e <em_size> ] input.svg
+ *
+ * Output is written to input.cairo.png and input.rsvg.png.
+ *
+ * -b print bounding box.
+ * -s Use standard SVG viewport. See below.
+ * -g render glyph with id <id>
+ * -e set the EM size. The default is 1000.
+ *
+ * SVG Glyphs are assumed to be wholely within the view port.
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <cairo.h>
+#include <librsvg/rsvg.h>
+
+typedef enum { CAIRO_SVG, LIBRSVG } svg_renderer_t;
+
+/* output image size */
+#define WIDTH 1000
+#define HEIGHT 1000
+
+static cairo_bool_t bbox = FALSE;
+static cairo_bool_t standard_svg = FALSE;
+static const char *glyph_id = NULL;
+static int em_size = 1000;
+
+cairo_bool_t
+_cairo_debug_svg_render (cairo_t *cr,
+ const char *svg_document,
+ const char *element,
+ double units_per_em,
+ int debug_level);
+
+static void
+cairo_render (const char *svg_document, cairo_t *cr)
+{
+ if (!_cairo_debug_svg_render (cr, svg_document, glyph_id, em_size, 2))
+ printf("_cairo_debug_svg_render() failed\n");
+}
+
+static void
+librsvg_render (const char *svg_document, cairo_t *cr)
+{
+ gboolean has_width;
+ gboolean has_height;
+ gboolean has_viewbox;
+ RsvgLength svg_width;
+ RsvgLength svg_height;
+ RsvgRectangle svg_viewbox;
+ RsvgRectangle viewport;
+ double width, height;
+ GError *error = NULL;
+
+ RsvgHandle *handle = rsvg_handle_new_from_data ((guint8 *)svg_document,
+ strlen(svg_document),
+ &error);
+ if (!handle) {
+ printf ("Could not load: %s", error->message);
+ return;
+ }
+
+ /* Default width if not specified is EM Square */
+ width = em_size;
+ height = em_size;
+
+ /* Get width/height if specified. */
+ rsvg_handle_get_intrinsic_dimensions(handle,
+ &has_width,
+ &svg_width,
+ &has_height,
+ &svg_height,
+ &has_viewbox,
+ &svg_viewbox);
+ if (has_width)
+ width = svg_width.length;
+
+ if (has_height)
+ height = svg_height.length;
+
+ /* We set the viewport for the call rsvg_handle_render_layer() to
+ * width/height. That way if either dimension is not specified in
+ * the SVG it will be inherited from the viewport we provide.
+ *
+ * As this scales up the rendered dimensions by width/height we
+ * need to undo this scaling to get a unit square scale that
+ * matches the cairo SVG renderer scale. The OpenType SVG spec
+ * does not say what to do if width != height. In this case we
+ * will just use a uniform scale that ensures the viewport fits in
+ * the unit square and also center it.
+ */
+
+ if (width > height) {
+ cairo_scale (cr, 1.0/width, 1.0/width);
+ cairo_translate (cr, 0, (width - height)/2.0);
+ } else {
+ cairo_scale (cr, 1.0/height, 1.0/height);
+ cairo_translate (cr, (height - width)/2.0, 0);
+ }
+
+ viewport.x = 0;
+ viewport.y = 0;
+ viewport.width = width;
+ viewport.height = height;
+ if (!rsvg_handle_render_layer (handle, cr, glyph_id, &viewport, &error)) {
+ printf ("librsvg render failed: %s", error->message);
+ return;
+ }
+}
+
+static void
+render_svg (const char *svg_document, svg_renderer_t renderer, const char* out_file)
+{
+ double x, y, w, h;
+
+ cairo_surface_t *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+ cairo_t *cr = cairo_create (recording);
+
+ /* Scale up to image size when recording to reduce rounding errors
+ * in cairo_recording_surface_ink_extents()
+ */
+ cairo_scale (cr, WIDTH, HEIGHT);
+
+ if (renderer == CAIRO_SVG) {
+ cairo_render (svg_document, cr);
+ } else {
+ librsvg_render (svg_document, cr);
+ }
+ cairo_destroy (cr);
+
+ if (bbox) {
+ cairo_recording_surface_ink_extents (recording, &x, &y, &w, &h);
+ if (renderer == CAIRO_SVG)
+ printf("cairo ");
+ else
+ printf("librsvg");
+
+ printf(" bbox left: %d right: %d top: %d bottom: %d\n",
+ (int)floor(x),
+ (int)ceil(x + w),
+ (int)floor(y),
+ (int)ceil(y + h));
+ }
+
+ cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT);
+ cr = cairo_create (surface);
+
+ /* If rendering a glyph need to translate base line to bottom of image */
+ if (standard_svg)
+ cairo_set_source_surface (cr, recording, 0, 0);
+ else
+ cairo_set_source_surface (cr, recording, 0, HEIGHT);
+
+ cairo_paint (cr);
+
+ cairo_surface_write_to_png (surface, out_file);
+ cairo_surface_destroy (surface);
+}
+
+static char *
+create_output_name (const char *svg_file, svg_renderer_t renderer)
+{
+ char buf[1000];
+ int len;
+
+ strcpy (buf, svg_file);
+ len = strlen (buf);
+
+ if (strlen (buf) > 5 && strcmp (buf + len - 4, ".svg") == 0)
+ buf[len - 4] = 0;
+
+ if (renderer == CAIRO_SVG)
+ strcat (buf, ".cairo.png");
+ else
+ strcat (buf, ".rsvg.png");
+
+ return strdup (buf);
+}
+
+static char *
+read_file(const char *filename)
+{
+ FILE *fp;
+ int len;
+ char *data;
+
+ fp = fopen (filename, "r");
+ if (fp == NULL)
+ return NULL;
+
+ fseek (fp, 0, SEEK_END);
+ len = ftell(fp);
+ rewind (fp);
+ data = malloc (len + 1);
+ if (fread(data, len, 1, fp) != 1)
+ return NULL;
+ data[len] = 0;
+ fclose(fp);
+ return data;
+}
+
+static void
+usage_and_exit()
+{
+ printf ("svg-render [-b] [-s] [-g <id>] [-e <em_size> ] input.svg\n");
+ exit (1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *input_file = NULL;
+ char *svg_document;
+ char *output_file;
+
+ argc--;
+ argv++;
+ while (argc > 0) {
+ if (strcmp (argv[0], "-b") == 0) {
+ bbox = TRUE;
+ argc--;
+ argv++;
+ } else if (strcmp (argv[0], "-s") == 0) {
+ standard_svg = TRUE;
+ argc--;
+ argv++;
+ } else if (strcmp (argv[0], "-g") == 0) {
+ if (argc > 1) {
+ glyph_id = argv[1];
+ argc -= 2;
+ argv += 2;
+ } else {
+ usage_and_exit();
+ }
+ } else if (strcmp (argv[0], "-e") == 0) {
+ if (argc > 1) {
+ em_size = atoi (argv[1]);
+ if (em_size <= 0) {
+ usage_and_exit();
+ }
+ argc -= 2;
+ argv += 2;
+ } else {
+ usage_and_exit();
+ }
+ } else {
+ input_file = argv[0];
+ argc--;
+ argv++;
+ }
+ }
+ if (!input_file)
+ usage_and_exit();
+
+ svg_document = read_file (input_file);
+ if (!svg_document) {
+ printf("error reading file %s\n", input_file);
+ exit (1);
+ }
+
+ output_file = create_output_name (input_file, CAIRO_SVG);
+ render_svg (svg_document, CAIRO_SVG, output_file);
+ free (output_file);
+
+ output_file = create_output_name (input_file, LIBRSVG);
+ render_svg (svg_document, LIBRSVG, output_file);
+ free (output_file);
+
+ free (svg_document);
+
+ return 0;
+}
diff --git a/test/svg/transform.0.translate.svg b/test/svg/transform.0.translate.svg
new file mode 100644
index 000000000..5d9555673
--- /dev/null
+++ b/test/svg/transform.0.translate.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <use xlink:href="#square" transform="translate(600, -400)"/>
+</svg>
diff --git a/test/svg/transform.1.scale.svg b/test/svg/transform.1.scale.svg
new file mode 100644
index 000000000..b7742fc80
--- /dev/null
+++ b/test/svg/transform.1.scale.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -400)">
+ <use xlink:href="#square" transform="scale(1.5, 0.5)"/>
+ </g>
+</svg>
diff --git a/test/svg/transform.2.rotate.svg b/test/svg/transform.2.rotate.svg
new file mode 100644
index 000000000..926ae63fc
--- /dev/null
+++ b/test/svg/transform.2.rotate.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(600, -600)">
+ <use xlink:href="#square" transform="rotate(30)"/>
+ </g>
+</svg>
diff --git a/test/svg/transform.3.skewX.svg b/test/svg/transform.3.skewX.svg
new file mode 100644
index 000000000..f7555534d
--- /dev/null
+++ b/test/svg/transform.3.skewX.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -400)">
+ <use xlink:href="#square" transform="skewX(30)"/>
+ </g>
+</svg>
diff --git a/test/svg/transform.4.skewY.svg b/test/svg/transform.4.skewY.svg
new file mode 100644
index 000000000..cd62c8a8a
--- /dev/null
+++ b/test/svg/transform.4.skewY.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(400, -500)">
+ <use xlink:href="#square" transform="skewY(30)"/>
+ </g>
+</svg>
diff --git a/test/svg/transform.5.matrix.svg b/test/svg/transform.5.matrix.svg
new file mode 100644
index 000000000..643fbe36b
--- /dev/null
+++ b/test/svg/transform.5.matrix.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <rect id="square" width="300" height="300"/>
+ </defs>
+ <use xlink:href="#square" transform="translate(100, -900)"/>
+ <g transform="translate(600, -600)">
+ <use xlink:href="#square" transform="matrix(1, 0.4, -0.6, 1.1, 50, -70)"/>
+ </g>
+</svg>
diff --git a/test/svg/transform.6.multiple.svg b/test/svg/transform.6.multiple.svg
new file mode 100644
index 000000000..90c7b3054
--- /dev/null
+++ b/test/svg/transform.6.multiple.svg
@@ -0,0 +1,16 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" />
+ </defs>
+ <use xlink:href="#heart"
+ transform="translate(10, -650)
+ rotate(-10 50 100)
+ translate(-166 125.5)
+ skewX(40)
+ scale(1 0.5),scale(8, 8)"
+ fill="grey"/>
+ <use xlink:href="#heart"
+ transform="translate(300, -800),scale(6, 6)"
+ fill="none" stroke="red" />
+</svg>
diff --git a/test/svg/transform.7.stroke.svg b/test/svg/transform.7.stroke.svg
new file mode 100644
index 000000000..f56b24dfe
--- /dev/null
+++ b/test/svg/transform.7.stroke.svg
@@ -0,0 +1,20 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="c1"
+ cx="100" cy="100" r="80"
+ fill="none"
+ stroke="black"
+ stroke-width="20" />
+ <circle id="c2"
+ cx="100" cy="100" r="80"
+ fill="none"
+ stroke="black"
+ stroke-width="20"
+ transform="scale(3,3)" />
+ </defs>
+
+ <use xlink:href="#c1" x="100" y="-900"/>
+ <use xlink:href="#c2" x="300" y="-700"/>
+
+</svg>
commit 252ff60f1ee62c5b36d650b91ac2536d03656bea
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Wed Jun 15 07:05:52 2022 +0930
SVG font test using cairo logo
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5f3ff2396..fa7403b35 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -94,6 +94,7 @@ fedora image:
dejavu-sans-mono-fonts
dejavu-serif-fonts
google-noto-emoji-color-fonts
+ fonttools
fedora autotools build:
extends:
diff --git a/meson.build b/meson.build
index 831527c43..02f74856c 100644
--- a/meson.build
+++ b/meson.build
@@ -302,6 +302,8 @@ if fontconfig_dep.found()
}]
endif
+ttx = find_program('ttx', required: false)
+
freetype_dep = dependency('freetype2',
required: get_option('freetype'),
version: freetype_required_version,
@@ -339,6 +341,9 @@ if freetype_dep.found()
endif
if cc.has_type('FT_SVG_Document', dependencies: freetype_dep, prefix: '#include <freetype/otsvg.h>')
conf.set('HAVE_FT_SVG_DOCUMENT', 1)
+ if ttx.found()
+ conf.set('CAIRO_CAN_TEST_TTX_FONT', 1)
+ endif
endif
check_funcs += ft_check_funcs
deps += [freetype_dep]
diff --git a/test/cairo-logo-font.ttx b/test/cairo-logo-font.ttx
new file mode 100644
index 000000000..9e4b832c0
--- /dev/null
+++ b/test/cairo-logo-font.ttx
@@ -0,0 +1,539 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.19">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x3e7355ef"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000001"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 15 00:00:00 2022"/>
+ <modified value="Wed Jun 15 00:00:00 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="0"/>
+ <lineGap value="200"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="1000"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="2"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="2"/>
+ <maxPoints value="1"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="1"/>
+ <maxCompositeContours value="1"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="256"/>
+ <maxSizeOfInstructions value="1"/>
+ <maxComponentElements value="2"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="1000"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000001"/>
+ <ySubscriptXSize value="600"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="600"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="300"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="djr "/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-100"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="720"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="1000" lsb="0"/>
+ <mtx name="A" width="1000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+ <TTGlyph name="A">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Cairo Logo
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Cairo Logo Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+
+ <SVG>
+
+ <svgDoc endGlyphID="0" startGlyphID="0">
+ <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"></svg>]]>
+ </svgDoc>
+ <svgDoc endGlyphID="1" startGlyphID="1">
+ <![CDATA[<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="310" height="508">
+ <defs id="cairo-artwork_defs">
+ <g id="hacker_emblem">
+ <!-- Note: This is similar though not identical to Keith Packard's SVG version
+ of the hacker emblem (http://www.catb.org/hacker-emblem/glider.svg) -->
+ <g id="hacker_emblem_grid" fill="white" stroke="none">
+ <!-- Outside: Top, Right, Bottom, Left -->
+ <rect x="-2.95" y="-3.05" width="6" height="0.1" />
+ <rect x="2.95" y="-2.95" width="0.1" height="6" />
+ <rect x="-3.05" y="2.95" width="6" height="0.1" />
+ <rect x="-3.05" y="-3.05" width="0.1" height="6" />
+ <!-- Vertical: Left, Right -->
+ <rect x="-1.05" y="-2.95" width="0.1" height="5.9" />
+ <rect x="0.95" y="-2.95" width="0.1" height="5.9" />
+ <!-- Horizontal: TopLeft, TopMiddle, TopRight -->
+ <rect x="-2.95" y="-1.05" width="1.9" height="0.1" />
+ <rect x="-0.95" y="-1.05" width="1.9" height="0.1" />
+ <rect x="1.05" y="-1.05" width="1.9" height="0.1" />
+ <!-- Horizontal: BottomLeft, BottomMiddle, BottomRight -->
+ <rect x="-2.95" y="0.95" width="1.9" height="0.1" />
+ <rect x="-0.95" y="0.95" width="1.9" height="0.1" />
+ <rect x="1.05" y="0.95" width="1.9" height="0.1" />
+ </g>
+ <g id="hacker_emblem_dots" fill="white">
+ <circle cx="0" cy="-2" r="0.7" />
+ <circle cx="2" cy="0" r="0.7" />
+ <circle cx="-2" cy="2" r="0.7" />
+ <circle cx="0" cy="2" r="0.7" />
+ <circle cx="2" cy="2" r="0.7" />
+ </g>
+ </g>
+ <g id="scarab" fill="#3B80AE">
+ <g transform="translate(-150, -170)">
+ <path id="scarab_head" d="M205.599,94.567c0-11.668-24.914-21.129-55.628-21.129
+ c-30.723,0-55.624,9.46-55.624,21.129c0,10.203,24.901,7.346,55.624,7.346C180.685,101.913,205.599,104.233,205.599,94.567z"/>
+ <path id="scarab_torso" d="M136.423,161.506c0,0,12.751,12.577,13.547,13.362
+ c2.262-2.232,13.545-13.362,13.545-13.362c7.135-7.036,87.111-6.399,91.066-6.363c-0.469-6.298-1.254-12.472-2.325-18.519
+ c-15.183-19.279-42.811-32.225-74.485-32.225h-55.518c-31.745,0-59.439,13.011-74.598,32.37c-1.054,6-1.829,12.128-2.296,18.374
+ C49.321,155.106,129.288,154.47,136.423,161.506z"/>
+ <path id="scarab_spine" d="M149.97,301.187c2.005-24.729,8.386-103.483,8.405-103.721
+ c-0.09-0.219-6.478-15.578-8.405-20.214c-1.936,4.655-8.316,19.995-8.408,20.214C141.582,197.704,147.965,276.458,149.97,301.187z"/>
+ <path id="scarab_wing_left" d="M140.403,197.149l8.862-21.31l-13.686-13.499
+ c-5.65-5.573-67.074-6.235-90.259-6.019l-0.006-0.622c-0.154,2.144-0.271,4.302-0.35,6.475
+ c-0.076,2.207,10.392,4.706,10.392,6.717c0,2.319-10.457,5.084-10.359,7.631c2.993,73.349,48.53,131.631,104.372,132.048
+ l-9.02-111.29L140.403,197.149z"/>
+ <path id="scarab_wing_right" d="M244.585,168.891c0-2.011,10.467-4.506,10.391-6.715
+ c-0.079-2.174-0.195-4.332-0.351-6.479l-0.004,0.624c-23.186-0.216-84.608,0.445-90.26,6.017l-13.688,13.502l8.915,21.438
+ l-9.017,111.29c55.854-0.417,101.378-58.698,104.373-132.049C255.04,173.976,244.585,171.209,244.585,168.891z"/>
+ <path id="scarab_leg_front_left" d="M44.506,141.12c-4.135-0.856-4.895-1.54-7.935-2.92
+ c-9.59-3.364-10.376-5.481-16.08-11.86c-7.426-8.306-12.661-20.142-17.1-29.463c-3.576-7.525-3.984-16.409-2.86-24.273
+ c0.991-6.935,7.144-12.869,12.074-18.92c5.844-7.191,10.356-14.822,17.924-21.354c7.736-6.682,23.203-9.809,26.168-19.648
+ C57.86,8.819,54.334,1.766,61.482,0c-0.366,4.703,3.639,8.477,2.397,13.575c-1.129,4.627-4.368,5.811-9.611,9.099
+ c-7.564,4.746-18.366,8.779-24.748,13.965c-7.175,5.827-4.369,13.771-10.569,20.057c-2.001,2.03-7.901,4.706-9.137,6.83
+ c-1.861,3.199-0.297,9.572-0.116,13.12c0.425,8.284,5.588,14.244,9.555,22.045c4.152,8.141,6.429,15.409,13.411,22.519
+ c4.183,4.262,11.429,4.802,16.21,10.647l-3.555,4.186L44.506,141.12z"/>
+ <path id="scarab_leg_middle_left" d="M43.94,191.922l-0.809-7.346
+ c-9.506-4.579-10.339-9.772-20.738-12.466c-23.728-6.151-21.361,11.25-15.532,26.373c5.676,14.726,8.237,30.23,14.345,44.795
+ c2.805,6.688,6.919,13.213,14.298,15.127c0.372-8.435-0.917-10.651-6.113-16.919c-4.395-5.293-3.326-12.548-6.072-18.504
+ c-3.581-7.804-4.196-15.646-7.279-23.502c-1.363-3.479-8.33-13.966-6.452-17.861c3.183-6.603,9.178-0.083,12.179,2.077
+ c4.218,3.036,6.467,2.223,11.681,2.898C34.041,186.673,37.005,188.756,43.94,191.922z"/>
+ <path id="scarab_leg_back_left" d="M65.839,257.063l-2.771-4.837
+ c-6.68,8.928-6.993,16.228-10.056,23.347c-5.277,12.263-0.157,28.851,9.854,37.676c6.052,5.375,15.907,9.618,23.122,13.136
+ c10.035,4.892,20.113,11.286,31.336,13.396c2.482,0.466,8.798,1.295,6.693-3.522c-0.975-2.237-8.091-4.591-10.146-5.734
+ c-8.312-4.623-16.377-10.524-24.142-16.176c-9.498-6.862-20.843-11.186-28.311-20.684c-3.054-3.885-3.544-4.922-2.816-9.39
+ c0.693-4.263,1.344-9.174,2.241-13.439C61.855,266.029,63.274,261.378,65.839,257.063z"/>
+ <path id="scarab_leg_front_right" d="M255.487,141.12c4.134-0.856,4.896-1.54,7.936-2.92
+ c9.583-3.364,10.369-5.481,16.071-11.86c7.428-8.306,12.661-20.142,17.115-29.463c3.574-7.525,3.983-16.409,2.86-24.273
+ c-0.992-6.935-7.157-12.869-12.087-18.92c-5.843-7.191-10.356-14.822-17.919-21.354c-7.735-6.682-23.202-9.809-26.167-19.648
+ C242.135,8.819,245.66,1.766,238.511,0c0.366,4.703-3.637,8.477-2.396,13.575c1.131,4.627,4.368,5.811,9.611,9.099
+ c7.563,4.746,18.367,8.779,24.747,13.965c7.17,5.827,4.362,13.771,10.563,20.057c2.001,2.03,7.901,4.706,9.139,6.83
+ c1.859,3.199,0.295,9.572,0.113,13.12c-0.424,8.284-5.588,14.244-9.553,22.045c-4.152,8.141-6.431,15.409-13.404,22.519
+ c-4.184,4.262-11.429,4.802-16.211,10.647l3.556,4.186L255.487,141.12z"/>
+ <path id="scarab_leg_middle_right" d="M256.053,191.922l0.81-7.346
+ c9.507-4.579,10.34-9.772,20.73-12.466c23.741-6.151,21.374,11.25,15.534,26.373c-5.676,14.726-8.238,30.23-14.347,44.795
+ c-2.804,6.688-6.911,13.213-14.291,15.127c-0.371-8.435,0.918-10.651,6.113-16.919c4.39-5.293,3.319-12.548,6.066-18.504
+ c3.58-7.804,4.197-15.646,7.278-23.502c1.363-3.479,8.33-13.966,6.453-17.861c-3.184-6.603-9.179-0.083-12.181,2.077
+ c-4.217,3.036-6.458,2.223-11.672,2.898C265.951,186.673,262.986,188.756,256.053,191.922z"/>
+ <path id="scarab_leg_back_right" d="M234.155,257.063l2.771-4.837
+ c6.679,8.928,6.991,16.228,10.057,23.347c5.274,12.263,0.154,28.851-9.854,37.676c-6.055,5.375-15.903,9.618-23.117,13.136
+ c-10.034,4.892-20.127,11.286-31.351,13.396c-2.481,0.466-8.789,1.295-6.691-3.522c0.976-2.237,8.092-4.591,10.146-5.734
+ c8.312-4.623,16.392-10.524,24.155-16.176c9.498-6.862,20.838-11.186,28.305-20.684c3.055-3.885,3.543-4.922,2.818-9.39
+ c-0.696-4.263-1.346-9.174-2.244-13.439C238.137,266.029,236.718,261.378,234.155,257.063z"/>
+ </g>
+ </g>
+ <radialGradient id="gradient_radial_dung"
+ cx="0" cy="0" r="60"
+ fx="0" fy="0" gradientUnits="userSpaceOnUse"
+ >
+ <stop offset="0" stop-color="#9a9a9a" />
+ <stop offset="0.70" stop-color="#bababa" />
+ <stop offset="0.95" stop-color="#FFFFFF" />
+ </radialGradient>
+ <g id="dung">
+ <circle cx="0" cy="0" r="60" fill="url(#gradient_radial_dung)" />
+ <g transform="translate(-61, -61)">
+ <!-- rough equivalent: <circle cx="0" cy="0" r="60" stroke="#8a8a8a" stroke-width="2" /> -->
+ <path fill="#8a8a8a" d="M0,61c0,33.636,27.364,61,61,61s61-27.364,61-61S94.636,0,61,0S0,27.364,0,61z
+ M2,61C2,28.467,28.467,2,61,2c32.532,0,59,26.467,59,59c0,32.533-26.468,59-59,59C28.467,120,2,93.533,2,61z"/>
+ </g>
+ <use xlink:href="#hacker_emblem" x="0" y="0" transform="scale(9)" />
+ </g>
+
+ <!-- scarab dimensions: 300x340 -->
+ <!-- dung dimensions: 120x120 (radius: 60) -->
+ <!-- scarab and dung dimensions: 300x400 -->
+
+ <g id="cairo_logo">
+ <!-- dimensions: 300x400, centered -->
+ <!-- The logo (scarab and dung), with the center-point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(0, -140)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(0, 30)" />
+ </g>
+ <g id="cairo_logo_dung-centered">
+ <!-- The logo (scarab and dung), with the dung at (0,0), the scarab below -->
+ <use xlink:href="#dung" x="0" y="0" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(0,170)" />
+ </g>
+ <g id="cairo_logo_scarab-centered">
+ <!-- The logo (scarab and dung), with the scarab's rotational center at (0,0), the dung above -->
+ <!-- The scarab's rotational center in this case is not the center of its bounding box,
+ but is calculated to be the intersection-point of the torso, spine and wings -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(0, -175.85)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(0, -5.85)" />
+ </g>
+ <g id="cairo_logo_top-centered">
+ <!-- The logo (scarab and dung), with the top-center point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(0, 60)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(0, 230)" /><!-- (0,170+60) -->
+ </g>
+ <g id="cairo_logo_bottom-centered">
+ <!-- The logo (scarab and dung), with the bottom-center point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(0, -340)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(0, -170)" />
+ </g>
+ <g id="cairo_logo_right-centered">
+ <!-- The logo (scarab and dung), with the right-center point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(-150, -140)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(-150, 30)" />
+ </g>
+ <g id="cairo_logo_left-centered">
+ <!-- The logo (scarab and dung), with the left-center point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(150, -140)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(150, 30)" />
+ </g>
+ <g id="cairo_logo_topleft-centered">
+ <!-- The logo (scarab and dung), with the top-left point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(150, 60)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(150, 230)" /><!-- (150, 170+60) -->
+ </g>
+ <g id="cairo_logo_topright-centered">
+ <!-- The logo (scarab and dung), with the top-right point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(-150, 60)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(-150, 230)" /><!-- (-150,170+60) -->
+ </g>
+ <g id="cairo_logo_bottomleft-centered">
+ <!-- The logo (scarab and dung), with the bottom-left point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(150, -340)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(150, -170)" />
+ </g>
+ <g id="cairo_logo_bottomright-centered">
+ <!-- The logo (scarab and dung), with the bottom-right point of the bounding box at (0,0) -->
+ <use xlink:href="#dung" x="0" y="0" transform="translate(-150, -340)" />
+ <use xlink:href="#scarab" x="0" y="0" transform="translate(-150, -170)" />
+ </g>
+
+ <g id="cairo_text" transform="translate(0,-97)">
+ <g transform="scale(0.1484,0.1484)"> <g transform="translate(-1139,-208.5)">
+ <!-- 63 (c), advance 444, 0 horiBearing 38,522 -->
+ <path transform="translate(65,0)" d="
+ M 412, 433
+ C 385, 422 336, 413 298, 413
+ C 142, 413 38, 525 38, 680
+ C 38, 826 144, 947 298, 947
+ C 332, 947 377, 944 416, 926
+ L 409, 842
+ C 380, 861 340, 871 308, 871
+ C 187, 871 138, 771 138, 680
+ C 138, 583 197, 489 302, 489
+ C 332, 489 368, 496 404, 511
+ L 412, 433 " />
+ <!-- 61 (a), advance 556, 0 horiBearing 46,522 -->
+ <path transform="translate(486.75,0)" d="
+ M 109, 541
+ C 147, 509 204, 489 257, 489
+ C 351, 489 383, 534 383, 622
+ C 346, 620 320, 620 283, 620
+ C 186, 620 46, 660 46, 788
+ C 46, 899 123, 947 233, 947
+ C 319, 947 369, 900 391, 869
+ L 393, 869
+ L 393, 935
+ L 481, 935
+ C 479, 920 477, 893 477, 835
+ L 477, 624
+ C 477, 485 418, 413 272, 413
+ C 207, 413 151, 433 104, 461
+ L 109, 541
+ M 383, 737
+ C 383, 813 334, 871 241, 871
+ C 198, 871 146, 842 146, 788
+ C 146, 698 272, 690 323, 690
+ C 343, 690 363, 692 383, 692
+ L 383, 737 " />
+ <!-- 69 (i), advance 278, 0 horiBearing 86,730 -->
+ <path transform="translate(1000,0)" d="
+ M 92, 935
+ L 186, 935
+ L 186, 425
+ L 92, 425
+ L 92, 935
+ M 88, 261
+ A 51, 51 0 1 1 190,261
+ A 51, 51 0 1 1 88,261" />
+ <!-- 72 (r), advance 389, 0 horiBearing 80,522 -->
+ <path transform="translate(1234.25,0)" d="
+ M 80, 935
+ L 174, 935
+ L 174, 703
+ C 174, 575 229, 495 313, 495
+ C 329, 495 348, 497 365, 504
+ L 365, 420
+ C 345, 416 331, 413 303, 413
+ C 249, 413 195, 451 170, 504
+ L 168, 504
+ L 168, 425
+ L 80, 425
+ L 80, 935 " />
+ <!-- 6f (o), advance 611, 0 horiBearing 46,522 -->
+ <path transform="translate(1610,0)" d="
+ M 46, 680
+ C 46, 826 152, 947 306, 947
+ C 459, 947 565, 826 565, 680
+ C 565, 525 461, 413 306, 413
+ C 150, 413 46, 525 46, 680
+ M 146, 680
+ C 146, 583 205, 489 306, 489
+ C 406, 489 465, 583 465, 680
+ C 465, 771 416, 871 306, 871
+ C 195, 871 146, 771 146, 680 " />
+ <!-- bounds: 38, 205 <-> 2232, 947 -->
+ </g> </g>
+ </g>
+
+ <!-- scaled by 0.72, shifted around to hit pixel boundaries -->
+ <g id="cairo_text_small" transform="translate(0,-71)">
+ <g transform="scale(0.085,0.085)"> <g transform="translate(-1139,-208.5)">
+ <!-- 63 (c), advance 444, 0 horiBearing 38,522 -->
+ <path transform="translate(-151,0)" d="
+ M 412, 433
+ C 385, 422 336, 413 298, 413
+ C 142, 413 38, 525 38, 680
+ C 38, 826 144, 947 298, 947
+ C 332, 947 377, 944 416, 926
+ L 409, 842
+ C 380, 861 340, 871 308, 871
+ C 187, 871 138, 771 138, 680
+ C 138, 583 197, 489 302, 489
+ C 332, 489 368, 496 404, 511
+ L 412, 433 " />
+ <!-- 61 (a), advance 556, 0 horiBearing 46,522 -->
+ <path transform="translate(379.5,0)" d="
+ M 109, 541
+ C 147, 509 204, 489 257, 489
+ C 351, 489 383, 534 383, 622
+ C 346, 620 320, 620 283, 620
+ C 186, 620 46, 660 46, 788
+ C 46, 899 123, 947 233, 947
+ C 319, 947 369, 900 391, 869
+ L 393, 869
+ L 393, 935
+ L 481, 935
+ C 479, 920 477, 893 477, 835
+ L 477, 624
+ C 477, 485 418, 413 272, 413
+ C 207, 413 151, 433 104, 461
+ L 109, 541
+ M 383, 737
+ C 383, 813 334, 871 241, 871
+ C 198, 871 146, 842 146, 788
+ C 146, 698 272, 690 323, 690
+ C 343, 690 363, 692 383, 692
+ L 383, 737 " />
+ <!-- 69 (i), advance 278, 0 horiBearing 86,730 -->
+ <path transform="translate(1000,0)" d="
+ M 92, 935
+ L 186, 935
+ L 186, 425
+ L 92, 425
+ L 92, 935
+ M 88, 261
+ A 51, 51 0 1 1 190,261
+ A 51, 51 0 1 1 88,261" />
+ <!-- 72 (r), advance 389, 0 horiBearing 80,522 -->
+ <path transform="translate(1341.5,0)" d="
+ M 80, 935
+ L 174, 935
+ L 174, 703
+ C 174, 575 229, 495 313, 495
+ C 329, 495 348, 497 365, 504
+ L 365, 420
+ C 345, 416 331, 413 303, 413
+ C 249, 413 195, 451 170, 504
+ L 168, 504
+ L 168, 425
+ L 80, 425
+ L 80, 935 " />
+ <!-- 6f (o), advance 611, 0 horiBearing 46,522 -->
+ <path transform="translate(1826,0)" d="
+ M 46, 680
+ C 46, 826 152, 947 306, 947
+ C 459, 947 565, 826 565, 680
+ C 565, 525 461, 413 306, 413
+ C 150, 413 46, 525 46, 680
+ M 146, 680
+ C 146, 583 205, 489 306, 489
+ C 406, 489 465, 583 465, 680
+ C 465, 771 416, 871 306, 871
+ C 195, 871 146, 771 146, 680 " />
+ <!-- bounds: 38, 205 <-> 2232, 947 -->
+ </g> </g>
+ </g>
+
+ <g id="cairo_logo_with_text">
+ <!-- The logo (scarab and dung), with the text 'cairo' below, the dot of the 'i' positioned between the hind legs of the scarab -->
+ <!-- dimensions: 300x490, centered -->
+ <use xlink:href="#cairo_logo_top-centered" transform="translate(0, -245)" />
+ <use xlink:href="#cairo_text" transform="translate(0, 245)" />
+ </g>
+
+ <g id="cairo_banner">
+ <!-- The logo on the left, the text 'cairo' in the center, and a mirror image of the logo on the right -->
+ <!-- The logos are scaled such that the scarab body nearly matches the height of the text characters (excepting the 'i')
+ and the dung should nearly aligns with the dot of the 'i'. The bottoms of the logos are aligned with the bottom of the text. -->
+ <!-- dimensions: 370x88, centered -->
+ <use xlink:href="#cairo_logo_bottomleft-centered" transform="translate(-180, 40), scale(0.1944)" />
+ <use xlink:href="#cairo_text_small" transform="translate(0, 42)" fill="black" />
+ <use xlink:href="#cairo_logo_bottomleft-centered" transform="translate(180, 40), scale(0.1944), scale(-1, 1)" />
+ </g>
+ </defs>
+ <g transform="translate(5, -500)">
+ <use xlink:href="#cairo_logo_with_text" transform="translate(150, 245)" />
+ </g>
+</svg>]]>
+ </svgDoc>
+ <colorPalettes>
+ </colorPalettes>
+ </SVG>
+
+</ttFont>
diff --git a/test/cairo-test.c b/test/cairo-test.c
index cbd4fb1ab..5a2cf1a74 100644
--- a/test/cairo-test.c
+++ b/test/cairo-test.c
@@ -1825,3 +1825,70 @@ cairo_test_status_from_status (const cairo_test_context_t *ctx,
return CAIRO_TEST_FAILURE;
}
+
+#if CAIRO_HAS_FT_FONT
+
+#include "cairo-ft.h"
+
+static void
+_free_face (void *face)
+{
+ FT_Done_Face ((FT_Face) face);
+}
+
+static FT_Library ft_library = NULL;
+
+#endif
+
+static const cairo_user_data_key_t ft_font_key;
+
+cairo_test_status_t
+cairo_test_ft_select_font_from_file (cairo_t *cr,
+ const char *filename)
+{
+ const cairo_test_context_t *ctx = cairo_test_get_context (cr);
+#if CAIRO_HAS_FT_FONT
+ FT_Face face;
+ cairo_font_face_t *font_face;
+ char *srcdir_filename = NULL;
+
+ if (access (filename, F_OK) != 0) {
+ if (ctx->srcdir) {
+ xasprintf (&srcdir_filename, "%s/%s", ctx->srcdir, filename);
+ filename = srcdir_filename;
+ }
+ }
+
+ if (access (filename, F_OK) != 0) {
+ cairo_test_log (ctx, "Could not find font file: %s\n", filename);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ if (!ft_library) {
+ if (FT_Init_FreeType (&ft_library))
+ return CAIRO_TEST_FAILURE;
+ }
+
+ if (FT_New_Face (ft_library, filename, 0, &face)) {
+ cairo_test_log (ctx, "FT_New_Face failed loading font file: %s\n", filename);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ free (srcdir_filename);
+ font_face = cairo_ft_font_face_create_for_ft_face (face, 0);
+ if (cairo_font_face_status (font_face))
+ return CAIRO_TEST_FAILURE;
+
+ cairo_font_face_set_user_data (font_face, &ft_font_key, face, _free_face);
+ cairo_set_font_face (cr, font_face);
+ if (cairo_status (cr))
+ return CAIRO_TEST_FAILURE;
+
+ cairo_font_face_destroy (font_face);
+
+ return CAIRO_TEST_SUCCESS;
+#else
+ cairo_test_log (ctx, "cairo_test_ft_select_font_from_file() requires the FreeType backend\n");
+ return CAIRO_TEST_FAILURE;
+#endif
+}
diff --git a/test/cairo-test.h b/test/cairo-test.h
index 6169c5371..adff2583c 100644
--- a/test/cairo-test.h
+++ b/test/cairo-test.h
@@ -323,6 +323,11 @@ cairo_t *
cairo_test_create (cairo_surface_t *surface,
const cairo_test_context_t *ctx);
+/* Set font face from a font file in build or src dir, using the FT backend. */
+cairo_test_status_t
+cairo_test_ft_select_font_from_file (cairo_t *cr,
+ const char *filename);
+
CAIRO_END_DECLS
#endif
diff --git a/test/ft-svg-cairo-logo.c b/test/ft-svg-cairo-logo.c
new file mode 100644
index 000000000..22fb25f23
--- /dev/null
+++ b/test/ft-svg-cairo-logo.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2022 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairo-test.h"
+#include <cairo-ft.h>
+
+#define FONT_SIZE 200
+#define MARGIN 5
+#define WIDTH (FONT_SIZE + MARGIN*2)
+#define HEIGHT (FONT_SIZE + MARGIN*2)
+
+#define FONT_FILE "cairo-logo-font.ttf"
+
+/* Character code in font of the logo */
+#define CAIRO_LOGO_CHAR "A"
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+ cairo_test_status_t result;
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ result = cairo_test_ft_select_font_from_file (cr, FONT_FILE);
+ if (result)
+ return result;
+
+ cairo_set_font_size (cr, FONT_SIZE);
+ cairo_move_to (cr, MARGIN, FONT_SIZE + MARGIN);
+
+ cairo_show_text (cr, CAIRO_LOGO_CHAR);
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+CAIRO_TEST (ft_svg_cairo_logo,
+ "Test cairo logo SVG font",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw)
diff --git a/test/meson.build b/test/meson.build
index 7d6d6e11b..3071ac6ca 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -429,6 +429,10 @@ test_ft_svg_font_sources = [
'ft-svg-color-font.c',
]
+test_ft_svg_ttx_font_sources = [
+ 'ft-svg-cairo-logo.c',
+]
+
test_gl_sources = [
'gl-device-release.c',
'gl-oversized-surface.c',
@@ -519,6 +523,10 @@ ps2png_sources = [
'ps2png.c',
]
+test_ttx_fonts = [
+ 'cairo-logo-font.ttx',
+]
+
build_any2ppm = false
has_multipage_surfaces = false
add_fallback_resolution = false
@@ -531,6 +539,9 @@ if feature_conf.get('CAIRO_HAS_FT_FONT', 0) == 1 and feature_conf.get('CAIRO_HAS
test_sources += test_ft_font_sources
if conf.get('HAVE_FT_SVG_DOCUMENT', 0) == 1
test_sources += test_ft_svg_font_sources
+ if conf.get('CAIRO_CAN_TEST_TTX_FONT', 0) == 1
+ test_sources += test_ft_svg_ttx_font_sources
+ endif
endif
endif
@@ -644,10 +655,22 @@ endforeach
if build_machine.system() != 'windows'
run_command('ln', '-sf',
meson.current_source_dir(),
- join_paths(meson.current_build_dir(), 'srcdir'),
+ meson.current_build_dir() / 'srcdir',
check: true)
endif
+if ttx.found()
+ # By default, if the output file exists, ttx creates a new name. We specify the full
+ # output name to make ttx overwrite the existing file instead of creating a new file.
+ foreach ttx_font : test_ttx_fonts
+ custom_target(ttx_font,
+ input: ttx_font,
+ command: [ ttx, '-q', '-o', '@OUTDIR@' / '@BASENAME at .ttf', '@INPUT@' ],
+ output: '@BASENAME at .ttf',
+ build_by_default: true)
+ endforeach
+endif
+
env = environment()
env.set('srcdir', meson.current_source_dir())
diff --git a/test/reference/ft-svg-cairo-logo.ref.png b/test/reference/ft-svg-cairo-logo.ref.png
new file mode 100644
index 000000000..0c92934d6
Binary files /dev/null and b/test/reference/ft-svg-cairo-logo.ref.png differ
commit 1bd5751324866a2bfbb084fcb7d5462d983a878e
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Tue Jun 14 22:01:04 2022 +0930
FT SVG color font test
diff --git a/configure.ac b/configure.ac
index 0420fba0c..ebca4fd10 100644
--- a/configure.ac
+++ b/configure.ac
@@ -468,7 +468,8 @@ if test "x$use_ft" = "xyes"; then
AC_CHECK_FUNCS(FT_Get_X11_Font_Format FT_GlyphSlot_Embolden FT_GlyphSlot_Oblique FT_Load_Sfnt_Table FT_Library_SetLcdFilter FT_Get_Var_Design_Coordinates FT_Done_MM_Var FT_Palette_Select)
- AC_CHECK_TYPES([FT_SVG_Document], [], [], [[#include <freetype/otsvg.h>]])
+ AC_CHECK_TYPES([FT_SVG_Document], [have_ft_svg=yes], [have_ft_svg=no], [[#include <freetype/otsvg.h>]])
+ AM_CONDITIONAL(HAVE_FT_SVG_DOCUMENT, test "x$have_ft_svg=" = "xyes")
AC_MSG_CHECKING(for FT_HAS_COLOR)
AC_LINK_IFELSE([AC_LANG_PROGRAM([
diff --git a/test/Makefile.am b/test/Makefile.am
index a74d71a7c..df2d59f44 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -14,6 +14,9 @@ if CAIRO_HAS_FT_FONT
test_sources += $(ft_font_test_sources)
if CAIRO_HAS_FC_FONT
test_sources += $(fc_font_test_sources)
+if HAVE_FT_SVG_DOCUMENT
+test_sources += $(fc_svg_font_test_sources)
+endif
endif
endif
diff --git a/test/Makefile.sources b/test/Makefile.sources
index 330aa9da1..107f1b7d0 100644
--- a/test/Makefile.sources
+++ b/test/Makefile.sources
@@ -423,6 +423,9 @@ fc_font_test_sources = \
ft-text-vertical-layout-type3.c \
ft-text-antialias-none.c
+fc_svg_font_test_sources = \
+ ft-svg-color-font.c
+
gl_surface_test_sources = \
gl-device-release.c \
gl-oversized-surface.c \
diff --git a/test/ft-svg-color-font.c b/test/ft-svg-color-font.c
new file mode 100644
index 000000000..56510feca
--- /dev/null
+++ b/test/ft-svg-color-font.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright © 2022 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairo-test.h"
+#include <cairo-ft.h>
+
+#define FONT_SIZE 50
+#define MARGIN 5
+#define WIDTH (FONT_SIZE*4 + MARGIN*2)
+#define HEIGHT (FONT_SIZE*3 + MARGIN*5)
+
+/* Check the full name to ensure we got an SVG font. */
+#define FONT_FAMILY "Twitter Color Emoji"
+#define FONT_FULLNAME "Twitter Color Emoji SVGinOT"
+
+static const char spade_utf8[] = { 0xe2, 0x99, 0xa0, 0x00 }; /* U+2660 glyph 87 */
+static const char club_utf8[] = { 0xe2, 0x99, 0xa3, 0x00 }; /* U+2663 glyph 88 */
+static const char heart_utf8[] = { 0xe2, 0x99, 0xa5, 0x00 }; /* U+2665 glyph 89 */
+static const char diamond_utf8[] = { 0xe2, 0x99, 0xa6, 0x00 }; /* U+2666 glyph 90 */
+
+static cairo_test_status_t
+set_color_emoji_font (cairo_t *cr)
+{
+ cairo_font_options_t *font_options;
+ cairo_font_face_t *font_face;
+ FcPattern *pattern;
+ FcPattern *resolved;
+ FcChar8 *font_name;
+ FcResult result;
+
+ pattern = FcPatternCreate ();
+ if (pattern == NULL)
+ return CAIRO_TEST_NO_MEMORY;
+
+ FcPatternAddString (pattern, FC_FAMILY, (FcChar8 *) FONT_FAMILY);
+ FcConfigSubstitute (NULL, pattern, FcMatchPattern);
+
+ font_options = cairo_font_options_create ();
+ cairo_get_font_options (cr, font_options);
+ cairo_ft_font_options_substitute (font_options, pattern);
+
+ FcDefaultSubstitute (pattern);
+ resolved = FcFontMatch (NULL, pattern, &result);
+ if (resolved == NULL) {
+ FcPatternDestroy (pattern);
+ return CAIRO_TEST_NO_MEMORY;
+ }
+
+ if (FcPatternGetString (resolved, FC_FULLNAME, 0, &font_name) == FcResultMatch) {
+ if (strcmp((char*)font_name, FONT_FULLNAME) != 0) {
+ const cairo_test_context_t *ctx = cairo_test_get_context (cr);
+ cairo_test_log (ctx, "Could not find %s font\n", FONT_FULLNAME);
+ return CAIRO_TEST_UNTESTED;
+ }
+ } else {
+ return CAIRO_TEST_FAILURE;
+ }
+
+ font_face = cairo_ft_font_face_create_for_pattern (resolved);
+ cairo_set_font_face (cr, font_face);
+
+ cairo_font_options_destroy (font_options);
+ cairo_font_face_destroy (font_face);
+ FcPatternDestroy (pattern);
+ FcPatternDestroy (resolved);
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+ cairo_font_options_t *font_options;
+ cairo_test_status_t result;
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ result = set_color_emoji_font (cr);
+ if (result != CAIRO_TEST_SUCCESS)
+ return result;
+
+ cairo_set_font_size (cr, FONT_SIZE);
+
+ /* Color glyphs */
+ cairo_move_to (cr, MARGIN, FONT_SIZE + MARGIN);
+ cairo_show_text (cr, diamond_utf8);
+ cairo_show_text (cr, club_utf8);
+ cairo_show_text (cr, heart_utf8);
+ cairo_show_text (cr, spade_utf8);
+
+ /* Non-color glyphs */
+ font_options = cairo_font_options_create ();
+ cairo_font_options_set_color_mode (font_options, CAIRO_COLOR_MODE_NO_COLOR);
+ cairo_set_font_options (cr, font_options);
+ cairo_move_to (cr, MARGIN, FONT_SIZE*2 + MARGIN*2);
+ cairo_show_text (cr, diamond_utf8);
+ cairo_show_text (cr, club_utf8);
+ cairo_show_text (cr, heart_utf8);
+ cairo_show_text (cr, spade_utf8);
+
+ /* Color glyph text path */
+ cairo_font_options_set_color_mode (font_options, CAIRO_COLOR_MODE_COLOR);
+ cairo_set_font_options (cr, font_options);
+ cairo_font_options_destroy (font_options);
+ cairo_move_to (cr, MARGIN, FONT_SIZE*3 + MARGIN*3);
+ cairo_text_path (cr, diamond_utf8);
+ cairo_text_path (cr, club_utf8);
+ cairo_text_path (cr, heart_utf8);
+ cairo_text_path (cr, spade_utf8);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke (cr);
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+CAIRO_TEST (ft_svg_color_font,
+ "Test color font",
+ "svgrender", /* keywords */
+ NULL, /* requirements */
+ WIDTH, HEIGHT,
+ NULL, draw)
diff --git a/test/meson.build b/test/meson.build
index 47d590690..7d6d6e11b 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -425,6 +425,10 @@ test_ft_font_sources = [
'ft-text-antialias-none.c',
]
+test_ft_svg_font_sources = [
+ 'ft-svg-color-font.c',
+]
+
test_gl_sources = [
'gl-device-release.c',
'gl-oversized-surface.c',
@@ -525,6 +529,9 @@ endif
if feature_conf.get('CAIRO_HAS_FT_FONT', 0) == 1 and feature_conf.get('CAIRO_HAS_FC_FONT', 0) == 1
test_sources += test_ft_font_sources
+ if conf.get('HAVE_FT_SVG_DOCUMENT', 0) == 1
+ test_sources += test_ft_svg_font_sources
+ endif
endif
if feature_conf.get('CAIRO_HAS_QUARTZ_SURFACE', 0) == 1
diff --git a/test/reference/ft-svg-color-font.ref.png b/test/reference/ft-svg-color-font.ref.png
new file mode 100644
index 000000000..22bafde03
Binary files /dev/null and b/test/reference/ft-svg-color-font.ref.png differ
commit 31700fed4f4eb2efb1b445c27b4016ea85bb14f3
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Fri May 6 21:14:13 2022 +0930
Support SVG fonts in FT backend
diff --git a/configure.ac b/configure.ac
index c30ce0916..0420fba0c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -468,6 +468,8 @@ if test "x$use_ft" = "xyes"; then
AC_CHECK_FUNCS(FT_Get_X11_Font_Format FT_GlyphSlot_Embolden FT_GlyphSlot_Oblique FT_Load_Sfnt_Table FT_Library_SetLcdFilter FT_Get_Var_Design_Coordinates FT_Done_MM_Var FT_Palette_Select)
+ AC_CHECK_TYPES([FT_SVG_Document], [], [], [[#include <freetype/otsvg.h>]])
+
AC_MSG_CHECKING(for FT_HAS_COLOR)
AC_LINK_IFELSE([AC_LANG_PROGRAM([
#include <ft2build.h>
diff --git a/meson.build b/meson.build
index 756cfcd6c..831527c43 100644
--- a/meson.build
+++ b/meson.build
@@ -337,6 +337,9 @@ if freetype_dep.found()
if not cc.links(files('meson-cc-tests/ft_has_color.c'), dependencies: freetype_dep, name: 'FT has color')
conf.set('FT_HAS_COLOR', '(0)')
endif
+ if cc.has_type('FT_SVG_Document', dependencies: freetype_dep, prefix: '#include <freetype/otsvg.h>')
+ conf.set('HAVE_FT_SVG_DOCUMENT', 1)
+ endif
check_funcs += ft_check_funcs
deps += [freetype_dep]
endif
diff --git a/src/Makefile.sources b/src/Makefile.sources
index 9328fca97..93b09bddd 100644
--- a/src/Makefile.sources
+++ b/src/Makefile.sources
@@ -285,7 +285,7 @@ cairo_svg_sources = cairo-svg-surface.c
cairo_ft_headers = cairo-ft.h
cairo_ft_private = cairo-ft-private.h
-cairo_ft_sources = cairo-ft-font.c
+cairo_ft_sources = cairo-ft-font.c cairo-svg-glyph-render.c
# These are private, even though they look like public headers
cairo_test_surfaces_private = \
diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c
index b60a57510..c27800b00 100644
--- a/src/cairo-ft-font.c
+++ b/src/cairo-ft-font.c
@@ -46,6 +46,7 @@
#include "cairo-ft-private.h"
#include "cairo-pattern-private.h"
#include "cairo-pixman-private.h"
+#include "cairo-recording-surface-private.h"
#include <float.h>
@@ -67,6 +68,10 @@
#include FT_LCD_FILTER_H
#endif
+#if HAVE_FT_SVG_DOCUMENT
+#include FT_OTSVG_H
+#endif
+
#if HAVE_UNISTD_H
#include <unistd.h>
#elif !defined(access)
@@ -2539,7 +2544,7 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
FT_Palette_Set_Foreground_Color (face, color);
}
- if (FT_Palette_Data_Get(face, &palette_data) == 0 && palette_data.num_palettes > 0) {
+ if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) {
FT_UShort palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
if (scaled_font->base.options.palette_index < palette_data.num_palettes)
palette_index = scaled_font->base.options.palette_index;
@@ -2621,45 +2626,216 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
return status;
}
+#if HAVE_FT_SVG_DOCUMENT
static cairo_int_status_t
-_cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
- cairo_scaled_glyph_t *scaled_glyph,
- cairo_scaled_glyph_info_t info,
- FT_Face face,
- cairo_bool_t vertical_layout,
- int load_flags)
+_cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face,
+ cairo_text_extents_t *extents)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
- cairo_ft_unscaled_font_t *unscaled = scaled_font->unscaled;
- FT_Glyph_Metrics *metrics;
- double x_factor, y_factor;
- FT_GlyphSlot glyph;
- cairo_bool_t scaled_glyph_loaded = FALSE;
- cairo_text_extents_t fs_metrics;
+ cairo_surface_t *recording_surface;
+ cairo_t *cr;
+ cairo_pattern_t *pattern;
+ FT_SVG_Document svg_doc = face->glyph->other;
+ char *svg_document;
+ FT_Color* palette;
+ FT_Palette_Data palette_data;
+
+ /* Create NULL terminated SVG document */
+ svg_document = strndup((const char*)svg_doc->svg_document, svg_doc->svg_document_length);
+
+ recording_surface =
+ cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+
+ cr = cairo_create (recording_surface);
+
+ if (!_cairo_matrix_is_scale_0 (&scaled_font->base.scale)) {
+ cairo_matrix_t scale;
+ scale = scaled_font->base.scale;
+ scale.x0 = scale.y0 = 0.;
+ cairo_set_matrix (cr, &scale);
+ }
- cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
+ cairo_set_font_size (cr, 1.0);
+ cairo_set_font_options (cr, &scaled_font->base.options);
- /* The font metrics for color glyphs should be the same as the
- * outline glyphs. But just in case there aren't, request the
- * color or outline metrics based on the font option and if
- * the font has color.
- */
- int color_flag = 0;
-#ifdef FT_LOAD_COLOR
- if (unscaled->have_color && scaled_font->base.options.color_mode != CAIRO_COLOR_MODE_NO_COLOR)
- color_flag = FT_LOAD_COLOR;
+ pattern = cairo_pattern_create_rgb (0, 0, 0);
+ pattern->is_userfont_foreground = TRUE;
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+
+ extents->x_bearing = DOUBLE_FROM_26_6(face->bbox.xMin);
+ extents->y_bearing = DOUBLE_FROM_26_6(face->bbox.yMin);
+ extents->width = DOUBLE_FROM_26_6(face->bbox.xMax) - extents->x_bearing;
+ extents->height = DOUBLE_FROM_26_6(face->bbox.yMax) - extents->y_bearing;
+
+ palette = NULL;
+ if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) {
+ FT_UShort palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
+ if (scaled_font->base.options.palette_index < palette_data.num_palettes)
+ palette_index = scaled_font->base.options.palette_index;
+
+ if (FT_Palette_Select (face, palette_index, &palette) != 0)
+ palette = NULL;
+ }
+
+ if (!_cairo_matrix_is_scale_0 (&scaled_font->base.scale)) {
+ status = _cairo_render_svg_glyph (svg_document,
+ svg_doc->start_glyph_id,
+ svg_doc->end_glyph_id,
+ _cairo_scaled_glyph_index(scaled_glyph),
+ svg_doc->units_per_EM,
+ palette,
+ palette ? palette_data.num_palette_entries : 0,
+ cr);
+ if (status == CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED)
+ status = CAIRO_INT_STATUS_UNSUPPORTED;
+
+ if (status == CAIRO_STATUS_SUCCESS)
+ status = cairo_status (cr);
+ }
+
+ cairo_destroy (cr);
+ free (svg_document);
+
+ if (status) {
+ cairo_surface_destroy (recording_surface);
+ scaled_glyph->color_glyph = FALSE;
+ scaled_glyph->color_glyph_set = TRUE;
+ return status;
+ }
+
+ _cairo_scaled_glyph_set_recording_surface (scaled_glyph,
+ &scaled_font->base,
+ recording_surface);
+
+ scaled_glyph->color_glyph = TRUE;
+ scaled_glyph->color_glyph_set = TRUE;
+
+ /* get metrics */
+
+ /* Copied from cairo-user-font.c */
+ cairo_matrix_t extent_scale;
+ double extent_x_scale;
+ double extent_y_scale;
+ double snap_x_scale;
+ double snap_y_scale;
+ double fixed_scale, x_scale, y_scale;
+
+ extent_scale = scaled_font->base.scale_inverse;
+ snap_x_scale = 1.0;
+ snap_y_scale = 1.0;
+ status = _cairo_matrix_compute_basis_scale_factors (&extent_scale,
+ &x_scale, &y_scale,
+ 1);
+ if (status == CAIRO_STATUS_SUCCESS) {
+ if (x_scale == 0)
+ x_scale = 1;
+ if (y_scale == 0)
+ y_scale = 1;
+
+ snap_x_scale = x_scale;
+ snap_y_scale = y_scale;
+
+ /* since glyphs are pretty much 1.0x1.0, we can reduce error by
+ * scaling to a larger square. say, 1024.x1024. */
+ fixed_scale = 1024;
+ x_scale /= fixed_scale;
+ y_scale /= fixed_scale;
+
+ cairo_matrix_scale (&extent_scale, 1.0 / x_scale, 1.0 / y_scale);
+
+ extent_x_scale = x_scale;
+ extent_y_scale = y_scale;
+ }
+
+ {
+ /* compute width / height */
+ cairo_box_t bbox;
+ double x1, y1, x2, y2;
+ double x_scale, y_scale;
+
+ /* Compute extents.x/y/width/height from recording_surface,
+ * in font space.
+ */
+ status = _cairo_recording_surface_get_bbox ((cairo_recording_surface_t *) recording_surface,
+ &bbox,
+ &extent_scale);
+ if (unlikely (status))
+ return status;
+
+ _cairo_box_to_doubles (&bbox, &x1, &y1, &x2, &y2);
+
+ x_scale = extent_x_scale;
+ y_scale = extent_y_scale;
+ extents->x_bearing = x1 * x_scale;
+ extents->y_bearing = y1 * y_scale;
+ extents->width = (x2 - x1) * x_scale;
+ extents->height = (y2 - y1) * y_scale;
+ }
+
+ if (scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF) {
+ extents->x_advance = _cairo_lround (extents->x_advance / snap_x_scale) * snap_x_scale;
+ extents->y_advance = _cairo_lround (extents->y_advance / snap_y_scale) * snap_y_scale;
+ }
+
+ return status;
+}
#endif
- status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
- scaled_glyph,
- face,
- load_flags | color_flag,
- !hint_metrics,
- vertical_layout);
- if (unlikely (status))
+
+static cairo_int_status_t
+_cairo_ft_scaled_glyph_init_surface_svg_glyph (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ const cairo_color_t *foreground_color)
+{
+ cairo_surface_t *surface;
+ int width, height;
+ cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+ width = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.x) -
+ _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.x);
+ height = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.y) -
+ _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.y);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+ cairo_surface_set_device_offset (surface,
+ - _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.x),
+ - _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.y));
+
+ status = _cairo_recording_surface_replay_with_foreground_color (scaled_glyph->recording_surface,
+ surface,
+ foreground_color);
+ if (unlikely (status)) {
+ cairo_surface_destroy(surface);
return status;
+ }
- glyph = face->glyph;
- scaled_glyph_loaded = hint_metrics;
+ _cairo_scaled_glyph_set_color_surface (scaled_glyph,
+ &scaled_font->base,
+ (cairo_image_surface_t *)surface,
+ TRUE);
+ surface = NULL;
+
+ if (surface)
+ cairo_surface_destroy (surface);
+
+ return status;
+}
+
+static void
+_cairo_ft_scaled_glyph_get_metrics (cairo_ft_scaled_font_t *scaled_font,
+ FT_Face face,
+ cairo_bool_t vertical_layout,
+ int load_flags,
+ cairo_text_extents_t *fs_metrics)
+{
+ FT_Glyph_Metrics *metrics;
+ double x_factor, y_factor;
+ cairo_ft_unscaled_font_t *unscaled = scaled_font->unscaled;
+ cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
+ FT_GlyphSlot glyph = face->glyph;
/*
* Compute font-space metrics
@@ -2700,14 +2876,14 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
advance = ((metrics->horiAdvance + 32) & -64);
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
+ fs_metrics->x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
+ fs_metrics->y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
- fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
+ fs_metrics->width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
+ fs_metrics->height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
- fs_metrics.x_advance = DOUBLE_FROM_26_6 (advance) * x_factor;
- fs_metrics.y_advance = 0;
+ fs_metrics->x_advance = DOUBLE_FROM_26_6 (advance) * x_factor;
+ fs_metrics->y_advance = 0;
} else {
x1 = (metrics->vertBearingX) & -64;
x2 = (metrics->vertBearingX + metrics->width + 63) & -64;
@@ -2716,39 +2892,142 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
advance = ((metrics->vertAdvance + 32) & -64);
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
+ fs_metrics->x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
+ fs_metrics->y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
- fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
+ fs_metrics->width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
+ fs_metrics->height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
- fs_metrics.x_advance = 0;
- fs_metrics.y_advance = DOUBLE_FROM_26_6 (advance) * y_factor;
+ fs_metrics->x_advance = 0;
+ fs_metrics->y_advance = DOUBLE_FROM_26_6 (advance) * y_factor;
}
} else {
- fs_metrics.width = DOUBLE_FROM_26_6 (metrics->width) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (metrics->height) * y_factor;
+ fs_metrics->width = DOUBLE_FROM_26_6 (metrics->width) * x_factor;
+ fs_metrics->height = DOUBLE_FROM_26_6 (metrics->height) * y_factor;
if (!vertical_layout) {
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->horiBearingX) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (-metrics->horiBearingY) * y_factor;
+ fs_metrics->x_bearing = DOUBLE_FROM_26_6 (metrics->horiBearingX) * x_factor;
+ fs_metrics->y_bearing = DOUBLE_FROM_26_6 (-metrics->horiBearingY) * y_factor;
if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
- fs_metrics.x_advance = DOUBLE_FROM_26_6 (metrics->horiAdvance) * x_factor;
+ fs_metrics->x_advance = DOUBLE_FROM_26_6 (metrics->horiAdvance) * x_factor;
else
- fs_metrics.x_advance = DOUBLE_FROM_16_16 (glyph->linearHoriAdvance) * x_factor;
- fs_metrics.y_advance = 0 * y_factor;
+ fs_metrics->x_advance = DOUBLE_FROM_16_16 (glyph->linearHoriAdvance) * x_factor;
+ fs_metrics->y_advance = 0 * y_factor;
} else {
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingX) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingY) * y_factor;
+ fs_metrics->x_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingX) * x_factor;
+ fs_metrics->y_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingY) * y_factor;
- fs_metrics.x_advance = 0 * x_factor;
+ fs_metrics->x_advance = 0 * x_factor;
if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
- fs_metrics.y_advance = DOUBLE_FROM_26_6 (metrics->vertAdvance) * y_factor;
+ fs_metrics->y_advance = DOUBLE_FROM_26_6 (metrics->vertAdvance) * y_factor;
else
- fs_metrics.y_advance = DOUBLE_FROM_16_16 (glyph->linearVertAdvance) * y_factor;
+ fs_metrics->y_advance = DOUBLE_FROM_16_16 (glyph->linearVertAdvance) * y_factor;
}
}
+}
+
+static cairo_int_status_t
+_cairo_ft_scaled_glyph_init_metrics_svg_glyph (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face,
+ cairo_bool_t vertical_layout,
+ int load_flags)
+{
+ cairo_int_status_t status = CAIRO_INT_STATUS_UNSUPPORTED;
+#if HAVE_FT_SVG_DOCUMENT
+ cairo_text_extents_t fs_metrics;
+ cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
+
+ if (scaled_font->base.options.color_mode == CAIRO_COLOR_MODE_NO_COLOR)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ load_flags | FT_LOAD_COLOR,
+ !hint_metrics,
+ vertical_layout);
+ if (unlikely (status))
+ return status;
+
+ if (face->glyph->format != FT_GLYPH_FORMAT_SVG)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* Get the advance. The other metrics are ignored */
+ _cairo_ft_scaled_glyph_get_metrics (scaled_font,
+ face,
+ vertical_layout,
+ load_flags,
+ &fs_metrics);
+
+ status = (cairo_int_status_t)_cairo_ft_scaled_glyph_init_record_svg_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ &fs_metrics);
+ if (status == CAIRO_INT_STATUS_SUCCESS) {
+ _cairo_scaled_glyph_set_metrics (scaled_glyph,
+ &scaled_font->base,
+ &fs_metrics);
+ }
+#endif
+ return status;
+}
+
+static cairo_int_status_t
+_cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ FT_Face face,
+ cairo_bool_t vertical_layout,
+ int load_flags)
+{
+ cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
+ cairo_text_extents_t fs_metrics;
+
+ cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
+
+ /* _cairo_ft_scaled_glyph_init_metrics() is called once the first
+ * time a cairo_scaled_glyph_t is created. We first check if this
+ * is an SVG glyph as SVG glyphs require the bounding box to be
+ * obtained from the ink extents of the SVG rendering.
+ *
+ * If an SVG glyph is found and succesfully rendered to a
+ * recording surface, the presence of
+ * CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE in the
+ * cairo_scaled_glyph_t indicates that this is an SVG glyph.
+ */
+ status = _cairo_ft_scaled_glyph_init_metrics_svg_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ vertical_layout,
+ load_flags);
+ if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+ return status;
+
+ /* The font metrics for color glyphs should be the same as the
+ * outline glyphs. But just in case there aren't, request the
+ * color or outline metrics based on the font option and if the
+ * font has color.
+ */
+ int color_flag = 0;
+#ifdef FT_LOAD_COLOR
+ if (scaled_font->unscaled->have_color && scaled_font->base.options.color_mode != CAIRO_COLOR_MODE_NO_COLOR)
+ color_flag = FT_LOAD_COLOR;
+#endif
+ status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ load_flags | color_flag,
+ !hint_metrics,
+ vertical_layout);
+ if (unlikely (status))
+ return status;
+
+ _cairo_ft_scaled_glyph_get_metrics (scaled_font,
+ face,
+ vertical_layout,
+ load_flags,
+ &fs_metrics);
_cairo_scaled_glyph_set_metrics (scaled_glyph,
&scaled_font->base,
@@ -2765,7 +3044,6 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
{
cairo_ft_scaled_font_t *scaled_font = abstract_font;
cairo_ft_unscaled_font_t *unscaled = scaled_font->unscaled;
- FT_GlyphSlot glyph;
FT_Face face;
int load_flags = scaled_font->ft_options.load_flags;
cairo_bool_t vertical_layout = FALSE;
@@ -2797,7 +3075,6 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
if (info & CAIRO_SCALED_GLYPH_INFO_METRICS) {
status = _cairo_ft_scaled_glyph_init_metrics (scaled_font,
scaled_glyph,
- info,
face,
vertical_layout,
load_flags);
@@ -2806,15 +3083,23 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
}
if (info & CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE) {
- status = _cairo_ft_scaled_glyph_init_surface (scaled_font,
- scaled_glyph,
- CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE,
- face,
- foreground_color,
- vertical_layout,
- load_flags);
- if (unlikely (status))
- goto FAIL;
+ if (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) {
+ status = _cairo_ft_scaled_glyph_init_surface_svg_glyph (scaled_font,
+ scaled_glyph,
+ foreground_color);
+ if (unlikely (status))
+ goto FAIL;
+ } else {
+ status = _cairo_ft_scaled_glyph_init_surface (scaled_font,
+ scaled_glyph,
+ CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE,
+ face,
+ foreground_color,
+ vertical_layout,
+ load_flags);
+ if (unlikely (status))
+ goto FAIL;
+ }
}
if (info & CAIRO_SCALED_GLYPH_INFO_SURFACE) {
@@ -2832,33 +3117,47 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
if (info & CAIRO_SCALED_GLYPH_INFO_PATH) {
cairo_path_fixed_t *path = NULL; /* hide compiler warning */
- /*
- * A kludge -- the above code will trash the outline,
- * so reload it. This will probably never occur though
- */
- if ((info & (CAIRO_SCALED_GLYPH_INFO_SURFACE | CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE)) != 0) {
- scaled_glyph_loaded = FALSE;
- load_flags |= FT_LOAD_NO_BITMAP;
- }
+ if (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) {
+ path = _cairo_path_fixed_create ();
+ if (!path) {
+ status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ goto FAIL;
+ }
- if (!scaled_glyph_loaded) {
- status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
- scaled_glyph,
- face,
- load_flags,
- FALSE,
- vertical_layout);
- if (unlikely (status))
+ status = _cairo_recording_surface_get_path (scaled_glyph->recording_surface, path);
+ if (unlikely (status)) {
+ _cairo_path_fixed_destroy (path);
goto FAIL;
+ }
- glyph = face->glyph;
- }
+ } else {
+ /*
+ * A kludge -- the above code will trash the outline,
+ * so reload it. This will probably never occur though
+ */
+ if ((info & (CAIRO_SCALED_GLYPH_INFO_SURFACE | CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE)) != 0) {
+ scaled_glyph_loaded = FALSE;
+ load_flags |= FT_LOAD_NO_BITMAP;
+ }
- if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
- status = _decompose_glyph_outline (face, &scaled_font->ft_options.base,
- &path);
- else
- status = CAIRO_INT_STATUS_UNSUPPORTED;
+ if (!scaled_glyph_loaded) {
+ status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ load_flags,
+ FALSE,
+ vertical_layout);
+ if (unlikely (status))
+ goto FAIL;
+ }
+
+ if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
+ status = _decompose_glyph_outline (face, &scaled_font->ft_options.base,
+ &path);
+ } else {
+ status = CAIRO_INT_STATUS_UNSUPPORTED;
+ }
+ }
if (unlikely (status))
goto FAIL;
diff --git a/src/cairo-ft-private.h b/src/cairo-ft-private.h
index 0dc811472..75d65b4db 100644
--- a/src/cairo-ft-private.h
+++ b/src/cairo-ft-private.h
@@ -55,6 +55,21 @@ _cairo_scaled_font_is_ft (cairo_scaled_font_t *scaled_font);
cairo_private unsigned int
_cairo_ft_scaled_font_get_load_flags (cairo_scaled_font_t *scaled_font);
+#if HAVE_FT_SVG_DOCUMENT
+
+typedef struct FT_Color_ FT_Color;
+
+cairo_private cairo_status_t
+_cairo_render_svg_glyph (const char *svg_document,
+ unsigned long first_glyph,
+ unsigned long last_glyph,
+ unsigned long glyph,
+ double units_per_em,
+ FT_Color *palette,
+ int num_palette_entries,
+ cairo_t *cr);
+#endif
+
CAIRO_END_DECLS
#endif /* CAIRO_HAS_FT_FONT */
diff --git a/src/cairo-svg-glyph-render.c b/src/cairo-svg-glyph-render.c
new file mode 100644
index 000000000..738d4f8ff
--- /dev/null
+++ b/src/cairo-svg-glyph-render.c
@@ -0,0 +1,3225 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2022 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ * Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairoint.h"
+#include "cairo-ft-private.h"
+#include "cairo-array-private.h"
+#include "cairo-scaled-font-subsets-private.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#if HAVE_FT_SVG_DOCUMENT
+
+#include <ft2build.h>
+#include FT_COLOR_H
+
+/* #define SVG_RENDER_PRINT_FUNCTIONS 1 */
+
+#define WHITE_SPACE_CHARS " \n\r\t\v\f"
+
+typedef struct {
+ const char *name;
+ int red;
+ int green;
+ int blue;
+} color_name_t;
+
+/* Must be sorted */
+static color_name_t color_names[] = {
+ { "aliceblue", 240, 248, 255 },
+ { "antiquewhite", 250, 235, 215 },
+ { "aqua", 0, 255, 255 },
+ { "aquamarine", 127, 255, 212 },
+ { "azure", 240, 255, 255 },
+ { "beige", 245, 245, 220 },
+ { "bisque", 255, 228, 196 },
+ { "black", 0, 0, 0 },
+ { "blanchedalmond", 255, 235, 205 },
+ { "blue", 0, 0, 255 },
+ { "blueviolet", 138, 43, 226 },
+ { "brown", 165, 42, 42 },
+ { "burlywood", 222, 184, 135 },
+ { "cadetblue", 95, 158, 160 },
+ { "chartreuse", 127, 255, 0 },
+ { "chocolate", 210, 105, 30 },
+ { "coral", 255, 127, 80 },
+ { "cornflowerblue", 100, 149, 237 },
+ { "cornsilk", 255, 248, 220 },
+ { "crimson", 220, 20, 60 },
+ { "cyan", 0, 255, 255 },
+ { "darkblue", 0, 0, 139 },
+ { "darkcyan", 0, 139, 139 },
+ { "darkgoldenrod", 184, 134, 11 },
+ { "darkgray", 169, 169, 169 },
+ { "darkgreen", 0, 100, 0 },
+ { "darkgrey", 169, 169, 169 },
+ { "darkkhaki", 189, 183, 107 },
+ { "darkmagenta", 139, 0, 139 },
+ { "darkolivegreen", 85, 107, 47 },
+ { "darkorange", 255, 140, 0 },
+ { "darkorchid", 153, 50, 204 },
+ { "darkred", 139, 0, 0 },
+ { "darksalmon", 233, 150, 122 },
+ { "darkseagreen", 143, 188, 143 },
+ { "darkslateblue", 72, 61, 139 },
+ { "darkslategray", 47, 79, 79 },
+ { "darkslategrey", 47, 79, 79 },
+ { "darkturquoise", 0, 206, 209 },
+ { "darkviolet", 148, 0, 211 },
+ { "deeppink", 255, 20, 147 },
+ { "deepskyblue", 0, 191, 255 },
+ { "dimgray", 105, 105, 105 },
+ { "dimgrey", 105, 105, 105 },
+ { "dodgerblue", 30, 144, 255 },
+ { "firebrick", 178, 34, 34 },
+ { "floralwhite", 255, 250, 240 },
+ { "forestgreen", 34, 139, 34 },
+ { "fuchsia", 255, 0, 255 },
+ { "gainsboro", 220, 220, 220 },
+ { "ghostwhite", 248, 248, 255 },
+ { "gold", 255, 215, 0 },
+ { "goldenrod", 218, 165, 32 },
+ { "gray", 128, 128, 128 },
+ { "green", 0, 128, 0 },
+ { "greenyellow", 173, 255, 47 },
+ { "grey", 128, 128, 128 },
+ { "honeydew", 240, 255, 240 },
+ { "hotpink", 255, 105, 180 },
+ { "indianred", 205, 92, 92 },
+ { "indigo", 75, 0, 130 },
+ { "ivory", 255, 255, 240 },
+ { "khaki", 240, 230, 140 },
+ { "lavender", 230, 230, 250 },
+ { "lavenderblush", 255, 240, 245 },
+ { "lawngreen", 124, 252, 0 },
+ { "lemonchiffon", 255, 250, 205 },
+ { "lightblue", 173, 216, 230 },
+ { "lightcoral", 240, 128, 128 },
+ { "lightcyan", 224, 255, 255 },
+ { "lightgoldenrodyellow", 250, 250, 210 },
+ { "lightgray", 211, 211, 211 },
+ { "lightgreen", 144, 238, 144 },
+ { "lightgrey", 211, 211, 211 },
+ { "lightpink", 255, 182, 193 },
+ { "lightsalmon", 255, 160, 122 },
+ { "lightseagreen", 32, 178, 170 },
+ { "lightskyblue", 135, 206, 250 },
+ { "lightslategray", 119, 136, 153 },
+ { "lightslategrey", 119, 136, 153 },
+ { "lightsteelblue", 176, 196, 222 },
+ { "lightyellow", 255, 255, 224 },
+ { "lime", 0, 255, 0 },
+ { "limegreen", 50, 205, 50 },
+ { "linen", 250, 240, 230 },
+ { "magenta", 255, 0, 255 },
+ { "maroon", 128, 0, 0 },
+ { "mediumaquamarine", 102, 205, 170 },
+ { "mediumblue", 0, 0, 205 },
+ { "mediumorchid", 186, 85, 211 },
+ { "mediumpurple", 147, 112, 219 },
+ { "mediumseagreen", 60, 179, 113 },
+ { "mediumslateblue", 123, 104, 238 },
+ { "mediumspringgreen", 0, 250, 154 },
+ { "mediumturquoise", 72, 209, 204 },
+ { "mediumvioletred", 199, 21, 133 },
+ { "midnightblue", 25, 25, 112 },
+ { "mintcream", 245, 255, 250 },
+ { "mistyrose", 255, 228, 225 },
+ { "moccasin", 255, 228, 181 },
+ { "navajowhite", 255, 222, 173 },
+ { "navy", 0, 0, 128 },
+ { "oldlace", 253, 245, 230 },
+ { "olive", 128, 128, 0 },
+ { "olivedrab", 107, 142, 35 },
+ { "orange", 255, 165, 0 },
+ { "orangered", 255, 69, 0 },
+ { "orchid", 218, 112, 214 },
+ { "palegoldenrod", 238, 232, 170 },
+ { "palegreen", 152, 251, 152 },
+ { "paleturquoise", 175, 238, 238 },
+ { "palevioletred", 219, 112, 147 },
+ { "papayawhip", 255, 239, 213 },
+ { "peachpuff", 255, 218, 185 },
+ { "peru", 205, 133, 63 },
+ { "pink", 255, 192, 203 },
+ { "plum", 221, 160, 221 },
+ { "powderblue", 176, 224, 230 },
+ { "purple", 128, 0, 128 },
+ { "red", 255, 0, 0 },
+ { "rosybrown", 188, 143, 143 },
+ { "royalblue", 65, 105, 225 },
+ { "saddlebrown", 139, 69, 19 },
+ { "salmon", 250, 128, 114 },
+ { "sandybrown", 244, 164, 96 },
+ { "seagreen", 46, 139, 87 },
+ { "seashell", 255, 245, 238 },
+ { "sienna", 160, 82, 45 },
+ { "silver", 192, 192, 192 },
+ { "skyblue", 135, 206, 235 },
+ { "slateblue", 106, 90, 205 },
+ { "slategray", 112, 128, 144 },
+ { "slategrey", 112, 128, 144 },
+ { "snow", 255, 250, 250 },
+ { "springgreen", 0, 255, 127 },
+ { "steelblue", 70, 130, 180 },
+ { "tan", 210, 180, 140 },
+ { "teal", 0, 128, 128 },
+ { "thistle", 216, 191, 216 },
+ { "tomato", 255, 99, 71 },
+ { "turquoise", 64, 224, 208 },
+ { "violet", 238, 130, 238 },
+ { "wheat", 245, 222, 179 },
+ { "white", 255, 255, 255 },
+ { "whitesmoke", 245, 245, 245 },
+ { "yellow", 255, 255, 0 },
+ { "yellowgreen", 154, 205, 50 }
+};
+
+typedef struct {
+ char *name;
+ char *value;
+} svg_attribute_t;
+
+typedef enum {
+ CONTAINER_ELEMENT,
+ EMPTY_ELEMENT,
+ PROCESSING_INSTRUCTION,
+ DOCTYPE,
+ CDATA,
+ COMMENT
+} tag_type_t;
+
+#define TOP_ELEMENT_TAG "_top"
+
+typedef struct _cairo_svg_element {
+ cairo_hash_entry_t base;
+ tag_type_t type;
+ char *tag;
+ char *id;
+ cairo_array_t attributes; /* svg_attribute_t */
+ cairo_array_t children; /* cairo_svg_element_t* */
+ cairo_array_t content; /* char */
+ cairo_pattern_t *pattern; /* defined if a paint server */
+ struct _cairo_svg_element *next; /* next on element stack */
+} cairo_svg_element_t;
+
+typedef struct _cairo_svg_color {
+ enum { RGB, CURRENT_COLOR } type;
+ double red;
+ double green;
+ double blue;
+} cairo_svg_color_t;
+
+typedef struct _cairo_svg_paint {
+ enum { PAINT_COLOR, PAINT_SERVER, PAINT_NONE } type;
+ cairo_svg_color_t color;
+ cairo_svg_element_t *paint_server;
+} cairo_svg_paint_t;
+
+typedef enum {
+ GS_RENDER,
+ GS_NO_RENDER,
+ GS_COMPUTE_BBOX,
+ GS_CLIP
+} gs_mode_t;
+
+typedef struct _cairo_svg_graphics_state {
+ cairo_svg_paint_t fill;
+ cairo_svg_paint_t stroke;
+ cairo_svg_color_t color;
+ double fill_opacity;
+ double stroke_opacity;
+ double opacity;
+ cairo_fill_rule_t fill_rule;
+ cairo_fill_rule_t clip_rule;
+ cairo_path_t *clip_path;
+ char *dash_array;
+ double dash_offset;
+ gs_mode_t mode;
+ struct {
+ double x;
+ double y;
+ double width;
+ double height;
+ } bbox;
+ struct _cairo_svg_graphics_state *next;
+} cairo_svg_graphics_state_t;
+
+typedef enum {
+ BUILD_PATTERN_NONE,
+ BUILD_PATTERN_LINEAR,
+ BUILD_PATTERN_RADIAL
+} build_pattern_t;
+
+typedef struct _cairo_svg_glyph_render {
+ cairo_svg_element_t *tree;
+ cairo_hash_table_t *ids;
+ cairo_svg_graphics_state_t *graphics_state;
+ cairo_t *cr;
+ cairo_pattern_t *foreground_color;
+ double units_per_em;
+ struct {
+ cairo_svg_element_t *paint_server;
+ cairo_pattern_t *pattern;
+ build_pattern_t type;
+ } build_pattern;
+ int render_element_tree_depth;
+ int num_palette_entries;
+ FT_Color* palette;
+
+ /* Viewport */
+ double width;
+ double height;
+ cairo_bool_t view_port_set;
+
+ int debug; /* 0 = quiet, 1 = errors, 2 = warnings, 3 = info */
+} cairo_svg_glyph_render_t;
+
+
+#define ERROR 1
+#define WARNING 2
+#define INFO 3
+
+#define print_error(render, ...) cairo_svg_glyph_render_printf(render, ERROR, ##__VA_ARGS__)
+#define print_warning(render, ...) cairo_svg_glyph_render_printf(render, WARNING, ##__VA_ARGS__)
+#define print_info(render, ...) cairo_svg_glyph_render_printf(render, INFO, ##__VA_ARGS__)
+
+static void
+cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
+ int level,
+ const char *fmt, ...) CAIRO_PRINTF_FORMAT (3, 4);
+
+static void
+cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
+ int level,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ if (svg_render->debug >= level ) {
+ switch (level) {
+ case ERROR:
+ printf("ERROR: ");
+ break;
+ case WARNING:
+ printf("WARNING: ");
+ break;
+ }
+ va_start (ap, fmt);
+ vprintf (fmt, ap);
+ va_end (ap);
+ printf ("\n");
+ }
+}
+
+static cairo_bool_t
+string_equal (const char *s1, const char *s2)
+{
+ if (s1 && s2)
+ return strcmp (s1, s2) == 0;
+
+ if (!s1 && !s2)
+ return TRUE;
+
+ return FALSE;
+}
+
+static cairo_bool_t
+string_match (const char **p, const char *str)
+{
+ if (*p && strncmp (*p, str, strlen (str)) == 0) {
+ *p += strlen (str);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const char *
+skip_space (const char *p)
+{
+ while (*p && _cairo_isspace (*p))
+ p++;
+
+ return p;
+}
+
+/* Skip over character c and and whitespace before or after. Returns
+ * NULL if c not found. */
+static const char *
+skip_char (const char *p, char c)
+{
+ while (_cairo_isspace (*p))
+ p++;
+
+ if (*p != c)
+ return NULL;
+
+ p++;
+
+ while (_cairo_isspace (*p))
+ p++;
+
+ return p;
+}
+
+static int
+_color_name_compare (const void *a, const void *b)
+{
+ const color_name_t *a_color = a;
+ const color_name_t *b_color = b;
+
+ return strcmp (a_color->name, b_color->name);
+}
+
+static void
+init_element_id_key (cairo_svg_element_t *element)
+{
+ element->base.hash = _cairo_hash_string (element->id);
+}
+
+static cairo_bool_t
+_element_id_equal (const void *key_a, const void *key_b)
+{
+ const cairo_svg_element_t *a = key_a;
+ const cairo_svg_element_t *b = key_b;
+
+ return string_equal (a->id, b->id);
+}
+
+/* Find element with the "id" attribute matching id. id may have the
+ * '#' prefix. It will be stripped before searching.
+ */
+static cairo_svg_element_t *
+lookup_element (cairo_svg_glyph_render_t *svg_render, const char *id)
+{
+ cairo_svg_element_t key;
+
+ if (!id || strlen (id) < 1)
+ return NULL;
+
+ key.id = (char *)(id[0] == '#' ? id + 1 : id);
+ init_element_id_key (&key);
+ return _cairo_hash_table_lookup (svg_render->ids, &key.base);
+}
+
+/* Find element with the "id" attribute matching url where url is of
+ * the form "url(#id)".
+ */
+static cairo_svg_element_t *
+lookup_url_element (cairo_svg_glyph_render_t *svg_render, const char *url)
+{
+ const char *p = url;
+ cairo_svg_element_t *element = NULL;
+
+ if (p && string_match (&p, "url")) {
+ p = skip_char (p, '(');
+ if (!p)
+ return NULL;
+
+ const char *end = strpbrk(p, WHITE_SPACE_CHARS ")");
+ if (end) {
+ char *id = strndup (p, end - p);
+ element = lookup_element (svg_render, id);
+ free (id);
+ }
+ }
+ return element;
+}
+
+static const char *
+get_attribute (const cairo_svg_element_t *element, const char *name)
+{
+ svg_attribute_t attr;
+ int num_elems, i;
+
+ num_elems = _cairo_array_num_elements (&element->attributes);
+ for (i = 0; i < num_elems; i++) {
+ _cairo_array_copy_element (&element->attributes, i, &attr);
+ if (string_equal (attr.name, name))
+ return attr.value;
+ }
+ return NULL;
+}
+
+static const char *
+get_href_attribute (const cairo_svg_element_t *element)
+{
+ svg_attribute_t attr;
+ int num_elems, i, len;
+
+ /* SVG2 requires the href attribute to be "href". Older versions
+ * used "xlink:href". I have seen at least one font that used an
+ * alternative name space eg "ns1:href". To keep things simple we
+ * search for an attribute named "href" or ending in ":href".
+ */
+ num_elems = _cairo_array_num_elements (&element->attributes);
+ for (i = 0; i < num_elems; i++) {
+ _cairo_array_copy_element (&element->attributes, i, &attr);
+ if (string_equal (attr.name, "href"))
+ return attr.value;
+
+ len = strlen (attr.name);
+ if (len > 4 && string_equal (attr.name + len - 5, ":href"))
+ return attr.value;
+ }
+ return NULL;
+}
+
+/* Get a float attribute or float percentage. If attribute is a
+ * percentage, the returned value is percentage * scale. Does not
+ * modify value if it returns FALSE. This allows value to be set to a
+ * default before calling get_float_attribute(), then used without
+ * checking the return value of this function.
+ */
+static cairo_bool_t
+get_float_or_percent_attribute (const cairo_svg_element_t *element,
+ const char *name,
+ double scale,
+ double *value)
+{
+ const char *p;
+ char *end;
+ double v;
+
+ p = get_attribute (element, name);
+ if (p) {
+ v = _cairo_strtod (p, &end);
+ if (end != p) {
+ *value = v;
+ if (*end == '%')
+ *value *= scale / 100.0;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* Does not modify value if it returns FALSE. This allows value to be
+ * set to a default before calling get_float_attribute(), then used
+ * without checking the return value of this function.
+ */
+static cairo_bool_t
+get_float_attribute (const cairo_svg_element_t *element, const char *name, double *value)
+{
+ const char *p;
+ char *end;
+ double v;
+
+ p = get_attribute (element, name);
+ if (p) {
+ v = _cairo_strtod (p, &end);
+ if (end != p) {
+ *value = v;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static cairo_fill_rule_t
+get_fill_rule_attribute (const cairo_svg_element_t *element, const char *name, cairo_fill_rule_t default_value)
+{
+ const char *p;
+
+ p = get_attribute (element, name);
+ if (string_equal (p, "nonzero"))
+ return CAIRO_FILL_RULE_WINDING;
+ else if (string_equal (p, "evenodd"))
+ return CAIRO_FILL_RULE_EVEN_ODD;
+ else
+ return default_value;
+}
+
+static void
+free_elements (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element)
+{
+ int num_elems;
+
+ num_elems = _cairo_array_num_elements (&element->children);
+ for (int i = 0; i < num_elems; i++) {
+ cairo_svg_element_t *child;
+ _cairo_array_copy_element (&element->children, i, &child);
+ free_elements (svg_render, child);
+ }
+ _cairo_array_fini (&element->children);
+
+ num_elems = _cairo_array_num_elements (&element->attributes);
+ for (int i = 0; i < num_elems; i++) {
+ svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
+ free (attr->name);
+ free (attr->value);
+ }
+ _cairo_array_fini (&element->attributes);
+ _cairo_array_fini (&element->content);
+
+ free (element->tag);
+
+ if (element->id) {
+ _cairo_hash_table_remove (svg_render->ids, &element->base);
+ free (element->id);
+ }
+
+ if (element->pattern)
+ cairo_pattern_destroy (element->pattern);
+
+ free (element);
+}
+
+#if SVG_RENDER_PRINT_FUNCTIONS
+
+static void indent(int level)
+{
+ for (int i = 1; i < level; i++)
+ printf(" ");
+}
+
+static void
+print_element (cairo_svg_element_t *element, cairo_bool_t recurse, int level)
+{
+ char *content = strndup (_cairo_array_index_const (&element->content, 0),
+ _cairo_array_num_elements (&element->content));
+
+ indent(level);
+ if (element->type == COMMENT) {
+ printf("<!--%s-->\n", content);
+ } else if (element->type == CDATA) {
+ printf("<![CDATA[%s]]>\n", content);
+ } else if (element->type == DOCTYPE) {
+ printf("<!DOCTYPE%s>\n", content);
+ } else if (element->type == PROCESSING_INSTRUCTION) {
+ printf("<?%s?>\n", content);
+ } else {
+ cairo_bool_t top_element = string_equal (element->tag, TOP_ELEMENT_TAG);
+
+ if (!top_element) {
+ printf("<%s", element->tag);
+ int num_elems = _cairo_array_num_elements (&element->attributes);
+ for (int i = 0; i < num_elems; i++) {
+ svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
+ printf(" %s=\"%s\"", attr->name, attr->value);
+ }
+ if (num_elems > 0)
+ printf(" ");
+
+ if (element->type == EMPTY_ELEMENT)
+ printf("/>\n");
+ else
+ printf(">\n");
+ }
+
+ if (element->type == CONTAINER_ELEMENT) {
+ if (recurse) {
+ int num_elems = _cairo_array_num_elements (&element->children);
+ for (int i = 0; i < num_elems; i++) {
+ cairo_svg_element_t *child;
+ _cairo_array_copy_element (&element->children, i, &child);
+ print_element (child, TRUE, level + 1);
+ }
+ }
+ if (!top_element)
+ printf("</%s>\n", element->tag);
+ }
+ }
+ free (content);
+}
+#endif
+
+static const char *
+parse_list_of_floats (const char *p,
+ int num_required,
+ int num_optional,
+ cairo_bool_t *have_optional,
+ va_list ap)
+{
+ double d;
+ double *dp;
+ char *end;
+ const char *q = NULL;
+ int num_found = 0;
+
+ for (int i = 0; i < num_required + num_optional; i++) {
+ while (p && (*p == ',' || _cairo_isspace (*p)))
+ p++;
+
+ if (!p)
+ break;
+
+ d = _cairo_strtod (p, &end);
+ if (end == p) {
+ p = NULL;
+ break;
+ }
+ p = end;
+ dp = va_arg (ap, double *);
+ *dp = d;
+ num_found++;
+ if (num_found == num_required)
+ q = p;
+ }
+
+ if (num_optional > 0) {
+ if (num_found == num_required + num_optional) {
+ *have_optional = TRUE;
+ } else {
+ *have_optional = FALSE;
+ /* restore pointer to end of required floats */
+ p = q;
+ }
+ }
+
+ return p;
+}
+
+static const char *
+get_floats (const char *p,
+ int num_required,
+ int num_optional,
+ cairo_bool_t *have_optional,
+ ...)
+{
+ va_list ap;
+
+ va_start (ap, have_optional);
+ p = parse_list_of_floats (p, num_required, num_optional, have_optional, ap);
+ va_end (ap);
+ return p;
+}
+
+static const char *
+get_path_params (const char *p, int num_params, ...)
+{
+ va_list ap;
+
+ va_start (ap, num_params);
+ p = parse_list_of_floats (p, num_params, 0, NULL, ap);
+ va_end (ap);
+ return p;
+}
+
+static cairo_bool_t
+get_color (cairo_svg_glyph_render_t *svg_render,
+ const char *s,
+ cairo_svg_color_t *color)
+{
+ int len, matched;
+ unsigned r = 0, g = 0, b = 0;
+
+ if (!s)
+ return FALSE;
+
+ len = strlen(s);
+
+ if (string_equal (s, "inherit") ||
+ string_equal (s, "currentColor") ||
+ string_equal (s, "context-fill") ||
+ string_equal (s, "context-stroke"))
+ {
+ color->type = CURRENT_COLOR;
+ color->red = color->green = color->blue = 0;
+ return TRUE;
+ } else if (len > 0 && s[0] == '#') {
+ if (len == 4) {
+ matched = sscanf (s + 1, "%1x%1x%1x", &r, &g, &b);
+ if (matched == 3) {
+ /* Each digit is repeated to convert to 6 digits. eg 0x123 -> 0x112233 */
+ color->type = RGB;
+ color->red = 0x11*r/255.0;
+ color->green = 0x11*g/255.0;
+ color->blue = 0x11*b/255.0;
+ return TRUE;
+ }
+ } else if (len == 7) {
+ matched = sscanf (s + 1, "%2x%2x%2x", &r, &g, &b);
+ if (matched == 3) {
+ color->type = RGB;
+ color->red = r/255.0;
+ color->green = g/255.0;
+ color->blue = b/255.0;
+ return TRUE;
+ }
+ }
+ } else if (strncmp (s, "rgb", 3) == 0) {
+ matched = sscanf (s, "rgb ( %u , %u , %u )", &r, &g, &b);
+ if (matched == 3) {
+ color->type = RGB;
+ color->red = r/255.0;
+ color->green = g/255.0;
+ color->blue = b/255.0;
+ return TRUE;
+ }
+ } else if (strncmp (s, "var", 3) == 0) {
+ /* CPAL palettes colors. eg "var(--color0, yellow)" */
+ s += 3;
+ s = skip_char (s, '(');
+ if (!string_match (&s, "--color"))
+ return FALSE;
+
+ char *end;
+ int entry = strtol (s, &end, 10);
+ if (end == s)
+ return FALSE;
+
+ if (svg_render->palette && entry > 0 && entry < svg_render->num_palette_entries) {
+ FT_Color *palette_color = &svg_render->palette[entry];
+ color->type = RGB;
+ color->red = palette_color->red / 255.0;
+ color->green = palette_color->green/ 255.0;
+ color->blue = palette_color->blue / 255.0;
+ return TRUE;
+ } else {
+ /* Fallback color */
+ s = skip_char (end, ',');
+ if (!s)
+ return FALSE;
+
+ end = strpbrk(s, WHITE_SPACE_CHARS ")");
+ if (!end || end == s)
+ return FALSE;
+
+ char *fallback = strndup (s, end - s);
+ cairo_bool_t success = get_color (svg_render, fallback, color);
+ free (fallback);
+ return success;
+ }
+ } else {
+ const color_name_t *color_name;
+ color_name_t color_name_key;
+
+ color_name_key.name = (char *) s;
+ color_name = bsearch (&color_name_key,
+ color_names,
+ ARRAY_LENGTH (color_names),
+ sizeof (color_name_t),
+ _color_name_compare);
+ if (color_name) {
+ color->type = RGB;
+ color->red = color_name->red/255.0;
+ color->green = color_name->green/255.0;
+ color->blue = color_name->blue/255.0;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+get_paint (cairo_svg_glyph_render_t *svg_render,
+ const char *p,
+ cairo_svg_paint_t *paint)
+{
+ cairo_svg_element_t *element;
+
+ if (string_match (&p, "none")) {
+ paint->type = PAINT_NONE;
+ paint->paint_server = NULL;
+ } else if (p && strncmp (p, "url", 3) == 0) {
+ element = lookup_url_element (svg_render, p);
+ if (element) {
+ paint->type = PAINT_SERVER;
+ paint->paint_server = element;
+ }
+ } else {
+ if (get_color (svg_render, p, &paint->color)) {
+ paint->type = PAINT_COLOR;
+ paint->paint_server = NULL;
+ }
+ }
+}
+
+#ifdef SVG_RENDER_PRINT_FUNCTIONS
+
+static void
+print_color (cairo_svg_color_t *color)
+{
+ switch (color->type) {
+ case FOREGROUND_COLOR:
+ printf("foreground");
+ break;
+ case RGB:
+ printf("#%02x%02x%02x",
+ (int)(color->red*255),
+ (int)(color->red*255),
+ (int)(color->red*255));
+ break;
+ }
+}
+
+static void
+print_paint (cairo_svg_paint_t *paint)
+{
+ printf("Paint: ");
+ switch (paint->type) {
+ case PAINT_COLOR:
+ printf("color: ");
+ print_color (&paint->color);
+ break;
+ case PAINT_SERVER:
+ printf("server: %s", paint->paint_server->tag);
+ break;
+ case PAINT_NONE:
+ printf("none");
+ break;
+ }
+ printf("\n");
+}
+
+#endif
+
+static void
+parse_error (cairo_svg_glyph_render_t *svg_render,
+ const char *string,
+ const char *location,
+ const char *fmt,
+ ...) CAIRO_PRINTF_FORMAT (4, 5);
+
+static void
+parse_error (cairo_svg_glyph_render_t *svg_render,
+ const char *string,
+ const char *location,
+ const char *fmt,
+ ...)
+{
+ va_list ap;
+ const int context = 40;
+ const char *start;
+ const char *end;
+
+ if (svg_render->debug >= ERROR) {
+ printf("ERROR: ");
+ va_start (ap, fmt);
+ vprintf (fmt, ap);
+ va_end (ap);
+ putchar ('\n');
+ start = location - context;
+ if (start < string)
+ start = string;
+
+ end = location + strlen (location);
+ if (end - location > context)
+ end = location + context;
+
+ for (const char *p = start; p < end; p++) {
+ if (_cairo_isspace (*p))
+ putchar (' ');
+ else
+ putchar (*p);
+ }
+ putchar ('\n');
+
+ for (int i = 0; i < location - start; i++)
+ putchar(' ');
+ putchar ('^');
+ putchar ('\n');
+ printf (" at position %td\n", location - string);
+ }
+}
+
+static cairo_bool_t
+append_attribute (cairo_svg_element_t *element, svg_attribute_t *attribute)
+{
+ const char *p;
+ const char *end;
+ svg_attribute_t attr;
+
+ memset (&attr, 0, sizeof (attr));
+ if (string_equal (attribute->name, "style")) {
+ /* split style into individual attributes */
+ p = attribute->value;
+ while (*p) {
+ end = strchr (p, ':');
+ if (!end || end == p)
+ break;
+ attr.name = strndup (p, end - p);
+ p = end + 1;
+ p = skip_space(p);
+ end = strchr (p, ';');
+ if (!end)
+ end = strchr (p, 0);
+ if (end == p)
+ goto split_style_fail;
+
+ attr.value = strndup (p, end - p);
+ if (*end)
+ p = end + 1;
+
+ if (_cairo_array_append (&element->attributes, &attr))
+ goto split_style_fail;
+
+ memset (&attr, 0, sizeof (attr));
+ p = skip_space (p);
+ }
+ }
+
+ if (_cairo_array_append (&element->attributes, attribute))
+ return FALSE;
+
+ return TRUE;
+
+ split_style_fail:
+ free (attr.name);
+ free (attr.value);
+ return FALSE;
+}
+
+static cairo_bool_t
+add_child_element (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *parent,
+ cairo_svg_element_t *child)
+{
+ cairo_status_t status;
+ const char* id;
+
+ id = get_attribute (child, "id");
+ if (id) {
+ child->id = strdup (id);
+ init_element_id_key (child);
+ status = _cairo_hash_table_insert (svg_render->ids, &child->base);
+ if (unlikely (status))
+ return FALSE;
+ }
+
+ status = _cairo_array_append (&parent->children, &child);
+ return status == CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_svg_element_t *
+create_element (tag_type_t type, char *tag)
+{
+ cairo_svg_element_t *elem;
+ cairo_status_t status;
+
+ elem = _cairo_malloc (sizeof (cairo_svg_element_t));
+ if (unlikely (elem == NULL)) {
+ status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ return NULL;
+ }
+
+ elem->type = type;
+ elem->tag = tag;
+ elem->id = NULL;
+ _cairo_array_init (&elem->attributes, sizeof(svg_attribute_t));
+ _cairo_array_init (&elem->children, sizeof(cairo_svg_element_t *));
+ _cairo_array_init (&elem->content, sizeof(char));
+ elem->pattern = NULL;
+ elem->next = NULL;
+
+ return elem;
+}
+
+static const char *
+parse_attributes (cairo_svg_glyph_render_t *svg_render,
+ const char *attributes,
+ cairo_svg_element_t *element)
+{
+ svg_attribute_t attr;
+ char quote_char;
+ const char *p;
+ const char *end;
+
+ p = attributes;
+ memset (&attr, 0, sizeof (svg_attribute_t));
+ p = skip_space (p);
+ while (*p && *p != '/' && *p != '>' && *p != '?') {
+ end = strpbrk(p, WHITE_SPACE_CHARS "=");
+ if (!end) {
+ parse_error (svg_render, attributes, p, "Could not find '='");
+ goto fail;
+ }
+
+ if (end == p) {
+ parse_error (svg_render, attributes, p, "Missing attribute name");
+ goto fail;
+ }
+
+ attr.name = strndup (p, end - p);
+ p = end;
+
+ p = skip_space (p);
+ if (*p != '=') {
+ parse_error (svg_render, attributes, p, "Expected '='");
+ goto fail;
+ }
+
+ p++;
+ p = skip_space (p);
+ if (*p == '\"' || *p == '\'') {
+ quote_char = *p;
+ } else {
+ parse_error (svg_render, attributes, p, "Could not find '\"' or '''");
+ goto fail;
+ }
+
+ p++;
+ end = strchr (p, quote_char);
+ if (!end) {
+ parse_error (svg_render, attributes, p, "Could not find '%c'", quote_char);
+ goto fail;
+ }
+
+ attr.value = strndup (p, end - p);
+ p = end + 1;
+
+ if (!append_attribute (element, &attr))
+ goto fail;
+
+ memset (&attr, 0, sizeof (svg_attribute_t));
+
+ p = skip_space (p);
+ }
+
+ return p;
+
+ fail:
+ free (attr.name);
+ free (attr.value);
+ return NULL;
+}
+
+static cairo_bool_t
+parse_svg (cairo_svg_glyph_render_t *svg_render,
+ const char *svg_document)
+{
+ const char *p = svg_document;
+ const char *end;
+ int nesting; /* when > 0 we parse content */
+ cairo_svg_element_t *open_elem; /* Stack of open elements */
+ cairo_svg_element_t *new_elem = NULL;
+ char *name;
+ cairo_status_t status;
+
+ /* Create top level element to use as a container for all top
+ * level elements in the document and push it on the stack. */
+ open_elem = create_element (CONTAINER_ELEMENT, strdup(TOP_ELEMENT_TAG));
+
+ /* We don't want to add content to the top level container. There
+ * should only be whitesapce between tags. */
+ nesting = 0;
+
+ while (*p) {
+ if (nesting > 0) {
+ /* In an open element. Anything before the next '<' is content */
+ end = strchr (p, '<');
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '<'");
+ goto fail;
+ }
+ status = _cairo_array_append_multiple (&open_elem->content, p, end - p);
+ p = end;
+
+ } else {
+ p = skip_space (p);
+ if (*p == 0)
+ break; /* end of document */
+ }
+
+ /* We should now be at the start of a tag */
+ if (*p != '<') {
+ parse_error (svg_render, svg_document, p, "Could not find '<'");
+ goto fail;
+ }
+
+ p++;
+ if (*p == '!') {
+ p++;
+ if (string_match (&p, "[CDATA[")) {
+ new_elem = create_element (CDATA, NULL);
+ end = strstr (p, "]]>");
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find ']]>'");
+ goto fail;
+ }
+
+ status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
+ p = end + 3;
+ } else if (string_match (&p, "--")) {
+ new_elem = create_element (COMMENT, NULL);
+ end = strstr (p, "-->");
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '-->'");
+ goto fail;
+ }
+
+ status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
+ p = end + 3;
+ } else if (string_match (&p, "DOCTYPE")) {
+ new_elem = create_element (DOCTYPE, NULL);
+ end = strchr (p, '>');
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '>'");
+ goto fail;
+ }
+
+ status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
+ p = end + 1;
+ } else {
+ parse_error (svg_render, svg_document, p, "Invalid");
+ goto fail;
+ }
+
+ if (!add_child_element (svg_render, open_elem, new_elem))
+ goto fail;
+
+ new_elem = NULL;
+ continue;
+ }
+
+ if (*p == '?') {
+ p++;
+ new_elem = create_element (PROCESSING_INSTRUCTION, NULL);
+ end = strstr (p, "?>");
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '?>'");
+ goto fail;
+ }
+
+ status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
+ p = end + 2;
+
+ if (!add_child_element (svg_render, open_elem, new_elem))
+ goto fail;
+
+ new_elem = NULL;
+ continue;
+ }
+
+ if (*p == '/') {
+ /* Closing tag */
+ p++;
+
+ /* find end of tag name */
+ end = strpbrk(p, WHITE_SPACE_CHARS ">");
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '>'");
+ goto fail;
+ }
+
+ name = strndup (p, end - p);
+ p = end;
+ p = skip_space (p);
+ if (*p != '>') {
+ parse_error (svg_render, svg_document, p, "Could not find '>'");
+ free (name);
+ goto fail;
+ }
+
+ p++;
+ if (nesting == 0) {
+ parse_error (svg_render, svg_document, p, "parse_elements: parsed </%s> but no matching start tag", name);
+ free (name);
+ goto fail;
+ }
+ if (!string_equal (name, open_elem->tag)) {
+ parse_error (svg_render, svg_document, p,
+ "parse_elements: found </%s> but current open tag is <%s>",
+ name, open_elem->tag);
+ free (name);
+ goto fail;
+ }
+
+ /* pop top element on open elements stack into new_elem */
+ new_elem = open_elem;
+ open_elem = open_elem->next;
+ new_elem->next = NULL;
+ nesting--;
+
+ free (name);
+ if (!add_child_element (svg_render, open_elem, new_elem))
+ goto fail;
+
+ new_elem = NULL;
+ continue;
+ }
+
+ /* We should now be in a start or empty element tag */
+
+ /* find end of tag name */
+ end = strpbrk(p, WHITE_SPACE_CHARS ">");
+ if (!end) {
+ parse_error (svg_render, svg_document, p, "Could not find '>'");
+ goto fail;
+ }
+
+ name = strndup (p, end - p);
+ p = end;
+
+ new_elem = create_element (CONTAINER_ELEMENT, name);
+ p = parse_attributes (svg_render, p, new_elem);
+ if (!p)
+ goto fail;
+
+ p = skip_space (p);
+ if (*p == '/') {
+ new_elem->type = EMPTY_ELEMENT;
+ p++;
+ }
+
+ if (!p || *p != '>') {
+ print_error (svg_render, "Could not find '>'");
+ goto fail;
+ }
+
+ p++;
+ if (new_elem->type == EMPTY_ELEMENT) {
+ if (!add_child_element (svg_render, open_elem, new_elem))
+ goto fail;
+
+ new_elem = NULL;
+ } else {
+ /* push new elem onto open elements stack */
+ new_elem->next = open_elem;
+ open_elem = new_elem;
+ new_elem = NULL;
+ nesting++;
+ }
+ }
+
+ if (nesting != 0) {
+ parse_error (svg_render, svg_document, p, "Missing closing tag for <%s>", open_elem->tag);
+ goto fail;
+ }
+
+ svg_render->tree = open_elem;
+ return TRUE;
+
+ fail:
+ if (new_elem)
+ free_elements (svg_render, new_elem);
+
+ while (open_elem) {
+ cairo_svg_element_t *elem = open_elem;
+ open_elem = open_elem->next;
+ free_elements (svg_render, elem);
+ }
+
+ return FALSE;
+}
+
+static cairo_bool_t
+parse_transform (const char *p, cairo_matrix_t *matrix)
+{
+ cairo_matrix_t m;
+ double x, y, a;
+ cairo_bool_t have_optional;
+
+ cairo_matrix_init_identity (matrix);
+ while (p) {
+ while (p && (*p == ',' || _cairo_isspace (*p)))
+ p++;
+
+ if (!p || *p == 0)
+ break;
+
+ if (string_match (&p, "matrix")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 6, 0, NULL, &m.xx, &m.yx, &m.xy, &m.yy, &m.x0, &m.y0);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ cairo_matrix_multiply (matrix, &m, matrix);
+
+ } else if (string_match (&p, "translate")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 1, 1, &have_optional, &x, &y);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ if (!have_optional)
+ y = 0;
+
+ cairo_matrix_translate (matrix, x, y);
+
+ } else if (string_match (&p, "scale")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 1, 1, &have_optional, &x, &y);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ if (!have_optional)
+ y = x;
+
+ cairo_matrix_scale (matrix, x, y);
+
+ } else if (string_match (&p, "rotate")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 1, 2, &have_optional, &a, &x, &y);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ if (!have_optional) {
+ x = 0;
+ y = 0;
+ }
+
+ a *= M_PI/180.0;
+ cairo_matrix_translate (matrix, x, y);
+ cairo_matrix_rotate (matrix, a);
+ cairo_matrix_translate (matrix, -x, -y);
+
+ } else if (string_match (&p, "skewX")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 1, 0, NULL, &a);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ a *= M_PI/180.0;
+ cairo_matrix_init_identity (&m);
+ m.xy = tan (a);
+ cairo_matrix_multiply (matrix, &m, matrix);
+
+ } else if (string_match (&p, "skewY")) {
+ p = skip_char (p, '(');
+ if (!p)
+ break;
+
+ p = get_floats (p, 1, 0, NULL, &a);
+ if (!p)
+ break;
+
+ p = skip_char (p, ')');
+ if (!p)
+ break;
+
+ a *= M_PI/180.0;
+ cairo_matrix_init_identity (&m);
+ m.yx = tan (a);
+ cairo_matrix_multiply (matrix, &m, matrix);
+
+ } else {
+ break;
+ }
+ }
+ return p != NULL;
+}
+
+static void
+render_element_tree (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_svg_element_t *display_element,
+ cairo_bool_t children_only);
+
+static cairo_pattern_t *
+create_pattern (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *paint_server)
+{
+ cairo_pattern_t *pattern = NULL;
+
+ if (paint_server) {
+ svg_render->build_pattern.paint_server = paint_server;
+ render_element_tree (svg_render, paint_server, NULL, FALSE);
+ pattern = svg_render->build_pattern.pattern;
+ svg_render->build_pattern.pattern = NULL;
+ svg_render->build_pattern.paint_server = NULL;
+ svg_render->build_pattern.type = BUILD_PATTERN_NONE;
+ }
+
+ if (!pattern)
+ pattern = cairo_pattern_create_rgb (0, 0, 0);
+
+ return pattern;
+}
+
+static cairo_bool_t
+render_element_svg (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double width, height;
+ double vb_x, vb_y, vb_height, vb_width;
+ const char *p;
+ const char *end;
+
+ if (end_tag)
+ return FALSE;
+
+ /* Default viewport width, height is EM square */
+ if (!get_float_or_percent_attribute (element, "width", svg_render->units_per_em, &width))
+ width = svg_render->units_per_em;
+
+ if (!get_float_or_percent_attribute (element, "height", svg_render->units_per_em, &height))
+ height = svg_render->units_per_em;
+
+ /* Transform viewport to unit square, centering it if width != height. */
+ if (width > height) {
+ cairo_scale (svg_render->cr, 1.0/width, 1.0/width);
+ cairo_translate (svg_render->cr, 0, (width - height)/2.0);
+ } else {
+ cairo_scale (svg_render->cr, 1.0/height, 1.0/height);
+ cairo_translate (svg_render->cr, (height - width)/2.0, 0);
+ }
+
+ svg_render->width = width;
+ svg_render->height = height;
+
+ p = get_attribute (element, "viewBox");
+ if (p) {
+ /* Transform viewport to viewbox */
+ end = get_path_params (p, 4, &vb_x, &vb_y, &vb_width, &vb_height);
+ if (!end) {
+ print_warning (svg_render, "viewBox expected 4 numbers: %s", p);
+ return FALSE;
+ }
+ cairo_translate (svg_render->cr, -vb_x * width/vb_width, -vb_y * width/vb_width);
+ cairo_scale (svg_render->cr, width/vb_width, height/vb_height);
+ svg_render->width = vb_width;
+ svg_render->height = vb_height;
+ }
+
+ svg_render->view_port_set = TRUE;
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_clip_path (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
+ const char *p;
+
+ if (end_tag || gs->mode != GS_CLIP || svg_render->build_pattern.type != BUILD_PATTERN_NONE) {
+ return FALSE;
+ }
+
+ p = get_attribute (element, "clipPathUnits");
+ if (string_equal (p, "objectBoundingBox")) {
+ cairo_translate (svg_render->cr,
+ svg_render->graphics_state->bbox.x,
+ svg_render->graphics_state->bbox.y);
+ cairo_scale (svg_render->cr,
+ svg_render->graphics_state->bbox.width,
+ svg_render->graphics_state->bbox.height);
+ }
+
+ return TRUE;
+}
+
+static void
+apply_gradient_attributes (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element)
+{
+ cairo_pattern_t *pattern = svg_render->build_pattern.pattern;
+ cairo_bool_t object_bbox = TRUE;
+ cairo_matrix_t transform;
+ cairo_matrix_t mat;
+ const char *p;
+
+ if (!pattern)
+ return;
+
+ p = get_attribute (element, "gradientUnits");
+ if (string_equal (p, "userSpaceOnUse"))
+ object_bbox = FALSE;
+
+ cairo_matrix_init_identity (&mat);
+ if (object_bbox) {
+ cairo_matrix_translate (&mat,
+ svg_render->graphics_state->bbox.x,
+ svg_render->graphics_state->bbox.y);
+ cairo_matrix_scale (&mat,
+ svg_render->graphics_state->bbox.width,
+ svg_render->graphics_state->bbox.height);
+ }
+
+ p = get_attribute (element, "gradientTransform");
+ if (parse_transform (p, &transform))
+ cairo_matrix_multiply (&mat, &transform, &mat);
+
+ if (cairo_matrix_invert (&mat) == CAIRO_STATUS_SUCCESS)
+ cairo_pattern_set_matrix (pattern, &mat);
+
+ p = get_attribute (element, "spreadMethod");
+ if (string_equal (p, "reflect"))
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT);
+ else if (string_equal (p, "repeat"))
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+}
+
+static cairo_bool_t
+render_element_linear_gradient (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double x1, y1, x2, y2;
+
+ if (svg_render->build_pattern.paint_server != element ||
+ end_tag ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ /* FIXME default value for userSpaceOnUse? */
+ double width = 1.0;
+ double height = 1.0;
+
+ if (!get_float_or_percent_attribute (element, "x1", width, &x1))
+ x1 = 0.0;
+
+ if (!get_float_or_percent_attribute (element, "y1", height, &y1))
+ y1 = 0.0;
+
+ if (!get_float_or_percent_attribute (element, "x2", width, &x2))
+ x2 = width;
+
+ if (!get_float_or_percent_attribute (element, "y2", height, &y2))
+ y2 = 0.0;
+
+ if (svg_render->build_pattern.pattern)
+ abort();
+
+ svg_render->build_pattern.pattern = cairo_pattern_create_linear (x1, y1, x2, y2);
+ svg_render->build_pattern.type = BUILD_PATTERN_LINEAR;
+ apply_gradient_attributes (svg_render, element);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_radial_gradient (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double cx, cy, r, fx, fy;
+
+ if (svg_render->build_pattern.paint_server != element ||
+ end_tag ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ /* FIXME default value for userSpaceOnUse? */
+ double width = 1.0;
+ double height = 1.0;
+
+ if (!get_float_or_percent_attribute (element, "cx", width, &cx))
+ cx = 0.5 * width;
+
+ if (!get_float_or_percent_attribute (element, "cy", height, &cy))
+ cy = 0.5 * height;
+
+ if (!get_float_or_percent_attribute (element, "r", width, &r))
+ r = 0.5 * width;
+
+ if (!get_float_or_percent_attribute (element, "fx", width, &fx))
+ fx = cx;
+
+ if (!get_float_or_percent_attribute (element, "fy", height, &fy))
+ fy = cy;
+
+ svg_render->build_pattern.pattern = cairo_pattern_create_radial (fx, fy, 0, cx, cy, r);
+ svg_render->build_pattern.type = BUILD_PATTERN_RADIAL;
+ apply_gradient_attributes (svg_render, element);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_stop (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double offset, opacity;
+ cairo_pattern_t *pattern = svg_render->build_pattern.pattern;
+
+ if (!pattern)
+ return FALSE;
+
+ if (cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_LINEAR &&
+ cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_RADIAL)
+ return FALSE;
+
+ if (!get_float_or_percent_attribute (element, "offset", 1.0, &offset))
+ return FALSE;
+
+ if (!get_float_attribute (element, "stop-opacity", &opacity))
+ opacity = 1.0;
+
+ cairo_svg_color_t color;
+ get_color (svg_render, "black", &color);
+ get_color (svg_render, get_attribute(element, "stop-color"), &color);
+ if (color.type == RGB) {
+ cairo_pattern_add_color_stop_rgba (pattern,
+ offset,
+ color.red,
+ color.green,
+ color.blue,
+ opacity);
+ }
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_g (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ if (svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ if (!end_tag) {
+ cairo_push_group (svg_render->cr);
+ } else {
+ cairo_pop_group_to_source (svg_render->cr);
+ cairo_paint_with_alpha (svg_render->cr, svg_render->graphics_state->opacity);
+ }
+ return TRUE;
+}
+
+typedef struct {
+ const char *data; /* current position in base64 data */
+ char buf[3]; /* decode buffer */
+ int buf_pos; /* current position in buf_pos. */
+} base64_decode_t;
+
+static cairo_status_t
+_read_png_from_base64 (void *closure, unsigned char *data, unsigned int length)
+{
+ base64_decode_t *decode = closure;
+ int n, c;
+ unsigned val;
+
+ while (length) {
+ if (decode->buf_pos >= 0) {
+ *data++ = decode->buf[decode->buf_pos++];
+ length--;
+ if (decode->buf_pos == 3)
+ decode->buf_pos = -1;
+ }
+ if (length > 0 && decode->buf_pos < 0) {
+ n = 0;
+ while (*decode->data && n < 4) {
+ c = *decode->data++;
+ if (c >='A' && c <='Z') {
+ val = (val << 6) | (c -'A');
+ n++;
+ } else if (c >='a' && c <='z') {
+ val = (val << 6) | (c -'a' + 26);
+ n++;
+ } else if (c >='0' && c <='9') {
+ val = (val << 6) | (c -'0' + 52);
+ n++;
+ } else if (c =='+') {
+ val = (val << 6) | 62;
+ n++;
+ } else if (c =='/') {
+ val = (val << 6) | 63;
+ n++;
+ } else if (c == '=') {
+ val = (val << 6);
+ n++;
+ }
+ }
+ if (n < 4)
+ return CAIRO_STATUS_READ_ERROR;
+
+ decode->buf[0] = val >> 16;
+ decode->buf[1] = val >> 8;
+ decode->buf[2] = val >> 0;
+ decode->buf_pos = 0;
+ }
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_bool_t
+render_element_image (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double x, y, width, height;
+ int w, h;
+ const char *data;
+ cairo_surface_t *surface;
+ base64_decode_t decode;
+
+ if (svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ if (!get_float_attribute (element, "x", &x))
+ x = 0;
+
+ if (!get_float_attribute (element, "y", &y))
+ y = 0;
+
+ if (!get_float_attribute (element, "width", &width))
+ return FALSE;
+
+ if (!get_float_attribute (element, "height", &height))
+ return FALSE;
+
+ data = get_href_attribute (element);
+ if (!data)
+ return FALSE;
+
+ if (!string_match (&data, "data:image/png;base64,"))
+ return FALSE;
+
+ decode.data = data;
+ decode.buf_pos = -1;
+ surface = cairo_image_surface_create_from_png_stream (_read_png_from_base64, &decode);
+ if (cairo_surface_status (surface)) {
+ print_warning (svg_render, "Unable to decode PNG");
+ cairo_surface_destroy (surface);
+ return FALSE;
+ }
+
+ w = cairo_image_surface_get_width (surface);
+ h = cairo_image_surface_get_height (surface);
+
+ if (w > 0 && h > 0) {
+ cairo_translate (svg_render->cr, x, y);
+ cairo_scale (svg_render->cr, width/w, height/h);
+ cairo_set_source_surface (svg_render->cr, surface, 0, 0);
+ cairo_paint (svg_render->cr);
+ }
+
+ cairo_surface_destroy (surface);
+
+ return FALSE;
+}
+
+static cairo_bool_t
+render_element_use (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double x = 0;
+ double y = 0;
+ const char *id;
+
+ if (end_tag || svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ get_float_attribute (element, "x", &x);
+ get_float_attribute (element, "y", &y);
+
+ id = get_href_attribute (element);
+ if (!id)
+ return FALSE;
+
+ cairo_svg_element_t *use_element = lookup_element (svg_render, id);
+ cairo_translate (svg_render->cr, x, y);
+ render_element_tree (svg_render, use_element, NULL, FALSE);
+ return TRUE;
+}
+
+static cairo_bool_t
+draw_path (cairo_svg_glyph_render_t *svg_render)
+{
+ cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
+ cairo_pattern_t *pattern;
+ cairo_bool_t opacity_group = FALSE;
+
+ if (gs->mode == GS_COMPUTE_BBOX) {
+ cairo_set_source_rgb (svg_render->cr, 0, 0, 0);
+ cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
+ cairo_fill (svg_render->cr);
+ return FALSE;
+ } else if (gs->mode == GS_CLIP) {
+ return FALSE;
+ }
+
+ if (gs->opacity < 1.0) {
+ cairo_push_group (svg_render->cr);
+ opacity_group = TRUE;
+ }
+
+ cairo_path_t *path = cairo_copy_path (svg_render->cr);
+ cairo_new_path (svg_render->cr);
+
+ if (gs->fill.type != PAINT_NONE) {
+ cairo_bool_t group = FALSE;
+ if (gs->fill.type == PAINT_COLOR) {
+ if (gs->fill.color.type == RGB) {
+ cairo_set_source_rgba (svg_render->cr,
+ gs->fill.color.red,
+ gs->fill.color.green,
+ gs->fill.color.blue,
+ gs->fill_opacity);
+ } else if (gs->fill.color.type == CURRENT_COLOR) {
+ cairo_set_source_rgba (svg_render->cr,
+ gs->color.red,
+ gs->color.green,
+ gs->color.blue,
+ gs->fill_opacity);
+ }
+ } else if (gs->fill.type == PAINT_SERVER) {
+ pattern = create_pattern (svg_render, gs->fill.paint_server);
+ cairo_set_source (svg_render->cr, pattern);
+ cairo_pattern_destroy (pattern);
+ if (gs->fill_opacity < 1.0)
+ group = TRUE;
+ }
+
+ if (group)
+ cairo_push_group (svg_render->cr);
+
+ cairo_append_path (svg_render->cr, path);
+ cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
+ cairo_fill (svg_render->cr);
+ if (group) {
+ cairo_pop_group_to_source (svg_render->cr);
+ cairo_paint_with_alpha (svg_render->cr, gs->fill_opacity);
+ }
+ }
+
+ if (gs->stroke.type != PAINT_NONE) {
+ cairo_bool_t group = FALSE;
+ if (gs->stroke.type == PAINT_COLOR) {
+ if (gs->stroke.color.type == RGB) {
+ cairo_set_source_rgba (svg_render->cr,
+ gs->stroke.color.red,
+ gs->stroke.color.green,
+ gs->stroke.color.blue,
+ gs->stroke_opacity);
+ } else if (gs->stroke.color.type == CURRENT_COLOR) {
+ cairo_set_source_rgba (svg_render->cr,
+ gs->color.red,
+ gs->color.green,
+ gs->color.blue,
+ gs->stroke_opacity);
+ }
+ } else if (gs->stroke.type == PAINT_SERVER) {
+ pattern = create_pattern (svg_render, gs->stroke.paint_server);
+ cairo_set_source (svg_render->cr, pattern);
+ cairo_pattern_destroy (pattern);
+ if (gs->stroke_opacity < 1.0)
+ group = TRUE;
+ }
+
+ if (group)
+ cairo_push_group (svg_render->cr);
+
+ cairo_append_path (svg_render->cr, path);
+ cairo_stroke (svg_render->cr);
+
+ if (group) {
+ cairo_pop_group_to_source (svg_render->cr);
+ cairo_paint_with_alpha (svg_render->cr, gs->stroke_opacity);
+ }
+ }
+
+ cairo_path_destroy (path);
+
+ if (opacity_group) {
+ cairo_pop_group_to_source (svg_render->cr);
+ cairo_paint_with_alpha (svg_render->cr, gs->opacity);
+ }
+ return TRUE;
+}
+
+static void
+elliptical_arc (cairo_svg_glyph_render_t *svg_render,
+ double cx,
+ double cy,
+ double rx,
+ double ry,
+ double angle1,
+ double angle2)
+{
+ cairo_save (svg_render->cr);
+ cairo_translate (svg_render->cr, cx, cy);
+ cairo_scale (svg_render->cr, rx, ry);
+ cairo_arc (svg_render->cr, 0, 0, 1, angle1, angle2);
+ cairo_restore (svg_render->cr);
+}
+
+static cairo_bool_t
+render_element_rect (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double x = 0;
+ double y = 0;
+ double width = svg_render->width;
+ double height = svg_render->height;
+ double rx = 0;
+ double ry = 0;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ get_float_or_percent_attribute (element, "x", svg_render->width, &x);
+ get_float_or_percent_attribute (element, "y", svg_render->height, &y);
+ get_float_or_percent_attribute (element, "width", svg_render->width, &width);
+ get_float_or_percent_attribute (element, "height", svg_render->height, &height);
+ get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
+ get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);
+
+ if (rx == 0 && ry == 0) {
+ cairo_rectangle (svg_render->cr, x, y, width, height);
+ } else {
+ cairo_move_to (svg_render->cr, x + rx, y);
+ cairo_line_to (svg_render->cr, x + width - rx, y);
+ elliptical_arc (svg_render, x + width - rx, y + ry, rx, ry, -M_PI/2, 0);
+ cairo_line_to (svg_render->cr, x + width, y + height - ry);
+ elliptical_arc (svg_render, x + width - rx, y + height - ry, rx, ry, 0, M_PI/2);
+ cairo_line_to (svg_render->cr, x + rx, y + height);
+ elliptical_arc (svg_render, x + rx, y + height - ry, rx, ry, M_PI/2, M_PI);
+ cairo_line_to (svg_render->cr, x, y + ry);
+ elliptical_arc (svg_render, x + rx, y + ry, rx, ry, M_PI, -M_PI/2);
+ }
+
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_circle (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double cx = 0;
+ double cy = 0;
+ double r = 0;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
+ get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
+ get_float_or_percent_attribute (element, "r", svg_render->width, &r);
+
+ cairo_arc (svg_render->cr, cx, cy, r, 0, 2*M_PI);
+
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_ellipse (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double cx = 0;
+ double cy = 0;
+ double rx = 0;
+ double ry = 0;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
+ get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
+ get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
+ get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);
+
+ elliptical_arc (svg_render, cx, cy, rx, ry, 0, 2*M_PI);
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_line (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double x1 = 0;
+ double y1 = 0;
+ double x2 = 0;
+ double y2 = 0;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ get_float_or_percent_attribute (element, "x1", svg_render->width, &x1);
+ get_float_or_percent_attribute (element, "y1", svg_render->height, &y1);
+ get_float_or_percent_attribute (element, "x2", svg_render->width, &x2);
+ get_float_or_percent_attribute (element, "y2", svg_render->height, &y2);
+
+ cairo_move_to (svg_render->cr, x1, y1);
+ cairo_line_to (svg_render->cr, x2, y2);
+
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static cairo_bool_t
+render_element_polyline (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ const char *p;
+ const char *end;
+ double x, y;
+ cairo_bool_t have_move = FALSE;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ p = get_attribute (element, "points");
+ do {
+ end = get_path_params (p, 2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "points expected 2 numbers: %s", p);
+ break;
+ }
+ p = end;
+ if (!have_move) {
+ cairo_move_to (svg_render->cr, x, y);
+ have_move = TRUE;
+ } else {
+ cairo_line_to (svg_render->cr, x, y);
+ }
+ p = skip_space (p);
+ } while (p && *p);
+
+ if (string_equal (element->tag, "polygon"))
+ cairo_close_path (svg_render->cr);
+
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static double
+angle_between_vectors (double ux,
+ double uy,
+ double vx,
+ double vy)
+{
+ double dot = ux*vx + uy*vy;
+ double umag = sqrt (ux*ux + uy*uy);
+ double vmag = sqrt (vx*vx + vy*vy);
+ double c = dot/(umag*vmag);
+ if (c > 1.0)
+ c = 1.0;
+
+ if (c < -1.0)
+ c = -1.0;
+
+ double a = acos (c);
+ if (ux * vy - uy * vx < 0.0)
+ a = -a;
+
+ return a;
+}
+
+static void
+arc_path (cairo_t *cr,
+ double x1, double y1,
+ double x2, double y2,
+ double rx, double ry,
+ double rotate,
+ cairo_bool_t large_flag,
+ cairo_bool_t sweep_flag)
+{
+ double x1_, y1_, cx_, cy_;
+ double xm, ym, cx, cy;
+ double a, b, d;
+ double ux, uy, vx, vy;
+ double theta, delta_theta;
+ double epsilon;
+ cairo_matrix_t ctm;
+
+ cairo_get_matrix (cr, &ctm);
+ epsilon = _cairo_matrix_transformed_circle_major_axis (&ctm, cairo_get_tolerance (cr));
+
+ rotate *= M_PI/180.0;
+
+ /* Convert endpoint to center parameterization.
+ * See SVG 1.1 Appendix F.6. Step numbers are the steps in the appendix.
+ */
+
+ rx = fabs (rx);
+ ry = fabs (ry);
+ if (rx < epsilon || ry < epsilon) {
+ cairo_line_to (cr, x2, y2);
+ return;
+ }
+
+ if (fabs(x1 - x2) < epsilon && fabs(y1 - y2) < epsilon) {
+ cairo_line_to (cr, x2, y2);
+ return;
+ }
+
+ /* Step 1 */
+ xm = (x1 - x2)/2;
+ ym = (y1 - y2)/2;
+ x1_ = xm * cos (rotate) + ym * sin (rotate);
+ y1_ = xm * -sin (rotate) + ym * cos (rotate);
+
+ d = (x1_*x1_)/(rx*rx) + (y1_*y1_)/(ry*ry);
+ if (d > 1.0) {
+ d = sqrt (d);
+ rx *= d;
+ ry *= d;
+ }
+
+ /* Step 2 */
+ a = (rx*rx * y1_*y1_) + (ry*ry * x1_*x1_);
+ if (a == 0.0)
+ return;
+
+ b = (rx*rx * ry*ry) / a - 1.0;
+ if (b < 0)
+ b = 0.0;
+
+ d = sqrt(b);
+ if (large_flag == sweep_flag)
+ d = -d;
+
+ cx_ = d * rx*y1_/ry;
+ cy_ = d * -ry*x1_/rx;
+
+ /* Step 3 */
+ cx = cx_ * cos (rotate) - cy_ * sin (rotate) + (x1 + x2)/2;
+ cy = cx_ * sin (rotate) + cy_ * cos (rotate) + (y1 + y2)/2;
+
+ /* Step 4 */
+ ux = (x1_ - cx_)/rx;
+ uy = (y1_ - cy_)/ry;
+ vx = (-x1_ - cx_)/rx;
+ vy = (-y1_ - cy_)/ry;
+ theta = angle_between_vectors (1.0, 0, ux, uy);
+ delta_theta = angle_between_vectors (ux, uy, vx, vy);
+
+ if (!sweep_flag && delta_theta > 0)
+ delta_theta -= 2 * M_PI;
+ else if (sweep_flag && delta_theta < 0)
+ delta_theta += 2 * M_PI;
+
+ /* Now we can call cairo_arc() */
+ cairo_save (cr);
+ cairo_translate (cr, cx, cy);
+ cairo_scale (cr, rx, ry);
+ cairo_rotate (cr, theta);
+ if (delta_theta >= 0.0)
+ cairo_arc (cr, 0, 0, 1, 0, delta_theta);
+ else
+ cairo_arc_negative (cr, 0, 0, 1, 0, delta_theta);
+ cairo_restore (cr);
+}
+
+static void
+get_current_point (cairo_svg_glyph_render_t *svg_render, double *x, double *y)
+{
+ if (cairo_has_current_point (svg_render->cr)) {
+ cairo_get_current_point (svg_render->cr, x, y);
+ } else {
+ *x = 0;
+ *y = 0;
+ }
+}
+
+static void
+reflect_point (double origin_x, double origin_y, double *x, double *y)
+{
+ *x = 2*origin_x - *x;
+ *y = 2*origin_y - *y;
+}
+
+static cairo_bool_t
+render_element_path (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ double cur_x, cur_y;
+ double last_cp_x, last_cp_y;
+ double x, y, x1, y1, x2, y2;
+ double qx1, qy1, qx2, qy2;
+ double rx, ry, rotate, large_flag, sweep_flag;
+ cairo_bool_t rel, have_move;
+ enum { CUBIC, QUADRATIC, OTHER } last_op;
+
+ if (end_tag ||
+ svg_render->graphics_state->mode == GS_NO_RENDER ||
+ svg_render->build_pattern.type != BUILD_PATTERN_NONE)
+ return FALSE;
+
+ last_op = OTHER;
+ const char *p = get_attribute (element, "d");
+ const char *end;
+ int op;
+
+ while (p) {
+ while (p && _cairo_isspace (*p))
+ p++;
+
+ if (!p || *p == 0)
+ break;
+
+ op = *p;
+ switch (op) {
+ case 'M':
+ case 'm':
+ rel = op == 'm';
+ p++;
+ have_move = FALSE;
+ do {
+ end = get_path_params (p, 2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ if (rel) {
+ get_current_point (svg_render, &cur_x, &cur_y);
+ x += cur_x;
+ y += cur_y;
+ }
+ if (!have_move) {
+ cairo_move_to (svg_render->cr, x, y);
+ have_move = TRUE;
+ } else {
+ cairo_line_to (svg_render->cr, x, y);
+ }
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = OTHER;
+ break;
+ case 'Z':
+ case 'z':
+ p++;
+ cairo_close_path (svg_render->cr);
+ last_op = OTHER;
+ break;
+ case 'L':
+ case 'l':
+ rel = op == 'l';
+ p++;
+ do {
+ end = get_path_params (p, 2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ if (rel) {
+ get_current_point (svg_render, &cur_x, &cur_y);
+ x += cur_x;
+ y += cur_y;
+ }
+ cairo_line_to (svg_render->cr, x, y);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = OTHER;
+ break;
+ case 'H':
+ case 'h':
+ rel = op == 'h';
+ p++;
+ do {
+ end = get_path_params (p, 1, &x1);
+ if (!end) {
+ print_warning (svg_render, "path %c expected a number: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ x1 += cur_x;
+ }
+ cairo_line_to (svg_render->cr, x1, cur_y);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = OTHER;
+ break;
+ case 'V':
+ case 'v':
+ rel = op == 'v';
+ p++;
+ do {
+ end = get_path_params (p, 1, &y1);
+ if (!end) {
+ print_warning (svg_render, "path %c expected a number: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ y1 += cur_y;
+ }
+ cairo_line_to (svg_render->cr, cur_x, y1);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = OTHER;
+ break;
+ case 'C':
+ case 'c':
+ rel = op == 'c';
+ p++;
+ do {
+ end = get_path_params (p, 6, &x1, &y1, &x2, &y2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 6 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ if (rel) {
+ get_current_point (svg_render, &cur_x, &cur_y);
+ x1 += cur_x;
+ y1 += cur_y;
+ x2 += cur_x;
+ y2 += cur_y;
+ x += cur_x;
+ y += cur_y;
+ }
+ cairo_curve_to (svg_render->cr, x1, y1, x2, y2, x, y);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = CUBIC;
+ last_cp_x = x2;
+ last_cp_y = y2;
+ break;
+ case 'S':
+ case 's':
+ rel = op == 's';
+ p++;
+ do {
+ end = get_path_params (p, 4, &x2, &y2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 4 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ x2 += cur_x;
+ y2 += cur_y;
+ x += cur_x;
+ y += cur_y;
+ }
+ if (last_op == CUBIC) {
+ x1 = last_cp_x;
+ y1 = last_cp_y;
+ reflect_point (cur_x, cur_y, &x1, &y1);
+ } else {
+ x1 = cur_x;
+ y1 = cur_y;
+ }
+ cairo_curve_to (svg_render->cr, x1, y1, x2, y2, x, y);
+ last_op = CUBIC;
+ last_cp_x = x2;
+ last_cp_y = y2;
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ break;
+ case 'Q':
+ case 'q':
+ rel = op == 'q';
+ p++;
+ do {
+ end = get_path_params (p, 4, &x1, &y1, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 4 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ x1 += cur_x;
+ y1 += cur_y;
+ x += cur_x;
+ y += cur_y;
+ }
+ qx1 = cur_x + (x1 - cur_x)*2/3;
+ qy1 = cur_y + (y1 - cur_y)*2/3;
+ qx2 = x + (x1 - x)*2/3;
+ qy2 = y + (y1 - y)*2/3;
+ cairo_curve_to (svg_render->cr, qx1, qy1, qx2, qy2, x, y);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = QUADRATIC;
+ last_cp_x = x1;
+ last_cp_y = y1;
+ break;
+ case 'T':
+ case 't':
+ rel = op == 't';
+ p++;
+ do {
+ end = get_path_params (p, 2, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ x += cur_x;
+ y += cur_y;
+ }
+ if (last_op == QUADRATIC) {
+ x1 = last_cp_x;
+ y1 = last_cp_y;
+ reflect_point (cur_x, cur_y, &x1, &y1);
+ } else {
+ x1 = cur_x;
+ y1 = cur_y;
+ }
+ qx1 = cur_x + (x1 - cur_x)*2/3;
+ qy1 = cur_y + (y1 - cur_y)*2/3;
+ qx2 = x + (x1 - x)*2/3;
+ qy2 = y + (y1 - y)*2/3;
+ cairo_curve_to (svg_render->cr, qx1, qy1, qx2, qy2, x, y);
+ last_op = QUADRATIC;
+ last_cp_x = x1;
+ last_cp_y = y1;
+ p = skip_space (p);
+ } while (p && *p && *p && !_cairo_isalpha(*p));
+ break;
+ case 'A':
+ case 'a':
+ rel = op == 'a';
+ p++;
+ do {
+ end = get_path_params (p, 7, &rx, &ry, &rotate, &large_flag, &sweep_flag, &x, &y);
+ if (!end) {
+ print_warning (svg_render, "path %c expected 7 numbers: %s", op, p);
+ break;
+ }
+ p = end;
+ get_current_point (svg_render, &cur_x, &cur_y);
+ if (rel) {
+ x += cur_x;
+ y += cur_y;
+ }
+ arc_path (svg_render->cr,
+ cur_x, cur_y,
+ x, y,
+ rx, ry,
+ rotate,
+ large_flag > 0.5,
+ sweep_flag > 0.5);
+ p = skip_space (p);
+ } while (p && *p && !_cairo_isalpha(*p));
+ last_op = OTHER;
+ break;
+ default:
+ p = NULL;
+ break;
+ }
+ }
+
+ draw_path (svg_render);
+ return TRUE;
+}
+
+static void
+init_graphics_state (cairo_svg_glyph_render_t *svg_render)
+{
+ cairo_svg_graphics_state_t *gs;
+ double alpha;
+
+ gs = _cairo_malloc (sizeof (cairo_svg_graphics_state_t));
+ get_paint (svg_render, "black", &gs->fill);
+ get_paint (svg_render, "none", &gs->stroke);
+ gs->color.type = RGB;
+ if (cairo_pattern_get_rgba (svg_render->foreground_color,
+ &gs->color.red,
+ &gs->color.green,
+ &gs->color.blue,
+ &alpha) != CAIRO_STATUS_SUCCESS)
+ {
+ get_color (svg_render, "black", &gs->color);
+ }
+ gs->fill_opacity = 1.0;
+ gs->stroke_opacity = 1.0;
+ gs->opacity = 1.0;
+ gs->fill_rule = CAIRO_FILL_RULE_WINDING;
+ gs->clip_rule = CAIRO_FILL_RULE_WINDING;
+ gs->clip_path = NULL;
+ gs->dash_array = NULL;
+ gs->dash_offset = 0.0;
+ gs->mode = GS_RENDER;
+ gs->bbox.x = 0;
+ gs->bbox.y = 0;
+ gs->bbox.width = 0;
+ gs->bbox.height = 0;
+ gs->next = NULL;
+
+ svg_render->graphics_state = gs;
+
+ cairo_save (svg_render->cr);
+ cairo_set_source_rgb (svg_render->cr, 0, 0, 0);
+ cairo_set_line_width (svg_render->cr, 1.0);
+ cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_BUTT);
+ cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_MITER);
+ cairo_set_miter_limit (svg_render->cr, 4.0);
+}
+
+#define MAX_DASHES 100
+static void update_dash (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element)
+{
+ cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
+ const char *p;
+ char *end;
+ double value;
+ double dash_array[MAX_DASHES];
+ int num_dashes = 0;
+ cairo_bool_t not_zero = FALSE;
+
+ if (gs->dash_array == NULL || string_equal (gs->dash_array, "none")) {
+ cairo_set_dash (svg_render->cr, NULL, 0, 0);
+ return;
+ }
+
+ p = gs->dash_array;
+ while (*p && num_dashes < MAX_DASHES) {
+ while (*p && (*p == ',' || _cairo_isspace (*p)))
+ p++;
+
+ if (*p == 0)
+ break;
+
+ value = _cairo_strtod (p, &end);
+ if (end == p)
+ break;
+
+ p = end;
+ if (*p == '%') {
+ value *= svg_render->width / 100.0;
+ p++;
+ }
+
+ if (value < 0.0)
+ return;
+
+ if (value > 0.0)
+ not_zero = TRUE;
+
+ dash_array[num_dashes++] = value;
+ }
+
+ if (not_zero)
+ cairo_set_dash (svg_render->cr, dash_array, num_dashes, gs->dash_offset);
+}
+
+static cairo_bool_t
+pattern_requires_bbox (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *paint_server)
+{
+ const char *p;
+
+ if (string_equal (paint_server->tag, "linearGradient") ||
+ string_equal (paint_server->tag, "radialGradient"))
+ {
+ p = get_attribute (paint_server, "gradientUnits");
+ if (string_equal (p, "userSpaceOnUse"))
+ return FALSE;
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static cairo_bool_t
+clip_requires_bbox (cairo_svg_glyph_render_t *svg_render,
+ const char *clip_path)
+{
+ cairo_svg_element_t *element;
+ const char *p;
+
+ if (clip_path && strncmp (clip_path, "url", 3) == 0) {
+ element = lookup_url_element (svg_render, clip_path);
+ if (element) {
+ p = get_attribute (element, "clipPathUnits");
+ if (string_equal (p, "objectBoundingBox"))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static cairo_bool_t
+need_bbox (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element)
+{
+ cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
+ cairo_bool_t fill_needs_bbox = FALSE;
+ cairo_bool_t stroke_needs_bbox = FALSE;
+ cairo_bool_t clip_needs_bbox = FALSE;
+
+ if (gs->mode != GS_RENDER)
+ return FALSE;
+
+ if (gs->fill.type == PAINT_SERVER && pattern_requires_bbox (svg_render, gs->fill.paint_server))
+ fill_needs_bbox = TRUE;
+
+ if (gs->stroke.type == PAINT_SERVER && pattern_requires_bbox (svg_render, gs->stroke.paint_server))
+ stroke_needs_bbox = TRUE;
+
+ if (clip_requires_bbox (svg_render, get_attribute (element, "clip-path")))
+ clip_needs_bbox = TRUE;
+
+ if (string_equal (element->tag, "circle") ||
+ string_equal (element->tag, "ellipse") ||
+ string_equal (element->tag, "path") ||
+ string_equal (element->tag, "polygon") ||
+ string_equal (element->tag, "rect"))
+ {
+ return fill_needs_bbox || stroke_needs_bbox || clip_needs_bbox;
+ }
+
+ if (string_equal (element->tag, "line") ||
+ string_equal (element->tag, "polyline"))
+ {
+ return stroke_needs_bbox || clip_needs_bbox;
+ }
+
+ if (string_equal (element->tag, "g") ||
+ string_equal (element->tag, "image") ||
+ string_equal (element->tag, "use"))
+ {
+ return clip_needs_bbox;
+ }
+
+ return FALSE;
+}
+
+static cairo_bool_t
+call_element (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag);
+
+static void
+update_graphics_state (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element)
+{
+ double value;
+ const char *p;
+ cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
+
+ p = get_attribute (element, "transform");
+ if (p) {
+ cairo_matrix_t m;
+ if (parse_transform (p, &m))
+ cairo_transform (svg_render->cr, &m);
+ }
+
+ /* The transform is all we need for bbox computation. The SVG spec
+ * excludes clipping and stroke-width from the bbox. */
+ if (gs->mode == GS_COMPUTE_BBOX)
+ return;
+
+ p = get_attribute (element, "color");
+ if (p)
+ get_color (svg_render, p, &gs->color);
+
+ if (!get_float_attribute (element, "opacity", &gs->opacity))
+ gs->opacity = 1.0;
+
+ p = get_attribute (element, "fill");
+ if (p) {
+ get_paint (svg_render, p, &gs->fill);
+ }
+
+ get_float_attribute (element, "fill-opacity", &gs->fill_opacity);
+
+ gs->fill_rule = get_fill_rule_attribute (element, "fill-rule", gs->fill_rule);
+
+ gs->clip_rule = get_fill_rule_attribute (element, "fill-rule", gs->clip_rule);
+
+ p = get_attribute (element, "stroke");
+ if (p)
+ get_paint (svg_render, p, &gs->stroke);
+
+ if (get_float_or_percent_attribute (element, "stroke-width", svg_render->width, &value))
+ cairo_set_line_width (svg_render->cr, value);
+
+ p = get_attribute (element, "stroke-linecap");
+ if (string_equal (p, "butt"))
+ cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_BUTT);
+ else if (string_equal (p, "round"))
+ cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_ROUND);
+ else if (string_equal (p, "square"))
+ cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_SQUARE);
+
+ p = get_attribute (element, "stroke-linejoin");
+ if (string_equal (p, "miter"))
+ cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_MITER);
+ else if (string_equal (p, "round"))
+ cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_ROUND);
+ else if (string_equal (p, "bevel"))
+ cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_BEVEL);
+
+ if (get_float_attribute (element, "stroke-miterlimit", &value))
+ cairo_set_miter_limit (svg_render->cr, value);
+
+ p = get_attribute (element, "stroke-dasharray");
+ if (p)
+ gs->dash_array = strdup (p);
+
+ get_float_or_percent_attribute (element, "stroke-dashoffset", svg_render->width, &gs->dash_offset);
+ update_dash (svg_render, element);
+
+ /* Some elements may need the bounding box of the element thay are
+ * applied to. As this recursively calls render_element on the
+ * same element while we are in render_element and setting up the
+ * graphics state, we check gs->mode to avoid re-entering the
+ * compute bbox code. The GS_COMPUTE_MODE flag is also used by
+ * render functions to ignore patterns and strokes (SVG spec
+ * ignores stroke with in bbox calculations) and just use a solid
+ * color.
+ */
+ if (gs->mode == GS_RENDER && need_bbox (svg_render, element)) {
+ cairo_surface_t *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+ cairo_t *old_cr = svg_render->cr;
+ svg_render->cr = cairo_create (recording);
+ gs_mode_t old_mode = gs->mode;
+ gs->mode = GS_COMPUTE_BBOX;
+ /* To avoid recursing back into this function, we call the
+ * element directory then use render_element_tree to render
+ * the children */
+ call_element (svg_render, element, FALSE);
+ render_element_tree (svg_render, element, NULL, TRUE);
+ if (element->type == CONTAINER_ELEMENT)
+ call_element (svg_render, element, TRUE);
+ gs->mode = old_mode;
+ cairo_destroy (svg_render->cr);
+ svg_render->cr = old_cr;
+ cairo_recording_surface_ink_extents (recording,
+ &gs->bbox.x,
+ &gs->bbox.y,
+ &gs->bbox.width,
+ &gs->bbox.height);
+ cairo_surface_destroy (recording);
+ }
+
+ /* clip-path may require bbox */
+ p = get_attribute (element, "clip-path");
+ if (p && strncmp (p, "url", 3) == 0) {
+ element = lookup_url_element (svg_render, p);
+ if (element) {
+ gs_mode_t old_mode = gs->mode;
+ gs->mode = GS_CLIP;
+ render_element_tree (svg_render, element, NULL, FALSE);
+ cairo_set_fill_rule (svg_render->cr, gs->clip_rule);
+ cairo_clip (svg_render->cr);
+ gs->mode = old_mode;
+ }
+ }
+}
+
+static void
+save_graphics_state (cairo_svg_glyph_render_t *svg_render)
+{
+ cairo_svg_graphics_state_t *gs;
+
+ cairo_save (svg_render->cr);
+
+ gs = _cairo_malloc (sizeof (cairo_svg_graphics_state_t));
+ gs->fill = svg_render->graphics_state->fill;
+ gs->stroke = svg_render->graphics_state->stroke;
+ gs->color = svg_render->graphics_state->color;
+ gs->fill_opacity = svg_render->graphics_state->fill_opacity;
+ gs->stroke_opacity = svg_render->graphics_state->stroke_opacity;
+ gs->opacity = svg_render->graphics_state->opacity;
+ gs->fill_rule = svg_render->graphics_state->fill_rule;
+ gs->clip_rule = svg_render->graphics_state->clip_rule;
+ gs->clip_path = NULL;
+ gs->dash_array = NULL;
+ if (svg_render->graphics_state->dash_array)
+ gs->dash_array = strdup (svg_render->graphics_state->dash_array);
+ gs->dash_offset = svg_render->graphics_state->dash_offset;
+ gs->mode = svg_render->graphics_state->mode;
+ gs->bbox = svg_render->graphics_state->bbox;
+ gs->next = svg_render->graphics_state;
+ svg_render->graphics_state = gs;
+}
+
+static void
+restore_graphics_state (cairo_svg_glyph_render_t *svg_render)
+{
+ cairo_svg_graphics_state_t *gs;
+
+ gs = svg_render->graphics_state;
+ svg_render->graphics_state = gs->next;
+ if (gs->clip_path)
+ cairo_path_destroy (gs->clip_path);
+ free (gs->dash_array);
+ free (gs);
+
+ cairo_restore (svg_render->cr);
+}
+
+/* render function returns TRUE if render_element_tree() is to render
+ * the child nodes, FALSE if render_element_tree() is to skip the
+ * child nodes.
+ */
+struct render_func {
+ const char *tag;
+ cairo_bool_t (*render) (cairo_svg_glyph_render_t *, cairo_svg_element_t *, cairo_bool_t);
+};
+
+/* Must be sorted */
+static const struct render_func render_funcs[] = {
+ { "circle", render_element_circle },
+ { "clipPath", render_element_clip_path },
+ { "defs", NULL },
+ { "desc", NULL },
+ { "ellipse", render_element_ellipse },
+ { "g", render_element_g },
+ { "image", render_element_image },
+ { "line", render_element_line },
+ { "linearGradient", render_element_linear_gradient },
+ { "metadata", NULL },
+ { "path", render_element_path },
+ { "polygon", render_element_polyline },
+ { "polyline", render_element_polyline },
+ { "radialGradient", render_element_radial_gradient },
+ { "rect", render_element_rect },
+ { "stop", render_element_stop },
+ { "svg", render_element_svg },
+ { "title", NULL },
+ { "use", render_element_use },
+};
+
+static int
+_render_func_compare (const void *a, const void *b)
+{
+ const struct render_func *render_func_a = a;
+ const struct render_func *render_func_b = b;
+
+ return strcmp (render_func_a->tag, render_func_b->tag);
+}
+
+static cairo_bool_t
+call_element (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag)
+{
+ const struct render_func *func;
+ struct render_func key;
+ cairo_bool_t recurse = FALSE;
+
+ key.tag = element->tag;
+ key.render = NULL;
+ func = bsearch (&key,
+ render_funcs,
+ ARRAY_LENGTH (render_funcs),
+ sizeof (struct render_func),
+ _render_func_compare);
+ if (func) {
+ if (func->render) {
+ recurse = func->render (svg_render, element, end_tag);
+ }
+ } else {
+ print_warning (svg_render, "Unsupported element: %s", element->tag);
+ }
+
+ return recurse;
+}
+
+static cairo_bool_t
+render_element (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_bool_t end_tag,
+ cairo_svg_element_t *display_element)
+{
+ cairo_bool_t recurse = FALSE;
+ cairo_svg_graphics_state_t *gs;
+
+ /* Ignore elements if we have not seen "<svg>". Ignore
+ * "<svg>" if we have seen it */
+ if (svg_render->view_port_set) {
+ if (string_equal (element->tag, "svg"))
+ return FALSE;
+ } else {
+ if (!string_equal (element->tag, "svg"))
+ return FALSE;
+ }
+
+ if (element->type == EMPTY_ELEMENT ||
+ (element->type == CONTAINER_ELEMENT && !end_tag))
+ {
+ save_graphics_state (svg_render);
+ update_graphics_state (svg_render, element);
+ }
+
+ gs = svg_render->graphics_state;
+ if (gs->mode == GS_NO_RENDER && element == display_element)
+ gs->mode = GS_RENDER;
+
+ recurse = call_element (svg_render, element, end_tag);
+
+ if (element->type == EMPTY_ELEMENT ||
+ (element->type == CONTAINER_ELEMENT && end_tag))
+ {
+ restore_graphics_state (svg_render);
+ }
+
+ return recurse;
+}
+
+#define MAX_DEPTH 100
+
+static void
+render_element_tree (cairo_svg_glyph_render_t *svg_render,
+ cairo_svg_element_t *element,
+ cairo_svg_element_t *display_element,
+ cairo_bool_t children_only)
+{
+ if (!element)
+ return;
+
+ /* Avoid circular references by limiting the number of recursive
+ * calls to this function. */
+ if (svg_render->render_element_tree_depth > MAX_DEPTH)
+ return;
+
+ svg_render->render_element_tree_depth++;
+ if (element->type == EMPTY_ELEMENT && !children_only) {
+ render_element (svg_render, element, FALSE, display_element);
+
+ } else if (element->type == CONTAINER_ELEMENT) {
+ int num_elems;
+ cairo_bool_t recurse = TRUE;;
+
+ if (!children_only)
+ recurse = render_element (svg_render, element, FALSE, display_element);
+
+ /* We only render the children if the parent returned
+ * success. This is how we avoid rendering non display
+ * elements like gradients, <defs>, and anything not
+ * implemented. */
+ if (recurse) {
+ num_elems = _cairo_array_num_elements (&element->children);
+ for (int i = 0; i < num_elems; i++) {
+ cairo_svg_element_t *child;
+ _cairo_array_copy_element (&element->children, i, &child);
+ render_element_tree (svg_render, child, display_element, FALSE);
+ }
+ }
+
+ if (!children_only)
+ render_element (svg_render, element, TRUE, display_element);
+ }
+ svg_render->render_element_tree_depth--;
+}
+
+static void
+render_element_tree_id (cairo_svg_glyph_render_t *svg_render,
+ const char *element_id)
+{
+ cairo_svg_element_t *glyph_element = NULL;
+
+ if (element_id)
+ glyph_element = lookup_element (svg_render, element_id);
+
+ if (glyph_element)
+ svg_render->graphics_state->mode = GS_NO_RENDER;
+ else
+ svg_render->graphics_state->mode = GS_RENDER;
+
+ render_element_tree (svg_render, svg_render->tree, glyph_element, TRUE);
+}
+
+cairo_status_t
+_cairo_render_svg_glyph (const char *svg_document,
+ unsigned long first_glyph,
+ unsigned long last_glyph,
+ unsigned long glyph,
+ double units_per_em,
+ FT_Color *palette,
+ int num_palette_entries,
+ cairo_t *cr)
+{
+ cairo_status_t status = CAIRO_STATUS_SUCCESS;
+
+ cairo_svg_glyph_render_t *svg_render = _cairo_malloc (sizeof (cairo_svg_glyph_render_t));
+ if (unlikely (svg_render == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ svg_render->tree = NULL;
+ svg_render->ids = _cairo_hash_table_create (_element_id_equal);
+ if (unlikely (svg_render->ids == NULL)) {
+ free (svg_render);
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ svg_render->debug = 0;
+ const char *s = getenv ("CAIRO_DEBUG_SVG_RENDER");
+ if (s) {
+ if (strlen (s) > 0)
+ svg_render->debug = atoi (s);
+ else
+ svg_render->debug = ERROR;
+ }
+
+ svg_render->cr = cr;
+ svg_render->units_per_em = units_per_em;
+ svg_render->foreground_color = cairo_pattern_reference (cairo_get_source (cr));
+ svg_render->build_pattern.paint_server = NULL;
+ svg_render->build_pattern.pattern = NULL;
+ svg_render->build_pattern.type = BUILD_PATTERN_NONE;
+ svg_render->render_element_tree_depth = 0;
+ svg_render->view_port_set = FALSE;
+ svg_render->num_palette_entries = num_palette_entries;
+ svg_render->palette = palette;
+
+ init_graphics_state (svg_render);
+
+ print_info (svg_render, "Glyph ID: %ld", glyph);
+ print_info (svg_render, "Palette Entries: %d", num_palette_entries);
+ print_info (svg_render, "Units per EM: %f", units_per_em);
+ print_info (svg_render, "SVG Document:\n%s\n", svg_document);
+
+ /* First parse elements into a tree and populate ids hash table */
+ if (!parse_svg (svg_render, svg_document)) {
+ print_error (svg_render, "Parse SVG document failed");
+ status = CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED;
+ goto cleanup;
+ }
+
+#if SVG_RENDER_PRINT_FUNCTIONS
+ printf("\nTREE\n");
+ if (svg_render->tree) {
+ print_element (svg_render->tree, TRUE, 0);
+ printf("\n");
+ }
+#endif
+
+ /* Next, render glyph */
+ if (first_glyph == last_glyph) {
+ /* Render whole document */
+ render_element_tree_id (svg_render, NULL);
+ } else {
+ /* Render element with id "glyphID" where ID is glyph number. */
+
+ char glyph_id[30];
+ snprintf(glyph_id, sizeof(glyph_id), "#glyph%ld", glyph);
+ render_element_tree_id (svg_render, glyph_id);
+ }
+
+ cleanup:
+ if (svg_render->build_pattern.pattern)
+ cairo_pattern_destroy (svg_render->build_pattern.pattern);
+
+ if (svg_render->tree)
+ free_elements (svg_render, svg_render->tree);
+
+ while (svg_render->graphics_state)
+ restore_graphics_state (svg_render);
+
+ cairo_pattern_destroy (svg_render->foreground_color);
+
+ /* The hash entry for each element with an id is removed by
+ * free_elements() */
+ _cairo_hash_table_destroy (svg_render->ids);
+
+ free (svg_render);
+
+ return status;
+}
+
+#ifdef DEBUG_SVG_RENDER
+
+/**
+ * _cairo_debug_svg_render:
+ *
+ * Debug function for cairo-svg-glyph-render.c. Allows invoking the renderer from outside
+ * cairo to test with SVG documents, and to facilitate comparison with librsvg rendering.
+ * The viewport is .
+ *
+ * @cr: render target
+ * @svg_document: SVG Document
+ * @element: element within svg_document to render (eg "#glyph8"), or NULL to render entire document.
+ * @debug_level: 0 - quiet, 1 - print errors, 2 - print warnings, 3 - info
+ * @return TRUE on success, ie no errors, FALSE if error
+ */
+cairo_bool_t
+_cairo_debug_svg_render (cairo_t *cr,
+ const char *svg_document,
+ const char *element,
+ double units_per_em,
+ int debug_level);
+
+cairo_bool_t
+_cairo_debug_svg_render (cairo_t *cr,
+ const char *svg_document,
+ const char *element,
+ double units_per_em,
+ int debug_level)
+{
+ return _cairo_render_svg_glyph (svg_document,
+ 1, 1, 1,
+ units_per_em,
+ NULL, 0,
+ cr) == CAIRO_STATUS_SUCCESS;
+}
+
+#endif /* DEBUG_SVG_RENDER */
+
+#endif /* HAVE_FT_SVG_DOCUMENT */
diff --git a/src/meson.build b/src/meson.build
index 0a14b3ba3..f8e2290f2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -130,6 +130,7 @@ cairo_feature_sources = {
],
'cairo-ft': [
'cairo-ft-font.c',
+ 'cairo-svg-glyph-render.c'
],
'cairo-xlib': [
commit 392423aba38c0934235f17a40130d2e66e4490b0
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Mon Jun 13 21:56:02 2022 +0930
Fix ink extents of recording surfaces within recording surfaces
diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index a118e338c..0e22b9aa9 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -926,6 +926,12 @@ _paint_return_success (void *surface,
const cairo_pattern_t *source,
const cairo_clip_t *clip)
{
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
return CAIRO_INT_STATUS_SUCCESS;
}
@@ -936,6 +942,18 @@ _mask_return_success (void *surface,
const cairo_pattern_t *mask,
const cairo_clip_t *clip)
{
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
+ if (mask->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) mask;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
return CAIRO_INT_STATUS_SUCCESS;
}
@@ -951,6 +969,12 @@ _stroke_return_success (void *surface,
cairo_antialias_t antialias,
const cairo_clip_t *clip)
{
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
return CAIRO_INT_STATUS_SUCCESS;
}
@@ -964,6 +988,12 @@ _fill_return_success (void *surface,
cairo_antialias_t antialias,
const cairo_clip_t *clip)
{
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
return CAIRO_INT_STATUS_SUCCESS;
}
@@ -976,6 +1006,12 @@ _show_glyphs_return_success (void *surface,
cairo_scaled_font_t *scaled_font,
const cairo_clip_t *clip)
{
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
+ return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+ }
+
return CAIRO_INT_STATUS_SUCCESS;
}
commit 640e2dadfc96801c12f673f523c7391d72df613f
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Fri May 6 21:17:25 2022 +0930
user fonts: ensure snap_x/y_scale are initialized
diff --git a/src/cairo-user-font.c b/src/cairo-user-font.c
index fd989eaa0..03002d603 100644
--- a/src/cairo-user-font.c
+++ b/src/cairo-user-font.c
@@ -559,6 +559,8 @@ _cairo_user_font_face_scaled_font_create (void *abstract_
{
double fixed_scale, x_scale, y_scale;
+ user_scaled_font->snap_x_scale = 1.0;
+ user_scaled_font->snap_y_scale = 1.0;
user_scaled_font->extent_scale = user_scaled_font->base.scale_inverse;
status = _cairo_matrix_compute_basis_scale_factors (&user_scaled_font->extent_scale,
&x_scale, &y_scale,
commit 7c5e2758a405b180c52eedc7119cb761162e4b6c
Author: Adrian Johnson <ajohnson at redneon.com>
Date: Mon May 2 21:30:53 2022 +0930
ft: move CAIRO_SCALED_GLYPH_INFO_METRICS into separate function
diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c
index b0a42b5ce..b60a57510 100644
--- a/src/cairo-ft-font.c
+++ b/src/cairo-ft-font.c
@@ -2621,20 +2621,153 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font,
return status;
}
+static cairo_int_status_t
+_cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font,
+ cairo_scaled_glyph_t *scaled_glyph,
+ cairo_scaled_glyph_info_t info,
+ FT_Face face,
+ cairo_bool_t vertical_layout,
+ int load_flags)
+{
+ cairo_status_t status = CAIRO_STATUS_SUCCESS;
+ cairo_ft_unscaled_font_t *unscaled = scaled_font->unscaled;
+ FT_Glyph_Metrics *metrics;
+ double x_factor, y_factor;
+ FT_GlyphSlot glyph;
+ cairo_bool_t scaled_glyph_loaded = FALSE;
+ cairo_text_extents_t fs_metrics;
+
+ cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
+
+ /* The font metrics for color glyphs should be the same as the
+ * outline glyphs. But just in case there aren't, request the
+ * color or outline metrics based on the font option and if
+ * the font has color.
+ */
+ int color_flag = 0;
+#ifdef FT_LOAD_COLOR
+ if (unscaled->have_color && scaled_font->base.options.color_mode != CAIRO_COLOR_MODE_NO_COLOR)
+ color_flag = FT_LOAD_COLOR;
+#endif
+ status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
+ scaled_glyph,
+ face,
+ load_flags | color_flag,
+ !hint_metrics,
+ vertical_layout);
+ if (unlikely (status))
+ return status;
+
+ glyph = face->glyph;
+ scaled_glyph_loaded = hint_metrics;
+
+ /*
+ * Compute font-space metrics
+ */
+ metrics = &glyph->metrics;
+
+ if (unscaled->x_scale == 0)
+ x_factor = 0;
+ else
+ x_factor = 1 / unscaled->x_scale;
+
+ if (unscaled->y_scale == 0)
+ y_factor = 0;
+ else
+ y_factor = 1 / unscaled->y_scale;
+
+ /*
+ * Note: Y coordinates of the horizontal bearing need to be negated.
+ *
+ * Scale metrics back to glyph space from the scaled glyph space returned
+ * by FreeType
+ *
+ * If we want hinted metrics but aren't asking for hinted glyphs from
+ * FreeType, then we need to do the metric hinting ourselves.
+ */
+
+ if (hint_metrics && (load_flags & FT_LOAD_NO_HINTING))
+ {
+ FT_Pos x1, x2;
+ FT_Pos y1, y2;
+ FT_Pos advance;
+
+ if (!vertical_layout) {
+ x1 = (metrics->horiBearingX) & -64;
+ x2 = (metrics->horiBearingX + metrics->width + 63) & -64;
+ y1 = (-metrics->horiBearingY) & -64;
+ y2 = (-metrics->horiBearingY + metrics->height + 63) & -64;
+
+ advance = ((metrics->horiAdvance + 32) & -64);
+
+ fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
+ fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
+
+ fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
+ fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
+
+ fs_metrics.x_advance = DOUBLE_FROM_26_6 (advance) * x_factor;
+ fs_metrics.y_advance = 0;
+ } else {
+ x1 = (metrics->vertBearingX) & -64;
+ x2 = (metrics->vertBearingX + metrics->width + 63) & -64;
+ y1 = (metrics->vertBearingY) & -64;
+ y2 = (metrics->vertBearingY + metrics->height + 63) & -64;
+
+ advance = ((metrics->vertAdvance + 32) & -64);
+
+ fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
+ fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
+
+ fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
+ fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
+
+ fs_metrics.x_advance = 0;
+ fs_metrics.y_advance = DOUBLE_FROM_26_6 (advance) * y_factor;
+ }
+ } else {
+ fs_metrics.width = DOUBLE_FROM_26_6 (metrics->width) * x_factor;
+ fs_metrics.height = DOUBLE_FROM_26_6 (metrics->height) * y_factor;
+
+ if (!vertical_layout) {
+ fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->horiBearingX) * x_factor;
+ fs_metrics.y_bearing = DOUBLE_FROM_26_6 (-metrics->horiBearingY) * y_factor;
+
+ if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
+ fs_metrics.x_advance = DOUBLE_FROM_26_6 (metrics->horiAdvance) * x_factor;
+ else
+ fs_metrics.x_advance = DOUBLE_FROM_16_16 (glyph->linearHoriAdvance) * x_factor;
+ fs_metrics.y_advance = 0 * y_factor;
+ } else {
+ fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingX) * x_factor;
+ fs_metrics.y_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingY) * y_factor;
+
+ fs_metrics.x_advance = 0 * x_factor;
+ if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
+ fs_metrics.y_advance = DOUBLE_FROM_26_6 (metrics->vertAdvance) * y_factor;
+ else
+ fs_metrics.y_advance = DOUBLE_FROM_16_16 (glyph->linearVertAdvance) * y_factor;
+ }
+ }
+
+ _cairo_scaled_glyph_set_metrics (scaled_glyph,
+ &scaled_font->base,
+ &fs_metrics);
+
+ return status;
+}
+
static cairo_int_status_t
_cairo_ft_scaled_glyph_init (void *abstract_font,
cairo_scaled_glyph_t *scaled_glyph,
cairo_scaled_glyph_info_t info,
const cairo_color_t *foreground_color)
{
- cairo_text_extents_t fs_metrics;
cairo_ft_scaled_font_t *scaled_font = abstract_font;
cairo_ft_unscaled_font_t *unscaled = scaled_font->unscaled;
FT_GlyphSlot glyph;
FT_Face face;
int load_flags = scaled_font->ft_options.load_flags;
- FT_Glyph_Metrics *metrics;
- double x_factor, y_factor;
cairo_bool_t vertical_layout = FALSE;
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_bool_t scaled_glyph_loaded = FALSE;
@@ -2662,123 +2795,14 @@ _cairo_ft_scaled_glyph_init (void *abstract_font,
}
if (info & CAIRO_SCALED_GLYPH_INFO_METRICS) {
-
- cairo_bool_t hint_metrics = scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF;
-
- /* The font metrics for color glyphs should be the same as the
- * outline glyphs. But just in case there aren't, request the
- * color or outline metrics based on the font option and if
- * the font has color.
- */
- int color_flag = 0;
-#ifdef FT_LOAD_COLOR
- if (unscaled->have_color && scaled_font->base.options.color_mode != CAIRO_COLOR_MODE_NO_COLOR)
- color_flag = FT_LOAD_COLOR;
-#endif
- status = _cairo_ft_scaled_glyph_load_glyph (scaled_font,
- scaled_glyph,
- face,
- load_flags | color_flag,
- !hint_metrics,
- vertical_layout);
+ status = _cairo_ft_scaled_glyph_init_metrics (scaled_font,
+ scaled_glyph,
+ info,
+ face,
+ vertical_layout,
+ load_flags);
if (unlikely (status))
goto FAIL;
-
- glyph = face->glyph;
- scaled_glyph_loaded = hint_metrics;
-
- /*
- * Compute font-space metrics
- */
- metrics = &glyph->metrics;
-
- if (unscaled->x_scale == 0)
- x_factor = 0;
- else
- x_factor = 1 / unscaled->x_scale;
-
- if (unscaled->y_scale == 0)
- y_factor = 0;
- else
- y_factor = 1 / unscaled->y_scale;
-
- /*
- * Note: Y coordinates of the horizontal bearing need to be negated.
- *
- * Scale metrics back to glyph space from the scaled glyph space returned
- * by FreeType
- *
- * If we want hinted metrics but aren't asking for hinted glyphs from
- * FreeType, then we need to do the metric hinting ourselves.
- */
-
- if (hint_metrics && (load_flags & FT_LOAD_NO_HINTING))
- {
- FT_Pos x1, x2;
- FT_Pos y1, y2;
- FT_Pos advance;
-
- if (!vertical_layout) {
- x1 = (metrics->horiBearingX) & -64;
- x2 = (metrics->horiBearingX + metrics->width + 63) & -64;
- y1 = (-metrics->horiBearingY) & -64;
- y2 = (-metrics->horiBearingY + metrics->height + 63) & -64;
-
- advance = ((metrics->horiAdvance + 32) & -64);
-
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
-
- fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
-
- fs_metrics.x_advance = DOUBLE_FROM_26_6 (advance) * x_factor;
- fs_metrics.y_advance = 0;
- } else {
- x1 = (metrics->vertBearingX) & -64;
- x2 = (metrics->vertBearingX + metrics->width + 63) & -64;
- y1 = (metrics->vertBearingY) & -64;
- y2 = (metrics->vertBearingY + metrics->height + 63) & -64;
-
- advance = ((metrics->vertAdvance + 32) & -64);
-
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (x1) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (y1) * y_factor;
-
- fs_metrics.width = DOUBLE_FROM_26_6 (x2 - x1) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (y2 - y1) * y_factor;
-
- fs_metrics.x_advance = 0;
- fs_metrics.y_advance = DOUBLE_FROM_26_6 (advance) * y_factor;
- }
- } else {
- fs_metrics.width = DOUBLE_FROM_26_6 (metrics->width) * x_factor;
- fs_metrics.height = DOUBLE_FROM_26_6 (metrics->height) * y_factor;
-
- if (!vertical_layout) {
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->horiBearingX) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (-metrics->horiBearingY) * y_factor;
-
- if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
- fs_metrics.x_advance = DOUBLE_FROM_26_6 (metrics->horiAdvance) * x_factor;
- else
- fs_metrics.x_advance = DOUBLE_FROM_16_16 (glyph->linearHoriAdvance) * x_factor;
- fs_metrics.y_advance = 0 * y_factor;
- } else {
- fs_metrics.x_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingX) * x_factor;
- fs_metrics.y_bearing = DOUBLE_FROM_26_6 (metrics->vertBearingY) * y_factor;
-
- fs_metrics.x_advance = 0 * x_factor;
- if (hint_metrics || glyph->format != FT_GLYPH_FORMAT_OUTLINE)
- fs_metrics.y_advance = DOUBLE_FROM_26_6 (metrics->vertAdvance) * y_factor;
- else
- fs_metrics.y_advance = DOUBLE_FROM_16_16 (glyph->linearVertAdvance) * y_factor;
- }
- }
-
- _cairo_scaled_glyph_set_metrics (scaled_glyph,
- &scaled_font->base,
- &fs_metrics);
}
if (info & CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE) {
More information about the cairo-commit
mailing list