diff --git a/src/hb-subset-input.cc b/src/hb-subset-input.cc index a30266bfc..c2e4c5f88 100644 --- a/src/hb-subset-input.cc +++ b/src/hb-subset-input.cc @@ -24,6 +24,7 @@ * Google Author(s): Garret Rieger, Rod Sheeter, Behdad Esfahbod */ +#include "hb-subset-instancer-solver.hh" #include "hb-subset.hh" #include "hb-set.hh" #include "hb-utf.hh" @@ -524,6 +525,37 @@ hb_subset_input_set_axis_range (hb_subset_input_t *input, float new_default_val = hb_clamp(def, new_min_val, new_max_val); return input->axes_location.set (axis_tag, Triple (new_min_val, new_default_val, new_max_val)); } + +/** + * hb_subset_input_get_axis_range: (skip) + * @input: a #hb_subset_input_t object. + * @axis_tag: Tag of the axis + * @axis_min_value: Set to the previously configured minimum value of the axis variation range. + * @axis_max_value: Set to the previously configured maximum value of the axis variation range. + * @axis_def_value: Set to the previously configured default value of the axis variation range. + * + * Return value: `true` if a range has been set for this axis tag, `false` otherwise. + * + * XSince: EXPERIMENTAL + **/ +HB_EXTERN hb_bool_t +hb_subset_input_get_axis_range (hb_subset_input_t *input, + hb_tag_t axis_tag, + float *axis_min_value, + float *axis_max_value, + float *axis_def_value) + +{ + Triple* triple; + if (!input->axes_location.has(axis_tag, &triple)) { + return false; + } + + *axis_min_value = triple->minimum; + *axis_def_value = triple->middle; + *axis_max_value = triple->maximum; + return true; +} #endif #endif diff --git a/src/hb-subset.h b/src/hb-subset.h index 9ec6d669a..f3df9d280 100644 --- a/src/hb-subset.h +++ b/src/hb-subset.h @@ -182,6 +182,13 @@ hb_subset_input_pin_axis_location (hb_subset_input_t *input, float axis_value); #ifdef HB_EXPERIMENTAL_API +HB_EXTERN hb_bool_t +hb_subset_input_get_axis_range (hb_subset_input_t *input, + hb_tag_t axis_tag, + float *axis_min_value, + float *axis_max_value, + float *axis_def_value); + HB_EXTERN hb_bool_t hb_subset_input_set_axis_range (hb_subset_input_t *input, hb_face_t *face, diff --git a/util/hb-subset.cc b/util/hb-subset.cc index da03be149..04fe9efd8 100644 --- a/util/hb-subset.cc +++ b/util/hb-subset.cc @@ -30,8 +30,8 @@ #include "glib.h" #include "main-font-text.hh" #include "output-options.hh" +#include "helper-subset.hh" -#include #include static hb_face_t* preprocess_face(hb_face_t* face) @@ -677,92 +677,6 @@ parse_drop_tables (const char *name, #ifndef HB_NO_VAR -// Parses an axis position string and sets min, default, and max to -// the requested values. If a value should be set to it's default value -// then it will be set to NaN. -static gboolean -parse_axis_position(const char* s, - float* min, - float* def, - float* max, - gboolean* drop, - GError **error) -{ - const char* part = strpbrk(s, ":"); - *drop = false; - if (!part) { - // Single value. - if (strcmp (s, "drop") == 0) - { - *min = NAN; - *def = NAN; - *max = NAN; - *drop = true; - return true; - } - - errno = 0; - char *p; - float axis_value = strtof (s, &p); - if (errno || s == p) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Failed parsing axis value at: '%s'", s); - return false; - } - - *min = axis_value; - *def = axis_value; - *max = axis_value; - return true; - } - - - float values[3]; - int count = 0; - for (int i = 0; i < 3; i++) { - errno = 0; - count++; - if (!*s || part == s) { - values[i] = NAN; - - if (part == nullptr) break; - s = part + 1; - part = strpbrk(s, ":"); - continue; - } - - char *pend; - values[i] = strtof (s, &pend); - if (errno || s == pend || (part && pend != part)) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Failed parsing axis value at: '%s'", s); - return false; - } - - if (part == nullptr) break; - s = pend + 1; - part = strpbrk(s, ":"); - } - - if (count == 2) { - *min = values[0]; - *def = NAN; - *max = values[1]; - return true; - } else if (count == 3) { - *min = values[0]; - *def = values[1]; - *max = values[2]; - return true; - } - - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Failed parsing axis value at: '%s'", s); - return false; -} - static gboolean parse_instance (const char *name, const char *arg, @@ -775,76 +689,7 @@ parse_instance (const char *name, return true; } - char* s; - while ((s = strtok((char *) arg, "="))) - { - arg = nullptr; - unsigned len = strlen (s); - if (len > 4) //Axis tags are 4 bytes. - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Failed parsing axis tag at: '%s'", s); - return false; - } - - hb_tag_t axis_tag = hb_tag_from_string (s, len); - - s = strtok(nullptr, ", "); - if (!s) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Value not specified for axis: %c%c%c%c", HB_UNTAG (axis_tag)); - return false; - } - - gboolean drop; - float min, def, max; - if (!parse_axis_position(s, &min, &def, &max, &drop, error)) - return false; - - if (drop) - { - if (!hb_subset_input_pin_axis_to_default (subset_main->input, - subset_main->face, - axis_tag)) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); - return false; - } - continue; - } - - if (min == def && def == max) { - if (!hb_subset_input_pin_axis_location (subset_main->input, - subset_main->face, axis_tag, - def)) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); - return false; - } - continue; - } - -#ifdef HB_EXPERIMENTAL_API - if (!hb_subset_input_set_axis_range (subset_main->input, - subset_main->face, axis_tag, - min, max, def)) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); - return false; - } - continue; -#endif - - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Partial instancing is not supported."); - return false; - } - - return true; + return parse_instancing_spec(arg, subset_main->face, subset_main->input, error); } #endif diff --git a/util/helper-subset.hh b/util/helper-subset.hh new file mode 100644 index 000000000..067a62b71 --- /dev/null +++ b/util/helper-subset.hh @@ -0,0 +1,202 @@ +/* + * Copyright © 2023 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 + */ +#ifndef HELPER_SUBSET_HH +#define HELPER_SUBSET_HH + +#include "glib.h" +#include +#include +#include "hb-subset.h" + +#ifndef HB_NO_VAR + +// Parses an axis position string and sets min, default, and max to +// the requested values. If a value should be set to it's default value +// then it will be set to NaN. +static gboolean +parse_axis_position(const char* s, + float* min, + float* def, + float* max, + gboolean* drop, + GError **error) +{ + const char* part = strpbrk(s, ":"); + *drop = false; + if (!part) { + // Single value. + if (strcmp (s, "drop") == 0) + { + *min = NAN; + *def = NAN; + *max = NAN; + *drop = true; + return true; + } + + errno = 0; + char *p; + float axis_value = strtof (s, &p); + if (errno || s == p) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis value at: '%s'", s); + return false; + } + + *min = axis_value; + *def = axis_value; + *max = axis_value; + return true; + } + + + float values[3]; + int count = 0; + for (int i = 0; i < 3; i++) { + errno = 0; + count++; + if (!*s || part == s) { + values[i] = NAN; + + if (part == NULL) break; + s = part + 1; + part = strpbrk(s, ":"); + continue; + } + + char *pend; + values[i] = strtof (s, &pend); + if (errno || s == pend || (part && pend != part)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis value at: '%s'", s); + return false; + } + + if (part == NULL) break; + s = pend + 1; + part = strpbrk(s, ":"); + } + + if (count == 2) { + *min = values[0]; + *def = NAN; + *max = values[1]; + return true; + } else if (count == 3) { + *min = values[0]; + *def = values[1]; + *max = values[2]; + return true; + } + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis value at: '%s'", s); + return false; +} + +static gboolean +parse_instancing_spec (const char *arg, + hb_face_t* face, + hb_subset_input_t* input, + GError **error) +{ + char* s; + while ((s = strtok((char *) arg, "="))) + { + arg = NULL; + unsigned len = strlen (s); + if (len > 4) //Axis tags are 4 bytes. + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed parsing axis tag at: '%s'", s); + return false; + } + + hb_tag_t axis_tag = hb_tag_from_string (s, len); + + s = strtok(NULL, ", "); + if (!s) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Value not specified for axis: %c%c%c%c", HB_UNTAG (axis_tag)); + return false; + } + + gboolean drop; + float min, def, max; + if (!parse_axis_position(s, &min, &def, &max, &drop, error)) + return false; + + if (drop) + { + if (!hb_subset_input_pin_axis_to_default (input, + face, + axis_tag)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); + return false; + } + continue; + } + + if (min == def && def == max) { + if (!hb_subset_input_pin_axis_location (input, + face, axis_tag, + def)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); + return false; + } + continue; + } + +#ifdef HB_EXPERIMENTAL_API + if (!hb_subset_input_set_axis_range (input, + face, axis_tag, + min, max, def)) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag)); + return false; + } + continue; +#endif + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Partial instancing is not supported."); + return false; + } + + return true; +} + +#endif + +#endif diff --git a/util/meson.build b/util/meson.build index e857384cb..1f300eaf2 100644 --- a/util/meson.build +++ b/util/meson.build @@ -16,6 +16,7 @@ hb_ot_shape_closure_sources = [ hb_subset_cli_sources = [ 'hb-subset.cc', + 'helper-subset.hh', ] util_deps = [freetype_dep, cairo_dep, cairo_ft_dep, glib_dep] @@ -67,6 +68,21 @@ if conf.get('HAVE_GLIB', 0) == 1 install: true, ) meson.override_find_program('hb-ot-shape-closure', hb_ot_shape_closure) + + if get_option('experimental_api') + test('test-hb-subset-parsing', + executable('test-hb-subset-parsing', + ['test-hb-subset-parsing.c', 'helper-subset.hh'], + cpp_args: cpp_args, + c_args: ['-DHB_EXPERIMENTAL_API'], + include_directories: [incconfig, incsrc], + dependencies: util_deps, + link_with: [libharfbuzz, libharfbuzz_subset], + install: false, + ), + workdir : meson.current_source_dir(), + suite: ['util']) + endif else # Disable tests that use this hb_shape = disabler() diff --git a/util/test-hb-subset-parsing.c b/util/test-hb-subset-parsing.c new file mode 100644 index 000000000..53c20c6dc --- /dev/null +++ b/util/test-hb-subset-parsing.c @@ -0,0 +1,98 @@ +#include +#include +#include "glib.h" +#include "hb-subset.h" +#include "helper-subset.hh" + +hb_face_t* open_font(const char* path) +{ + hb_blob_t *blob = hb_blob_create_from_file_or_fail (path); + g_assert(blob); + hb_face_t* face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + + return face; +} + +gboolean check_parsing(hb_face_t* face, const char* spec, hb_tag_t axis, float exp_min, float exp_def, float exp_max) +{ + printf(">> testing spec: %s\n", spec); + hb_subset_input_t* input = hb_subset_input_create_or_fail(); + g_assert(input); + + { + GError* error; + char *spec_copy = g_strdup (spec); + gboolean res = parse_instancing_spec(spec_copy, face, input, &error); + g_free(spec_copy); + if (!res) { + hb_subset_input_destroy(input); + return res; + } + } + + float act_min = 0.0, act_def = 0.0, act_max = 0.0; + hb_bool_t res = hb_subset_input_get_axis_range(input, axis, &act_min, &act_max, &act_def); + if (!res) { + hb_subset_input_destroy(input); + return false; + + } + + g_assert_cmpuint(exp_min, ==, act_min); + g_assert_cmpuint(exp_def, ==, act_def); + g_assert_cmpuint(exp_max, ==, act_max); + + hb_subset_input_destroy(input); + return true; +} + +static hb_tag_t wght = HB_TAG('w', 'g', 'h', 't'); +static hb_tag_t xxxx = HB_TAG('x', 'x', 'x', 'x'); + +static void +test_parse_instancing_spec (void) +{ + hb_face_t* face = open_font("../test/api/fonts/AdobeVFPrototype-Subset.otf"); + hb_face_t* roboto = open_font("../test/api/fonts/Roboto-Variable.abc.ttf"); + + g_assert(check_parsing(face, "wght=300", wght, 300, 300, 300)); + g_assert(check_parsing(face, "wght=100:200:300", wght, 100, 200, 300)); + g_assert(check_parsing(face, "wght=:500:", wght, 0, 500, 1000)); + g_assert(check_parsing(face, "wght=::700", wght, 0, 700, 700)); + g_assert(check_parsing(face, "wght=200::", wght, 200, 1000, 1000)); + g_assert(check_parsing(face, "wght=200:300:", wght, 200, 300, 1000)); + g_assert(check_parsing(face, "wght=:300:500", wght, 0, 300, 500)); + g_assert(check_parsing(face, "wght=300::700", wght, 300, 700, 700)); + g_assert(check_parsing(face, "wght=300:700", wght, 300, 700, 700)); + g_assert(check_parsing(face, "wght=:700", wght, 0, 700, 700)); + g_assert(check_parsing(face, "wght=200:", wght, 200, 1000, 1000)); + + g_assert(check_parsing(face, "wght=200: xxxx=50", wght, 200, 1000, 1000)); + g_assert(check_parsing(face, "wght=200: xxxx=50", xxxx, 50, 50, 50)); + g_assert(check_parsing(face, "wght=200:,xxxx=50", wght, 200, 1000, 1000)); + g_assert(check_parsing(face, "wght=200:,xxxx=50", xxxx, 50, 50, 50)); + + g_assert(check_parsing(roboto, "wght=300", wght, 300, 300, 300)); + g_assert(check_parsing(roboto, "wght=100:200:300", wght, 100, 200, 300)); + g_assert(check_parsing(roboto, "wght=:500:", wght, 100, 500, 900)); + g_assert(check_parsing(roboto, "wght=::850", wght, 100, 400, 850)); + g_assert(check_parsing(roboto, "wght=200::", wght, 200, 400, 900)); + g_assert(check_parsing(roboto, "wght=200:300:", wght, 200, 300, 900)); + g_assert(check_parsing(roboto, "wght=:300:500", wght, 100, 300, 500)); + g_assert(check_parsing(roboto, "wght=300::700", wght, 300, 400, 700)); + g_assert(check_parsing(roboto, "wght=300:700", wght, 300, 400, 700)); + g_assert(check_parsing(roboto, "wght=:700", wght, 100, 400, 700)); + g_assert(check_parsing(roboto, "wght=200:", wght, 200, 400, 900)); + + hb_face_destroy(face); +} + + +int +main (int argc, char **argv) +{ + test_parse_instancing_spec(); + + return 0; +}