Add test suite for instancing option parsing.

pull/4473/head
Garret Rieger 1 year ago
parent 43236ce345
commit d30c1dacf5
  1. 32
      src/hb-subset-input.cc
  2. 7
      src/hb-subset.h
  3. 159
      util/hb-subset.cc
  4. 202
      util/helper-subset.hh
  5. 16
      util/meson.build
  6. 98
      util/test-hb-subset-parsing.c

@ -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

@ -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,

@ -30,8 +30,8 @@
#include "glib.h"
#include "main-font-text.hh"
#include "output-options.hh"
#include "helper-subset.hh"
#include <cmath>
#include <hb-subset.h>
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

@ -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 <math.h>
#include <stdbool.h>
#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

@ -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()

@ -0,0 +1,98 @@
#include <assert.h>
#include <stdio.h>
#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;
}
Loading…
Cancel
Save