blob: 2a4bb520f11ffd8b07031036384e19d92cca76d8 [file]
/*
* Copyright © 2018 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.
*
* Google Author(s): Garret Rieger
*/
#include "hb-test.h"
#include "hb-subset-test.h"
/* Unit tests for hb-subset-glyf.h */
static void
test_subset_32_tables (void)
{
hb_face_t *face = hb_test_open_font_file ("../fuzzing/fonts/oom-6ef8c96d3710262511bcc730dce9c00e722cb653");
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
hb_face_t *subset;
hb_set_add (codepoints, 'a');
hb_set_add (codepoints, 'b');
hb_set_add (codepoints, 'c');
subset = hb_subset_or_fail (face, input);
g_assert_true (!subset);
hb_subset_input_destroy (input);
hb_face_destroy (subset);
hb_face_destroy (face);
}
static void
test_subset_no_inf_loop (void)
{
hb_face_t *face = hb_test_open_font_file ("../fuzzing/fonts/clusterfuzz-testcase-minimized-hb-subset-fuzzer-5521982557782016");
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
hb_face_t *subset;
hb_set_add (codepoints, 'a');
hb_set_add (codepoints, 'b');
hb_set_add (codepoints, 'c');
subset = hb_subset_or_fail (face, input);
g_assert_true (!subset);
hb_subset_input_destroy (input);
hb_face_destroy (subset);
hb_face_destroy (face);
}
static void
test_subset_crash (void)
{
hb_face_t *face = hb_test_open_font_file ("../fuzzing/fonts/crash-4b60576767ee4d9fe1cc10959d89baf73d4e8249");
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
hb_face_t *subset;
hb_set_add (codepoints, 'a');
hb_set_add (codepoints, 'b');
hb_set_add (codepoints, 'c');
subset = hb_subset_or_fail (face, input);
g_assert_true (!subset);
hb_subset_input_destroy (input);
hb_face_destroy (subset);
hb_face_destroy (face);
}
#ifndef HB_NO_VAR
static void
assert_lookup_variations_shape (hb_face_t *face,
const int *coords,
unsigned coord_count,
const hb_codepoint_t *expected)
{
hb_font_t *font = hb_font_create (face);
hb_font_set_var_coords_normalized (font, coords, coord_count);
hb_buffer_t *buffer = hb_buffer_create ();
hb_buffer_add_utf8 (buffer, "ABCDEF", -1, 0, -1);
hb_buffer_guess_segment_properties (buffer);
hb_shape (font, buffer, NULL, 0);
unsigned length;
const hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &length);
g_assert_cmpuint (length, ==, 6);
for (unsigned i = 0; i < length; i++)
g_assert_cmpuint (info[i].codepoint, ==, expected[i]);
hb_buffer_destroy (buffer);
hb_font_destroy (font);
}
static void
test_subset_lookup_variations (void)
{
hb_face_t *face = hb_test_open_font_file (
"../shape/data/in-house/fonts/4e9f0bc6a8f25b5fd3547bbc17423ce8cedb915f.ttf");
const hb_codepoint_t expected_all[] = {2, 4, 6, 8, 9, 10};
const hb_codepoint_t expected_test_08_dumy_08[] = {2, 3, 6, 8, 9, 10};
const hb_codepoint_t expected_test_03_dumy_0[] = {2, 4, 5, 7, 8, 11};
const hb_codepoint_t expected_test_03_dumy_08[] = {2, 3, 6, 7, 8, 11};
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
hb_set_t *features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG ('l', 'i', 'g', 'a'));
hb_face_t *subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
int coords[] = {13107}; /* 0.8 */
assert_lookup_variations_shape (subset, coords, 1, expected_all);
hb_face_destroy (subset);
hb_subset_input_destroy (input);
input = hb_subset_input_create_or_fail ();
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG ('l', 'i', 'g', 'a'));
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('T', 'E', 'S', 'T'), 0.8f));
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('D', 'U', 'M', 'Y'), 0.f));
subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
assert_lookup_variations_shape (subset, NULL, 0, expected_all);
hb_face_destroy (subset);
hb_subset_input_destroy (input);
input = hb_subset_input_create_or_fail ();
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG ('l', 'i', 'g', 'a'));
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('T', 'E', 'S', 'T'), 0.8f));
subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
coords[0] = 0;
assert_lookup_variations_shape (subset, coords, 1, expected_all);
coords[0] = 13107; /* DUMY=0.8 */
assert_lookup_variations_shape (
subset, coords, 1, expected_test_08_dumy_08);
hb_face_destroy (subset);
hb_subset_input_destroy (input);
input = hb_subset_input_create_or_fail ();
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG ('l', 'i', 'g', 'a'));
hb_set_add (features, HB_TAG ('r', 'l', 'i', 'g'));
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('T', 'E', 'S', 'T'), 0.3f));
subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
coords[0] = 0;
assert_lookup_variations_shape (
subset, coords, 1, expected_test_03_dumy_0);
coords[0] = 13107; /* DUMY=0.8 */
assert_lookup_variations_shape (
subset, coords, 1, expected_test_03_dumy_08);
hb_face_destroy (subset);
hb_subset_input_destroy (input);
hb_face_destroy (face);
}
static void
assert_condition_instance_matches (hb_face_t *face, hb_face_t *subset,
const int *coords,
const int *subset_coords,
unsigned subset_coord_count)
{
hb_font_t *fonts[] = {hb_font_create (face), hb_font_create (subset)};
hb_buffer_t *buffers[] = {hb_buffer_create (), hb_buffer_create ()};
hb_font_set_var_coords_normalized (fonts[0], coords, 2);
hb_font_set_var_coords_normalized (fonts[1], subset_coords, subset_coord_count);
for (unsigned i = 0; i < 2; i++)
{
hb_buffer_add_utf8 (buffers[i], "ABCDEF", -1, 0, -1);
hb_buffer_guess_segment_properties (buffers[i]);
hb_shape (fonts[i], buffers[i], NULL, 0);
}
unsigned length, subset_length;
const hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffers[0], &length);
const hb_glyph_info_t *subset_info = hb_buffer_get_glyph_infos (buffers[1], &subset_length);
const hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffers[0], NULL);
const hb_glyph_position_t *subset_pos = hb_buffer_get_glyph_positions (buffers[1], NULL);
g_assert_cmpuint (length, ==, 6);
g_assert_cmpuint (subset_length, ==, length);
for (unsigned i = 0; i < length; i++)
{
g_assert_cmpuint (subset_info[i].codepoint, ==, info[i].codepoint);
g_assert_cmpint (subset_pos[i].x_advance, ==, pos[i].x_advance);
/* Numeric positioning deltas can round by one unit during instancing. */
g_assert_cmpint (abs (subset_pos[i].x_offset - pos[i].x_offset), <=, 1);
}
for (unsigned i = 0; i < 2; i++)
{
hb_buffer_destroy (buffers[i]);
hb_font_destroy (fonts[i]);
}
}
static void
test_subset_feature_variation_universal (void)
{
const char *filenames[] = {
"fonts/feature-variation-universal.ttf",
"fonts/feature-variation-universal-prefix.ttf"
};
const int widths[] = {-16384, 0, 9830, 13107, 16384};
for (unsigned f = 0; f < G_N_ELEMENTS (filenames); f++)
{
hb_face_t *face = hb_test_open_font_file (filenames[f]);
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('T', 'E', 'S', 'T'), 0.3f));
hb_face_t *subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
for (unsigned i = 0; i < G_N_ELEMENTS (widths); i++)
{
int coords[] = {4915, widths[i]};
assert_condition_instance_matches (face, subset, coords, &widths[i], 1);
}
hb_face_destroy (subset);
hb_subset_input_destroy (input);
hb_face_destroy (face);
}
}
static void
test_subset_lookup_variation_fractional_value (void)
{
hb_face_t *face = hb_test_open_font_file (
"../shape/data/in-house/fonts/4e9f0bc6a8f25b5fd3547bbc17423ce8cedb915f.ttf");
const float locations[] = {0.5f, 0.6f, 0.7f};
const int normalized[] = {8192, 9830, 11469};
const int test_coords[] = {4915, 13107};
for (unsigned i = 0; i < G_N_ELEMENTS (locations); i++)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
g_assert_true (hb_subset_input_pin_axis_location (
input, face, HB_TAG ('D', 'U', 'M', 'Y'), locations[i]));
hb_face_t *subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
for (unsigned j = 0; j < G_N_ELEMENTS (test_coords); j++)
{
int coords[] = {test_coords[j], normalized[i]};
assert_condition_instance_matches (face, subset, coords, &test_coords[j], 1);
}
hb_face_destroy (subset);
hb_subset_input_destroy (input);
}
hb_face_destroy (face);
}
static void
test_subset_condition_value_precision (void)
{
const char *filenames[] = {
"fonts/feature-variation-precision-GSUB-legacy.ttf",
"fonts/feature-variation-precision-GSUB-lookup.ttf",
"fonts/feature-variation-precision-GPOS-legacy.ttf",
"fonts/feature-variation-precision-GPOS-lookup.ttf",
"fonts/feature-variation-precision-GSUB-legacy-large.ttf",
"fonts/feature-variation-precision-GSUB-lookup-large.ttf",
"fonts/feature-variation-precision-GPOS-legacy-large.ttf",
"fonts/feature-variation-precision-GPOS-lookup-large.ttf",
"fonts/feature-variation-precision-GSUB-legacy-third.ttf",
"fonts/feature-variation-precision-GSUB-lookup-third.ttf",
"fonts/feature-variation-precision-GPOS-legacy-third.ttf",
"fonts/feature-variation-precision-GPOS-lookup-third.ttf",
"fonts/feature-variation-precision-GSUB-legacy-large-third.ttf",
"fonts/feature-variation-precision-GSUB-lookup-large-third.ttf",
"fonts/feature-variation-precision-GPOS-legacy-large-third.ttf",
"fonts/feature-variation-precision-GPOS-lookup-large-third.ttf",
"fonts/feature-variation-precision-GSUB-legacy-avar2.ttf",
"fonts/feature-variation-precision-GSUB-lookup-avar2.ttf",
"fonts/feature-variation-precision-GPOS-legacy-avar2.ttf",
"fonts/feature-variation-precision-GPOS-lookup-avar2.ttf"
};
const hb_tag_t tags[] = {HB_TAG ('T','E','S','T'), HB_TAG ('D','U','M','Y')};
const int locations[] = {-8192, 0, 1, 2048, 4096, 5461, 5462,
8192, 12288, 16383, 16384};
for (unsigned f = 0; f < G_N_ELEMENTS (filenames); f++)
{
g_test_message ("Font: %s", filenames[f]);
hb_face_t *face = hb_test_open_font_file (filenames[f]);
if (strstr (filenames[f], "large-third"))
{
hb_font_t *font = hb_font_create (face);
hb_buffer_t *buffer = hb_buffer_create ();
const int coords[] = {1, 16384};
hb_font_set_var_coords_normalized (font, coords, 2);
hb_buffer_add_utf8 (buffer, "AB", -1, 0, -1);
hb_buffer_guess_segment_properties (buffer);
hb_shape (font, buffer, NULL, 0);
unsigned length;
const hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &length);
const hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
g_assert_cmpuint (length, ==, 2);
/* 32767 + 1/12288 - 32767 is positive, even though float addition
* loses the fractional residual next to the large default. */
if (strstr (filenames[f], "-GSUB-"))
g_assert_cmpuint (info[1].codepoint, ==, 4);
else
g_assert_cmpint (pos[1].x_advance, ==, 620);
hb_buffer_destroy (buffer);
hb_font_destroy (font);
}
for (unsigned pinned = 0; pinned < 2; pinned++)
for (unsigned i = 0; i < G_N_ELEMENTS (locations); i++)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
g_assert_true (hb_subset_input_pin_axis_location (
input, face, tags[pinned], locations[i] / 16384.f));
hb_face_t *subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
for (unsigned j = 0; j < G_N_ELEMENTS (locations); j++)
{
int coords[] = {0, 0};
coords[pinned] = locations[i];
coords[1 - pinned] = locations[j];
assert_condition_instance_matches (face, subset, coords, &locations[j], 1);
/* A private bias region must survive instancing the font again. */
hb_subset_input_t *static_input = hb_subset_input_create_or_fail ();
hb_subset_input_set_flags (static_input, HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_set_add_range (hb_subset_input_unicode_set (static_input), 'A', 'F');
g_assert_true (hb_subset_input_pin_axis_location (
static_input, subset, tags[1 - pinned], locations[j] / 16384.f));
hb_face_t *static_subset = hb_subset_or_fail (subset, static_input);
g_assert_nonnull (static_subset);
assert_condition_instance_matches (face, static_subset, coords, NULL, 0);
hb_face_destroy (static_subset);
hb_subset_input_destroy (static_input);
}
hb_face_destroy (subset);
hb_subset_input_destroy (input);
}
hb_face_destroy (face);
}
}
static void
test_subset_feature_variation_conditions (void)
{
hb_face_t *face = hb_test_open_font_file ("fonts/feature-variation-conditions.ttf");
const float locations[] = {-0.8f, -0.6f, 0.f, 0.3f, 0.6f, 0.8f};
const int normalized[] = {-13107, -9830, 0, 4915, 9830, 13107};
const hb_tag_t tags[] = {HB_TAG ('T','E','S','T'), HB_TAG ('D','U','M','Y')};
const int original_coords[][2] = {{0, 0}, {4915, 0}, {4915, 13107},
{13107, -13107}, {4915, -9830}};
const hb_codepoint_t expected[][6] = {
{2, 3, 5, 7, 9, 11}, {2, 4, 5, 7, 9, 11}, {2, 3, 6, 7, 9, 11},
{2, 3, 5, 7, 9, 12}, {2, 3, 5, 8, 9, 11}
};
for (unsigned i = 0; i < G_N_ELEMENTS (original_coords); i++)
assert_lookup_variations_shape (face, original_coords[i], 2, expected[i]);
for (unsigned pinned = 0; pinned < 3; pinned++)
for (unsigned i = 0; i < G_N_ELEMENTS (locations); i++)
for (unsigned j = 0; j < (pinned == 2 ? G_N_ELEMENTS (locations) : 1); j++)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_set_add_range (hb_subset_input_unicode_set (input), 'A', 'F');
if (pinned == 2)
{
g_assert_true (hb_subset_input_pin_axis_location (input, face, tags[0], locations[i]));
g_assert_true (hb_subset_input_pin_axis_location (input, face, tags[1], locations[j]));
}
else
g_assert_true (hb_subset_input_pin_axis_location (input, face, tags[pinned], locations[i]));
hb_face_t *subset = hb_subset_or_fail (face, input);
g_assert_nonnull (subset);
if (pinned == 2)
{
int coords[] = {normalized[i], normalized[j]};
assert_condition_instance_matches (face, subset, coords, NULL, 0);
}
else
for (unsigned k = 0; k < G_N_ELEMENTS (locations); k++)
{
int coords[] = {0, 0};
coords[pinned] = normalized[i];
coords[1 - pinned] = normalized[k];
assert_condition_instance_matches (face, subset, coords, &normalized[k], 1);
}
hb_face_destroy (subset);
hb_subset_input_destroy (input);
}
hb_face_destroy (face);
}
#endif
static void
test_subset_set_flags (void)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
g_assert_true (hb_subset_input_get_flags (input) == HB_SUBSET_FLAGS_DEFAULT);
hb_subset_input_set_flags (input,
HB_SUBSET_FLAGS_NAME_LEGACY |
HB_SUBSET_FLAGS_NOTDEF_OUTLINE |
HB_SUBSET_FLAGS_GLYPH_NAMES);
g_assert_true (hb_subset_input_get_flags (input) ==
(hb_subset_flags_t) (
HB_SUBSET_FLAGS_NAME_LEGACY |
HB_SUBSET_FLAGS_NOTDEF_OUTLINE |
HB_SUBSET_FLAGS_GLYPH_NAMES));
hb_subset_input_set_flags (input,
HB_SUBSET_FLAGS_NAME_LEGACY |
HB_SUBSET_FLAGS_NOTDEF_OUTLINE |
HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES);
g_assert_true (hb_subset_input_get_flags (input) ==
(hb_subset_flags_t) (
HB_SUBSET_FLAGS_NAME_LEGACY |
HB_SUBSET_FLAGS_NOTDEF_OUTLINE |
HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES));
hb_subset_input_destroy (input);
}
static void
test_subset_sets (void)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t* set = hb_set_create ();
hb_set_add (hb_subset_input_set (input, HB_SUBSET_SETS_GLYPH_INDEX), 83);
hb_set_add (hb_subset_input_set (input, HB_SUBSET_SETS_UNICODE), 85);
hb_set_clear (hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG));
hb_set_add (hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG), 87);
hb_set_add (set, 83);
g_assert_true (hb_set_is_equal (hb_subset_input_glyph_set (input), set));
hb_set_clear (set);
hb_set_add (set, 85);
g_assert_true (hb_set_is_equal (hb_subset_input_unicode_set (input), set));
hb_set_clear (set);
hb_set_add (set, 87);
g_assert_true (hb_set_is_equal (hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG), set));
hb_set_clear (set);
hb_set_destroy (set);
hb_subset_input_destroy (input);
}
static void
test_subset_plan (void)
{
hb_face_t *face_abc = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf");
hb_face_t *face_ac = hb_test_open_font_file ("fonts/Roboto-Regular.ac.ttf");
hb_set_t *codepoints = hb_set_create();
hb_set_add (codepoints, 97);
hb_set_add (codepoints, 99);
hb_subset_input_t* input = hb_subset_test_create_input (codepoints);
hb_set_destroy (codepoints);
hb_subset_plan_t* plan = hb_subset_plan_create_or_fail (face_abc, input);
g_assert_true (plan);
const hb_map_t* mapping = hb_subset_plan_old_to_new_glyph_mapping (plan);
g_assert_true (hb_map_get (mapping, 1) == 1);
g_assert_true (hb_map_get (mapping, 3) == 2);
mapping = hb_subset_plan_new_to_old_glyph_mapping (plan);
g_assert_true (hb_map_get (mapping, 1) == 1);
g_assert_true (hb_map_get (mapping, 2) == 3);
mapping = hb_subset_plan_unicode_to_old_glyph_mapping (plan);
g_assert_true (hb_map_get (mapping, 0x63) == 3);
hb_face_t* face_abc_subset = hb_subset_plan_execute_or_fail (plan);
hb_subset_test_check (face_ac, face_abc_subset, HB_TAG ('l','o','c', 'a'));
hb_subset_test_check (face_ac, face_abc_subset, HB_TAG ('g','l','y','f'));
hb_subset_input_destroy (input);
hb_subset_plan_destroy (plan);
hb_face_destroy (face_abc_subset);
hb_face_destroy (face_abc);
hb_face_destroy (face_ac);
}
static hb_blob_t*
_copy_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data)
{
hb_blob_t *table = hb_face_reference_table ((hb_face_t*) user_data, tag);
hb_blob_t *copy = hb_blob_copy_writable_or_fail (table);
hb_blob_destroy (table);
return copy;
}
static void
test_subset_create_for_tables_face (void)
{
hb_face_t *face_abc = hb_test_open_font_file ("fonts/Roboto-Regular.abc.ttf");
hb_face_t *face_ac = hb_test_open_font_file ("fonts/Roboto-Regular.ac.ttf");
hb_face_t *face_create_for_tables = hb_face_create_for_tables (
_copy_table,
face_abc,
NULL);
hb_set_t *codepoints = hb_set_create();
hb_set_add (codepoints, 97);
hb_set_add (codepoints, 99);
hb_subset_input_t* input = hb_subset_test_create_input (codepoints);
hb_set_destroy (codepoints);
hb_face_t* face_abc_subset = hb_subset_or_fail (face_create_for_tables, input);
hb_subset_test_check (face_ac, face_abc_subset, HB_TAG ('l','o','c', 'a'));
hb_subset_test_check (face_ac, face_abc_subset, HB_TAG ('g','l','y','f'));
hb_subset_test_check (face_ac, face_abc_subset, HB_TAG ('g','a','s','p'));
hb_subset_input_destroy (input);
hb_face_destroy (face_abc_subset);
hb_face_destroy (face_create_for_tables);
hb_face_destroy (face_abc);
hb_face_destroy (face_ac);
}
#ifndef HB_NO_BEYOND_64K
static void
test_subset_create_for_tables_face_MAXP (void)
{
static const char MAXP_data[] = "\x00\x00\x50\x00\x00\x00\x03";
hb_face_t *source = hb_face_builder_create ();
HB_FACE_ADD_TABLE (source, "MAXP", MAXP_data);
hb_face_t *callback_face = hb_face_create_for_tables (_copy_table, source, NULL);
hb_set_t *glyphs = hb_set_create ();
hb_set_add (glyphs, 0);
hb_set_add (glyphs, 2);
hb_face_t *subset = hb_subset_test_create_subset (
callback_face, hb_subset_test_create_input_from_glyphs (glyphs));
hb_set_destroy (glyphs);
hb_blob_t *blob = hb_face_reference_table (subset, HB_TAG ('M','A','X','P'));
unsigned length;
const unsigned char *data =
(const unsigned char *) hb_blob_get_data (blob, &length);
g_assert_cmpuint (length, ==, 7);
g_assert_cmpuint ((data[4] << 16) | (data[5] << 8) | data[6], ==, 2);
hb_blob_destroy (blob);
hb_face_destroy (subset);
hb_face_destroy (callback_face);
hb_face_destroy (source);
}
#endif
#ifdef HB_EXPERIMENTAL_API
const uint8_t CFF2[226] = {
// From https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
0x02, 0x00, 0x05, 0x00, 0x07, 0xCF, 0x0C, 0x24, 0xC3, 0x11, 0x9B, 0x18, 0x00, 0x00, 0x00, 0x00,
0x00, 0x26, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01,
0x00, 0x02, 0xC0, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x03, 0x05,
0x20, 0x0A, 0x20, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x05, 0xF7, 0x06, 0xDA, 0x12, 0x77,
0x9F, 0xF8, 0x6C, 0x9D, 0xAE, 0x9A, 0xF4, 0x9A, 0x95, 0x9F, 0xB3, 0x9F, 0x8B, 0x8B, 0x8B, 0x8B,
0x85, 0x9A, 0x8B, 0x8B, 0x97, 0x73, 0x8B, 0x8B, 0x8C, 0x80, 0x8B, 0x8B, 0x8B, 0x8D, 0x8B, 0x8B,
0x8C, 0x8A, 0x8B, 0x8B, 0x97, 0x17, 0x06, 0xFB, 0x8E, 0x95, 0x86, 0x9D, 0x8B, 0x8B, 0x8D, 0x17,
0x07, 0x77, 0x9F, 0xF8, 0x6D, 0x9D, 0xAD, 0x9A, 0xF3, 0x9A, 0x95, 0x9F, 0xB3, 0x9F, 0x08, 0xFB,
0x8D, 0x95, 0x09, 0x1E, 0xA0, 0x37, 0x5F, 0x0C, 0x09, 0x8B, 0x0C, 0x0B, 0xC2, 0x6E, 0x9E, 0x8C,
0x17, 0x0A, 0xDB, 0x57, 0xF7, 0x02, 0x8C, 0x17, 0x0B, 0xB3, 0x9A, 0x77, 0x9F, 0x82, 0x8A, 0x8D,
0x17, 0x0C, 0x0C, 0xDB, 0x95, 0x57, 0xF7, 0x02, 0x85, 0x8B, 0x8D, 0x17, 0x0C, 0x0D, 0xF7, 0x06,
0x13, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1B, 0xBD, 0xBD, 0xEF, 0x8C, 0x10, 0x8B, 0x15, 0xF8,
0x88, 0x27, 0xFB, 0x5C, 0x8C, 0x10, 0x06, 0xF8, 0x88, 0x07, 0xFC, 0x88, 0xEF, 0xF7, 0x5C, 0x8C,
0x10, 0x06
};
const uint8_t CFF2_ONLY_CHARSTRINGS[12] = {
0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x03, 0x05,
0x20, 0x0A, 0x20, 0x0A
};
static void
test_subset_cff2_get_charstring_data (void)
{
const uint8_t maxp_data[6] = {
0x00, 0x00, 0x50, 0x00,
0x00, 0x02 // numGlyphs
};
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_blob_t* maxp = hb_blob_create ((const char*) maxp_data, 6, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_face_builder_add_table (builder, HB_TAG('m', 'a', 'x', 'p'), maxp);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
unsigned int length;
const uint8_t* data = (const uint8_t*) hb_blob_get_data (cs0, &length);
g_assert_true (length == 2);
g_assert_true (data[0] == 0x20);
g_assert_true (data[1] == 0x0A);
hb_blob_t* cs1 = hb_subset_cff2_get_charstring_data (face, 1);
data = (const uint8_t*) hb_blob_get_data (cs1, &length);
g_assert_true (length == 2);
g_assert_true (data[0] == 0x20);
g_assert_true (data[1] == 0x0A);
hb_blob_t* cs2 = hb_subset_cff2_get_charstring_data (face, 2);
data = (const uint8_t*) hb_blob_get_data (cs2, &length);
g_assert_true (length == 0);
hb_blob_destroy (cff2);
hb_blob_destroy (maxp);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy (cs0);
hb_blob_destroy (cs1);
hb_blob_destroy (cs2);
}
static void
test_subset_cff2_get_all_charstrings_data (void)
{
const uint8_t maxp_data[6] = {
0x00, 0x00, 0x50, 0x00,
0x00, 0x02 // numGlyphs
};
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_blob_t* maxp = hb_blob_create ((const char*) maxp_data, 6, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_face_builder_add_table (builder, HB_TAG('m', 'a', 'x', 'p'), maxp);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs = hb_subset_cff2_get_charstrings_index (face);
hb_blob_destroy (cff2);
hb_blob_destroy (maxp);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
unsigned int length;
const uint8_t* data = (const uint8_t*) hb_blob_get_data (cs, &length);
g_assert_cmpint (length, ==, 12);
for (int i = 0; i < 12; i++) {
g_assert_cmpint(data[i], ==, CFF2_ONLY_CHARSTRINGS[i]);
}
hb_blob_destroy (cs);
}
static void
test_subset_cff2_get_charstring_data_no_cff (void)
{
hb_face_t* builder = hb_face_builder_create ();
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
g_assert_true (hb_blob_get_length (cs0) == 0);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy (cs0);
}
static void
test_subset_cff2_get_charstring_data_invalid_cff2 (void)
{
// cff2 will parse as invalid since charstrings count doesn't not match num glyphs
// (because there is no maxp).
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
g_assert_true (hb_blob_get_length (cs0) == 0);
hb_blob_destroy (cff2);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
hb_blob_destroy (cs0);
}
static void
test_subset_cff2_get_charstring_data_lifetime (void)
{
const uint8_t maxp_data[6] = {
0x00, 0x00, 0x50, 0x00,
0x00, 0x02 // numGlyphs
};
hb_blob_t* cff2 = hb_blob_create ((const char*) CFF2, 226, HB_MEMORY_MODE_READONLY, 0, 0);
hb_blob_t* maxp = hb_blob_create ((const char*) maxp_data, 6, HB_MEMORY_MODE_READONLY, 0, 0);
hb_face_t* builder = hb_face_builder_create ();
hb_face_builder_add_table (builder, HB_TAG('C', 'F', 'F', '2'), cff2);
hb_face_builder_add_table (builder, HB_TAG('m', 'a', 'x', 'p'), maxp);
hb_blob_t* face_blob = hb_face_reference_blob (builder);
hb_face_t* face = hb_face_create (face_blob, 0);
hb_blob_t* cs0 = hb_subset_cff2_get_charstring_data (face, 0);
// Destroy the blob that cs0 is referencing via subblob to ensure lifetimes are being
// handled correctly.
hb_blob_destroy (cff2);
hb_blob_destroy (maxp);
hb_face_destroy (builder);
hb_blob_destroy (face_blob);
hb_face_destroy (face);
unsigned int length;
const uint8_t* data = (const uint8_t*) hb_blob_get_data (cs0, &length);
g_assert_true (length == 2);
g_assert_true (data[0] == 0x20);
g_assert_true (data[1] == 0x0A);
hb_blob_destroy (cs0);
}
#endif
#ifdef HB_EXPERIMENTAL_API
static void
test_subset_input_to_string (void)
{
/* Test NULL input */
g_assert_null (hb_subset_input_to_string_or_fail (NULL));
/* Test default input -> empty string */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
g_assert_cmpint (len, ==, 1);
g_assert_cmpstr (data, ==, "");
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test unicodes ranges */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *unicodes = hb_subset_input_unicode_set (input);
hb_set_add (unicodes, 0x41);
hb_set_add (unicodes, 0x42);
hb_set_add (unicodes, 0x43);
hb_set_add (unicodes, 0x45);
hb_set_add (unicodes, 0x61);
hb_set_add (unicodes, 0x62);
hb_set_add (unicodes, 0x63);
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
g_assert_cmpstr (data, ==, "--unicodes=41-43,45,61-63");
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test gids ranges and flags */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *gids = hb_subset_input_glyph_set (input);
hb_set_add (gids, 1);
hb_set_add (gids, 2);
hb_set_add (gids, 3);
hb_set_add (gids, 5);
hb_set_add (gids, 10);
hb_set_add (gids, 11);
hb_subset_input_set_flags (input, HB_SUBSET_FLAGS_NO_HINTING | HB_SUBSET_FLAGS_RETAIN_GIDS);
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
g_assert_cmpstr (data, ==, "--no-hinting --retain-gids --gids=1-3,5,10-11");
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test keep everything */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_subset_input_keep_everything (input);
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--name-legacy --passthrough-tables --notdef-outline --glyph-names --no-prune-unicode-ranges --unicodes=* --gids=* --name-IDs=* --name-languages=* --layout-features=* --drop-tables-=*";
g_assert_cmpstr (data, ==, expected);
g_assert_cmpint (len - 1, ==, strlen (expected));
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test inverted sets with exclusions */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *unicodes = hb_subset_input_set (input, HB_SUBSET_SETS_UNICODE);
hb_set_invert (unicodes);
hb_set_del (unicodes, 0x20);
hb_set_del (unicodes, 0x41);
hb_set_del (unicodes, 0x42);
hb_set_del (unicodes, 0x43);
hb_set_t *gids = hb_subset_input_set (input, HB_SUBSET_SETS_GLYPH_INDEX);
hb_set_invert (gids);
hb_set_del (gids, 5);
hb_set_del (gids, 6);
hb_set_del (gids, 7);
hb_set_t *features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_invert (features);
hb_set_del (features, HB_TAG ('k', 'e', 'r', 'n'));
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--unicodes=* --unicodes-=20,41-43 --gids=* --gids-=5-7 --layout-features=* --layout-features-=kern";
g_assert_cmpstr (data, ==, expected);
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test empty non-default sets */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_clear (hb_subset_input_set (input, HB_SUBSET_SETS_NAME_ID));
hb_set_clear (hb_subset_input_set (input, HB_SUBSET_SETS_NAME_LANG_ID));
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--name-IDs-=* --name-languages-=*";
g_assert_cmpstr (data, ==, expected);
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test non-default non-inverted tag sets */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG ('l', 'i', 'g', 'a'));
hb_set_add (features, HB_TAG ('k', 'e', 'r', 'n'));
hb_set_t *scripts = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG);
hb_set_clear (scripts);
hb_set_add (scripts, HB_TAG ('l', 'a', 't', 'n'));
hb_set_t *drop_tables = hb_subset_input_set (input, HB_SUBSET_SETS_DROP_TABLE_TAG);
hb_set_clear (drop_tables);
hb_set_add (drop_tables, HB_TAG ('G', 'S', 'U', 'B'));
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--layout-features=kern,liga --layout-scripts=latn --drop-tables=GSUB";
g_assert_cmpstr (data, ==, expected);
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test tag sanitization with unsafe ASCII characters */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
/* Tag with space, newline, and single-quote: 'a \n'' */
hb_set_add (features, HB_TAG ('a', ' ', '\n', '\''));
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--layout-features=a___";
g_assert_cmpstr (data, ==, expected);
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
/* Test trimming trailing whitespace from tags */
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
hb_set_t *drop_tables = hb_subset_input_set (input, HB_SUBSET_SETS_DROP_TABLE_TAG);
hb_set_clear (drop_tables);
hb_set_add (drop_tables, HB_TAG ('c', 'v', 't', ' '));
hb_set_t *features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
hb_set_clear (features);
hb_set_add (features, HB_TAG (' ', ' ', ' ', ' '));
hb_set_add (features, HB_TAG ('f', 'o', 'o', ' '));
hb_blob_t *blob = hb_subset_input_to_string_or_fail (input);
g_assert_nonnull (blob);
unsigned len = 0;
const char *data = hb_blob_get_data (blob, &len);
const char *expected = "--layout-features=,foo --drop-tables=cvt";
g_assert_cmpstr (data, ==, expected);
hb_blob_destroy (blob);
hb_subset_input_destroy (input);
}
}
#endif
int
main (int argc, char **argv)
{
hb_test_init (&argc, &argv);
hb_test_add (test_subset_32_tables);
hb_test_add (test_subset_no_inf_loop);
hb_test_add (test_subset_crash);
#ifndef HB_NO_VAR
hb_test_add (test_subset_lookup_variations);
hb_test_add (test_subset_lookup_variation_fractional_value);
hb_test_add (test_subset_condition_value_precision);
hb_test_add (test_subset_feature_variation_conditions);
hb_test_add (test_subset_feature_variation_universal);
#endif
hb_test_add (test_subset_set_flags);
hb_test_add (test_subset_sets);
hb_test_add (test_subset_plan);
hb_test_add (test_subset_create_for_tables_face);
#ifndef HB_NO_BEYOND_64K
hb_test_add (test_subset_create_for_tables_face_MAXP);
#endif
#ifdef HB_EXPERIMENTAL_API
hb_test_add (test_subset_input_to_string);
hb_test_add (test_subset_cff2_get_charstring_data);
hb_test_add (test_subset_cff2_get_all_charstrings_data);
hb_test_add (test_subset_cff2_get_charstring_data_no_cff);
hb_test_add (test_subset_cff2_get_charstring_data_invalid_cff2);
hb_test_add (test_subset_cff2_get_charstring_data_lifetime);
#endif
return hb_test_run();
}