blob: b31d2e94a63d9f1234f9233ee8ff0c61a7f37ea7 [file]
/*
* 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 ();
}