| /* |
| * 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" |
| |
| 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_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) |
| { |
| 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); |
| |
| g_assert_true (hb_subset_input_pin_axis_location (input, face, |
| HB_TAG ('w','g','h','t'), 400)); |
| hb_face_t *subset = hb_subset_or_fail (face, input); |
| g_assert_null (subset); |
| |
| hb_subset_input_destroy (input); |
| 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); |
| 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_can_be_explicitly_dropped_when_instancing); |
| hb_test_add (test_subset_varc_passthrough_requires_retained_gids); |
| return hb_test_run (); |
| } |