| /* | |
| * Copyright © 2026 Google, Inc. | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| */ | |
| #include "hb-test.h" | |
| #include "hb-subset-test.h" | |
| #include <math.h> | |
| static void | |
| draw_move_to (hb_draw_funcs_t *funcs HB_UNUSED, void *data, | |
| hb_draw_state_t *state HB_UNUSED, float x, float y, | |
| void *user_data HB_UNUSED) | |
| { g_string_append_printf ((GString *) data, "M%.9g,%.9g", x, y); } | |
| static void | |
| draw_line_to (hb_draw_funcs_t *funcs HB_UNUSED, void *data, | |
| hb_draw_state_t *state HB_UNUSED, float x, float y, | |
| void *user_data HB_UNUSED) | |
| { g_string_append_printf ((GString *) data, "L%.9g,%.9g", x, y); } | |
| static void | |
| draw_quadratic_to (hb_draw_funcs_t *funcs HB_UNUSED, void *data, | |
| hb_draw_state_t *state HB_UNUSED, | |
| float cx, float cy, float x, float y, | |
| void *user_data HB_UNUSED) | |
| { g_string_append_printf ((GString *) data, "Q%.9g,%.9g %.9g,%.9g", cx, cy, x, y); } | |
| static void | |
| draw_cubic_to (hb_draw_funcs_t *funcs HB_UNUSED, void *data, | |
| hb_draw_state_t *state HB_UNUSED, | |
| float c1x, float c1y, float c2x, float c2y, float x, float y, | |
| void *user_data HB_UNUSED) | |
| { | |
| g_string_append_printf ((GString *) data, "C%.9g,%.9g %.9g,%.9g %.9g,%.9g", | |
| c1x, c1y, c2x, c2y, x, y); | |
| } | |
| static void | |
| draw_close_path (hb_draw_funcs_t *funcs HB_UNUSED, void *data, | |
| hb_draw_state_t *state HB_UNUSED, | |
| void *user_data HB_UNUSED) | |
| { g_string_append_c ((GString *) data, 'Z'); } | |
| static char * | |
| draw_glyph (hb_font_t *font, hb_codepoint_t gid) | |
| { | |
| hb_draw_funcs_t *funcs = hb_draw_funcs_create (); | |
| hb_draw_funcs_set_move_to_func (funcs, draw_move_to, NULL, NULL); | |
| hb_draw_funcs_set_line_to_func (funcs, draw_line_to, NULL, NULL); | |
| hb_draw_funcs_set_quadratic_to_func (funcs, draw_quadratic_to, NULL, NULL); | |
| hb_draw_funcs_set_cubic_to_func (funcs, draw_cubic_to, NULL, NULL); | |
| hb_draw_funcs_set_close_path_func (funcs, draw_close_path, NULL, NULL); | |
| GString *path = g_string_new (NULL); | |
| hb_font_draw_glyph (font, gid, funcs, path); | |
| hb_draw_funcs_destroy (funcs); | |
| return g_string_free (path, false); | |
| } | |
| static char * | |
| draw_unicode_at (hb_face_t *face, hb_codepoint_t unicode, | |
| const hb_variation_t *variations, unsigned variation_count) | |
| { | |
| hb_font_t *font = hb_font_create (face); | |
| hb_font_set_variations (font, variations, variation_count); | |
| hb_codepoint_t gid; | |
| g_assert_true (hb_font_get_nominal_glyph (font, unicode, &gid)); | |
| char *path = draw_glyph (font, gid); | |
| hb_font_destroy (font); | |
| return path; | |
| } | |
| static char * | |
| draw_unicode (hb_face_t *face, hb_codepoint_t unicode) | |
| { return draw_unicode_at (face, unicode, NULL, 0); } | |
| static void | |
| assert_paths_close (const char *a, const char *b) | |
| { | |
| /* Baking gvar deltas changes floating-point summation order. */ | |
| while (*a && *b) | |
| { | |
| char *a_end, *b_end; | |
| double x = g_ascii_strtod (a, &a_end); | |
| double y = g_ascii_strtod (b, &b_end); | |
| if (a_end != a || b_end != b) | |
| { | |
| g_assert_true (a_end != a && b_end != b); | |
| g_assert_cmpfloat (fabs (x - y), <, 0.001); | |
| a = a_end; | |
| b = b_end; | |
| } | |
| else | |
| g_assert_cmpint (*a++, ==, *b++); | |
| } | |
| g_assert_cmpint (*a, ==, *b); | |
| } | |
| static void | |
| assert_has_varc (hb_face_t *face, hb_bool_t expected) | |
| { | |
| hb_blob_t *blob = hb_face_reference_table (face, HB_TAG ('V','A','R','C')); | |
| g_assert_cmpint (hb_blob_get_length (blob) != 0, ==, expected); | |
| hb_blob_destroy (blob); | |
| } | |
| static unsigned | |
| varc_length (hb_face_t *face) | |
| { | |
| hb_blob_t *blob = hb_face_reference_table (face, HB_TAG ('V','A','R','C')); | |
| unsigned length = hb_blob_get_length (blob); | |
| hb_blob_destroy (blob); | |
| return length; | |
| } | |
| static hb_face_t * | |
| subset_varc (hb_face_t *face, hb_codepoint_t unicode) | |
| { | |
| hb_set_t *unicodes = hb_set_create (); | |
| hb_set_add (unicodes, unicode); | |
| hb_face_t *subset = hb_subset_test_create_subset ( | |
| face, hb_subset_test_create_input (unicodes)); | |
| hb_set_destroy (unicodes); | |
| return subset; | |
| } | |
| static void | |
| test_subset_varc_closure_and_remap (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_face_t *ac00 = subset_varc (face, 0xAC00); | |
| g_assert_cmpuint (hb_face_get_glyph_count (ac00), ==, 6); | |
| assert_has_varc (ac00, true); | |
| g_assert_cmpuint (varc_length (ac00), <, varc_length (face)); | |
| hb_face_t *ac01 = subset_varc (face, 0xAC01); | |
| g_assert_cmpuint (hb_face_get_glyph_count (ac01), ==, 8); | |
| assert_has_varc (ac01, true); | |
| g_assert_cmpuint (varc_length (ac01), <, varc_length (face)); | |
| hb_face_destroy (ac01); | |
| hb_face_destroy (ac00); | |
| hb_face_destroy (face); | |
| } | |
| static void | |
| test_subset_varc_draw_equivalence (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_face_t *subset = subset_varc (face, 0xAC01); | |
| char *source_path = draw_unicode (face, 0xAC01); | |
| char *subset_path = draw_unicode (subset, 0xAC01); | |
| hb_variation_t variations[] = { | |
| {HB_TAG ('w','g','h','t'), 840.3f}, | |
| {HB_TAG ('o','p','s','z'), 1.f}, | |
| }; | |
| char *varied_source_path = draw_unicode_at (face, 0xAC01, | |
| variations, | |
| G_N_ELEMENTS (variations)); | |
| char *varied_subset_path = draw_unicode_at (subset, 0xAC01, | |
| variations, | |
| G_N_ELEMENTS (variations)); | |
| g_assert_cmpstr (source_path, !=, ""); | |
| g_assert_cmpstr (subset_path, ==, source_path); | |
| g_assert_cmpstr (varied_source_path, !=, source_path); | |
| g_assert_cmpstr (varied_subset_path, ==, varied_source_path); | |
| g_free (varied_subset_path); | |
| g_free (varied_source_path); | |
| g_free (subset_path); | |
| g_free (source_path); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| static void | |
| test_subset_varc_condition_closure (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac01-conditional.ttf"); | |
| hb_face_t *subset = subset_varc (face, 0xAC01); | |
| /* Closure conservatively retains components regardless of their condition. */ | |
| g_assert_cmpuint (hb_face_get_glyph_count (subset), ==, 8); | |
| assert_has_varc (subset, true); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| static void | |
| test_subset_varc_mismatched_coverage_and_records (void) | |
| { | |
| const char *fonts[] = { | |
| "fonts/varc-coverage-longer.ttf", | |
| "fonts/varc-records-longer.ttf", | |
| }; | |
| for (unsigned i = 0; i < G_N_ELEMENTS (fonts); i++) | |
| { | |
| hb_face_t *face = hb_test_open_font_file (fonts[i]); | |
| hb_face_t *subset = subset_varc (face, 0xAC00); | |
| g_assert_nonnull (subset); | |
| g_assert_cmpuint (hb_face_get_glyph_count (subset), ==, 6); | |
| assert_has_varc (subset, true); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| } | |
| static void | |
| test_subset_varc_no_variation_index (void) | |
| { | |
| const struct { | |
| const char *font; | |
| hb_codepoint_t unicode; | |
| unsigned glyph_count; | |
| } tests[] = { | |
| {"fonts/varc-no-variation-index.ttf", 0xAC00, 6}, | |
| {"fonts/varc-condition-no-variation-index.ttf", 0xAC01, 8}, | |
| }; | |
| for (unsigned i = 0; i < G_N_ELEMENTS (tests); i++) | |
| { | |
| hb_face_t *face = hb_test_open_font_file (tests[i].font); | |
| hb_face_t *subset = subset_varc (face, tests[i].unicode); | |
| g_assert_nonnull (subset); | |
| g_assert_cmpuint (hb_face_get_glyph_count (subset), ==, tests[i].glyph_count); | |
| assert_has_varc (subset, true); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| } | |
| static void | |
| test_subset_varc_retain_gids (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_unicode_set (input), 0xAC00); | |
| hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS); | |
| hb_face_t *subset = hb_subset_test_create_subset (face, input); | |
| g_assert_cmpuint (hb_face_get_glyph_count (subset), ==, 7); | |
| assert_has_varc (subset, true); | |
| hb_font_t *source_font = hb_font_create (face); | |
| hb_font_t *subset_font = hb_font_create (subset); | |
| hb_codepoint_t old_gid; | |
| hb_codepoint_t subset_gid; | |
| g_assert_true (hb_font_get_nominal_glyph (source_font, 0xAC00, &old_gid)); | |
| g_assert_true (hb_font_get_nominal_glyph (subset_font, 0xAC00, &subset_gid)); | |
| g_assert_cmpuint (subset_gid, ==, old_gid); | |
| char *source_path = draw_glyph (source_font, old_gid); | |
| char *subset_path = draw_glyph (subset_font, old_gid); | |
| g_assert_cmpstr (source_path, !=, ""); | |
| g_assert_cmpstr (subset_path, ==, source_path); | |
| g_free (subset_path); | |
| g_free (source_path); | |
| hb_font_destroy (subset_font); | |
| hb_font_destroy (source_font); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| static void | |
| test_subset_varc_fails_when_instancing (void) | |
| { | |
| const char *fonts[] = { | |
| "fonts/varc-unrelated-axis.ttf", | |
| "fonts/varc-unrelated-axis-avar2.ttf", | |
| }; | |
| const hb_tag_t tags[] = { | |
| HB_TAG ('w','g','h','t'), /* MultiVarStore */ | |
| HB_TAG ('o','p','s','z'), /* MultiVarStore */ | |
| HB_TAG ('0','0','0','0'), /* Component coordinates */ | |
| HB_TAG ('C','O','N','D'), /* Nested conditions only */ | |
| }; | |
| for (unsigned f = 0; f < G_N_ELEMENTS (fonts); f++) | |
| { | |
| hb_face_t *face = hb_test_open_font_file (fonts[f]); | |
| for (unsigned i = 0; i < G_N_ELEMENTS (tags); i++) | |
| for (unsigned restrict_range = 0; restrict_range < 2; restrict_range++) | |
| { | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_unicode_set (input), 0xAC01); | |
| hb_ot_var_axis_info_t axis; | |
| g_assert_true (hb_ot_var_find_axis_info (face, tags[i], &axis)); | |
| if (restrict_range) | |
| g_assert_true (hb_subset_input_set_axis_range ( | |
| input, face, tags[i], axis.min_value, | |
| (axis.default_value + axis.max_value) / 2, axis.default_value)); | |
| else | |
| g_assert_true (hb_subset_input_pin_axis_to_default (input, face, tags[i])); | |
| g_assert_null (hb_subset_or_fail (face, input)); | |
| hb_subset_input_destroy (input); | |
| } | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_unicode_set (input), 0xAC01); | |
| g_assert_true (hb_subset_input_pin_all_axes_to_default (input, face)); | |
| g_assert_null (hb_subset_or_fail (face, input)); | |
| hb_subset_input_destroy (input); | |
| hb_face_destroy (face); | |
| } | |
| } | |
| static void | |
| test_subset_varc_instance_unrelated_axis (void) | |
| { | |
| const char *fonts[] = { | |
| "fonts/varc-unrelated-axis.ttf", | |
| "fonts/varc-unrelated-axis-avar2.ttf", | |
| }; | |
| hb_tag_t dummy = HB_TAG ('D','U','M','Y'); | |
| for (unsigned f = 0; f < G_N_ELEMENTS (fonts); f++) | |
| { | |
| hb_face_t *face = hb_test_open_font_file (fonts[f]); | |
| for (unsigned mode = 0; mode < 3; mode++) | |
| { | |
| hb_bool_t restrict_range = mode == 1; | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_unicode_set (input), 0xAC01); | |
| if (mode == 2) | |
| hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS); | |
| if (restrict_range) | |
| g_assert_true (hb_subset_input_set_axis_range (input, face, dummy, 0, 1, 0.5f)); | |
| else | |
| g_assert_true (hb_subset_input_pin_axis_location (input, face, dummy, 0.5f)); | |
| hb_face_t *subset = hb_subset_test_create_subset (face, input); | |
| assert_has_varc (subset, true); | |
| g_assert_cmpuint (hb_ot_var_get_axis_count (subset), ==, | |
| hb_ot_var_get_axis_count (face) - !restrict_range); | |
| hb_ot_var_axis_info_t axis; | |
| g_assert_cmpint (hb_ot_var_find_axis_info (subset, dummy, &axis), ==, restrict_range); | |
| if (restrict_range) | |
| { | |
| g_assert_cmpfloat (axis.min_value, ==, 0); | |
| g_assert_cmpfloat (axis.default_value, ==, 0.5f); | |
| g_assert_cmpfloat (axis.max_value, ==, 1); | |
| } | |
| g_assert_true (hb_ot_var_find_axis_info (subset, HB_TAG ('w','g','h','t'), &axis)); | |
| g_assert_cmpuint (axis.axis_index, ==, restrict_range ? 2 : 1); | |
| for (unsigned sample = 0; sample < 8; sample++) | |
| { | |
| hb_variation_t variations[] = { | |
| {dummy, restrict_range && (sample & 1) ? 1.f : 0.5f}, | |
| {HB_TAG ('w','g','h','t'), sample & 2 ? 840.3f : 356.5f}, | |
| {HB_TAG ('o','p','s','z'), sample & 1 ? 1.f : 0.f}, | |
| {HB_TAG ('C','O','N','D'), sample & 4 ? 1.f : 0.f}, | |
| }; | |
| char *source_path = draw_unicode_at (face, 0xAC01, variations, | |
| G_N_ELEMENTS (variations)); | |
| char *subset_path = draw_unicode_at (subset, 0xAC01, variations, | |
| G_N_ELEMENTS (variations)); | |
| g_assert_cmpstr (source_path, !=, ""); | |
| assert_paths_close (subset_path, source_path); | |
| g_free (subset_path); | |
| g_free (source_path); | |
| } | |
| hb_face_destroy (subset); | |
| } | |
| hb_face_destroy (face); | |
| } | |
| } | |
| static void | |
| test_subset_varc_instance_without_varc_glyphs (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_glyph_set (input), 0); | |
| g_assert_true (hb_subset_input_pin_axis_to_default ( | |
| input, face, HB_TAG ('w','g','h','t'))); | |
| hb_face_t *subset = hb_subset_test_create_subset (face, input); | |
| assert_has_varc (subset, false); | |
| g_assert_cmpuint (hb_ot_var_get_axis_count (subset), ==, | |
| hb_ot_var_get_axis_count (face) - 1); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| static void | |
| test_subset_varc_can_be_explicitly_dropped_when_instancing (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_set_t *unicodes = hb_set_create (); | |
| hb_set_add (unicodes, 0xAC00); | |
| hb_subset_input_t *input = hb_subset_test_create_input (unicodes); | |
| hb_set_destroy (unicodes); | |
| hb_set_add (hb_subset_input_set (input, HB_SUBSET_SETS_DROP_TABLE_TAG), | |
| HB_TAG ('V','A','R','C')); | |
| g_assert_true (hb_subset_input_pin_axis_location (input, face, | |
| HB_TAG ('w','g','h','t'), 400)); | |
| hb_face_t *subset = hb_subset_test_create_subset (face, input); | |
| assert_has_varc (subset, false); | |
| hb_face_destroy (subset); | |
| hb_face_destroy (face); | |
| } | |
| static hb_subset_input_t * | |
| create_varc_no_subset_input (hb_bool_t retain_gids) | |
| { | |
| hb_subset_input_t *input = hb_subset_input_create_or_fail (); | |
| hb_set_add (hb_subset_input_unicode_set (input), 0xAC00); | |
| hb_set_add (hb_subset_input_set (input, HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG), | |
| HB_TAG ('V','A','R','C')); | |
| if (retain_gids) | |
| hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS); | |
| return input; | |
| } | |
| static void | |
| test_subset_varc_passthrough_requires_retained_gids (void) | |
| { | |
| hb_face_t *face = hb_test_open_font_file ("fonts/varc-ac00-ac01.ttf"); | |
| hb_subset_input_t *input = create_varc_no_subset_input (false); | |
| hb_face_t *subset = hb_subset_or_fail (face, input); | |
| g_assert_null (subset); | |
| hb_subset_input_destroy (input); | |
| input = create_varc_no_subset_input (true); | |
| subset = hb_subset_test_create_subset (face, input); | |
| assert_has_varc (subset, true); | |
| hb_face_destroy (subset); | |
| input = create_varc_no_subset_input (true); | |
| /* Even an unrelated pin cannot renumber axes in a passthrough table. */ | |
| g_assert_true (hb_subset_input_pin_axis_to_default ( | |
| input, face, HB_TAG ('0','0','0','5'))); | |
| g_assert_null (hb_subset_or_fail (face, input)); | |
| hb_subset_input_destroy (input); | |
| hb_face_destroy (face); | |
| } | |
| int | |
| main (int argc, char **argv) | |
| { | |
| hb_test_init (&argc, &argv); | |
| hb_test_add (test_subset_varc_closure_and_remap); | |
| hb_test_add (test_subset_varc_draw_equivalence); | |
| hb_test_add (test_subset_varc_condition_closure); | |
| hb_test_add (test_subset_varc_mismatched_coverage_and_records); | |
| hb_test_add (test_subset_varc_no_variation_index); | |
| hb_test_add (test_subset_varc_retain_gids); | |
| hb_test_add (test_subset_varc_fails_when_instancing); | |
| hb_test_add (test_subset_varc_instance_unrelated_axis); | |
| hb_test_add (test_subset_varc_instance_without_varc_glyphs); | |
| hb_test_add (test_subset_varc_can_be_explicitly_dropped_when_instancing); | |
| hb_test_add (test_subset_varc_passthrough_requires_retained_gids); | |
| return hb_test_run (); | |
| } |