[instancer] add the option to leave blanks in the min:def:max syntax.

When parsing axis positions in the --variations flag recognize empty values as meaning the existing value for that part. For example:

:300:500

Specifies min = existing, def = 300, max = 500.

See: https://github.com/fonttools/fonttools/issues/3322
pull/4473/head
Garret Rieger 1 year ago
parent 6a3ca37373
commit 43236ce345
  1. 30
      src/hb-subset-input.cc
  2. 2
      src/hb-subset.h
  3. 184
      util/hb-subset.cc

@ -481,16 +481,13 @@ hb_subset_input_pin_axis_location (hb_subset_input_t *input,
* @input: a #hb_subset_input_t object. * @input: a #hb_subset_input_t object.
* @face: a #hb_face_t object. * @face: a #hb_face_t object.
* @axis_tag: Tag of the axis * @axis_tag: Tag of the axis
* @axis_min_value: Minimum value of the axis variation range to set * @axis_min_value: Minimum value of the axis variation range to set, if NaN the existing min will be used.
* @axis_max_value: Maximum value of the axis variation range to set * @axis_max_value: Maximum value of the axis variation range to set if NaN the existing max will be used.
* @axis_def_value: Default value of the axis variation range to set, in case of * @axis_def_value: Default value of the axis variation range to set, if NaN the existing default will be used.
* null, it'll be determined automatically
* *
* Restricting the range of variation on an axis in the given subset input object. * Restricting the range of variation on an axis in the given subset input object.
* New min/default/max values will be clamped if they're not within the fvar axis range. * New min/default/max values will be clamped if they're not within the fvar axis range.
* If the new default value is null: *
* If the fvar axis default value is within the new range, then new default
* value is the same as original default value.
* If the fvar axis default value is not within the new range, the new default * If the fvar axis default value is not within the new range, the new default
* value will be changed to the new min or max value, whichever is closer to the fvar * value will be changed to the new min or max value, whichever is closer to the fvar
* axis default. * axis default.
@ -509,19 +506,22 @@ hb_subset_input_set_axis_range (hb_subset_input_t *input,
hb_tag_t axis_tag, hb_tag_t axis_tag,
float axis_min_value, float axis_min_value,
float axis_max_value, float axis_max_value,
float *axis_def_value /* IN, maybe NULL */) float axis_def_value)
{ {
if (axis_min_value > axis_max_value)
return false;
hb_ot_var_axis_info_t axis_info; hb_ot_var_axis_info_t axis_info;
if (!hb_ot_var_find_axis_info (face, axis_tag, &axis_info)) if (!hb_ot_var_find_axis_info (face, axis_tag, &axis_info))
return false; return false;
float new_min_val = hb_clamp(axis_min_value, axis_info.min_value, axis_info.max_value); float min = !std::isnan(axis_min_value) ? axis_min_value : axis_info.min_value;
float new_max_val = hb_clamp(axis_max_value, axis_info.min_value, axis_info.max_value); float max = !std::isnan(axis_max_value) ? axis_max_value : axis_info.max_value;
float new_default_val = axis_def_value ? *axis_def_value : axis_info.default_value; float def = !std::isnan(axis_def_value) ? axis_def_value : axis_info.default_value;
new_default_val = hb_clamp(new_default_val, new_min_val, new_max_val);
if (min > max)
return false;
float new_min_val = hb_clamp(min, axis_info.min_value, axis_info.max_value);
float new_max_val = hb_clamp(max, axis_info.min_value, axis_info.max_value);
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)); return input->axes_location.set (axis_tag, Triple (new_min_val, new_default_val, new_max_val));
} }
#endif #endif

@ -188,7 +188,7 @@ hb_subset_input_set_axis_range (hb_subset_input_t *input,
hb_tag_t axis_tag, hb_tag_t axis_tag,
float axis_min_value, float axis_min_value,
float axis_max_value, float axis_max_value,
float *axis_def_value); float axis_def_value);
HB_EXTERN hb_bool_t HB_EXTERN hb_bool_t
hb_subset_input_override_name_table (hb_subset_input_t *input, hb_subset_input_override_name_table (hb_subset_input_t *input,

@ -27,9 +27,11 @@
#include "batch.hh" #include "batch.hh"
#include "face-options.hh" #include "face-options.hh"
#include "glib.h"
#include "main-font-text.hh" #include "main-font-text.hh"
#include "output-options.hh" #include "output-options.hh"
#include <cmath>
#include <hb-subset.h> #include <hb-subset.h>
static hb_face_t* preprocess_face(hb_face_t* face) static hb_face_t* preprocess_face(hb_face_t* face)
@ -674,122 +676,172 @@ parse_drop_tables (const char *name,
} }
#ifndef HB_NO_VAR #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 static gboolean
parse_instance (const char *name, parse_axis_position(const char* s,
const char *arg, float* min,
gpointer data, float* def,
float* max,
gboolean* drop,
GError **error) GError **error)
{ {
subset_main_t *subset_main = (subset_main_t *) data; const char* part = strpbrk(s, ":");
if (!subset_main->face) { *drop = false;
// There is no face, which is needed to set up instancing. Skip parsing these options. if (!part) {
// Single value.
if (strcmp (s, "drop") == 0)
{
*min = NAN;
*def = NAN;
*max = NAN;
*drop = true;
return true; return true;
} }
char *s = strtok((char *) arg, "="); errno = 0;
while (s) char *p;
{ float axis_value = strtof (s, &p);
unsigned len = strlen (s); if (errno || s == p)
if (len > 4) //Axis tags are 4 bytes.
{ {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing axis tag at: '%s'", s); "Failed parsing axis value at: '%s'", s);
return false; return false;
} }
hb_tag_t axis_tag = hb_tag_from_string (s, len); *min = axis_value;
*def = axis_value;
s = strtok(nullptr, ", "); *max = axis_value;
if (!s) return true;
{
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;
} }
#ifdef HB_EXPERIMENTAL_API
char *pp = s; float values[3];
pp = strpbrk (pp, ":"); int count = 0;
if (pp) // partial instancing for (int i = 0; i < 3; i++) {
{
errno = 0; errno = 0;
count++;
if (!*s || part == s) {
values[i] = NAN;
if (part == nullptr) break;
s = part + 1;
part = strpbrk(s, ":");
continue;
}
char *pend; char *pend;
float min_val = strtof (s, &pend); values[i] = strtof (s, &pend);
if (errno || s == pend || pend != pp) if (errno || s == pend || (part && pend != part))
{ {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing axis value at: '%s'", s); "Failed parsing axis value at: '%s'", s);
return false; return false;
} }
pp++;
float max_val = strtof (pp, &pend); if (part == nullptr) break;
/* we need to specify 2 values or 3 values for partial instancing: s = pend + 1;
* at least new min and max values, new default is optional */ part = strpbrk(s, ":");
if (errno || pp == pend || (*pend != ':' && *pend != '\0')) }
{
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, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing axis value at: '%s'", s); "Failed parsing axis value at: '%s'", s);
return false; return false;
} }
/* 3 values are specified */
float *def_val_p = nullptr; static gboolean
float def_val; parse_instance (const char *name,
if (*pend == ':') const char *arg,
gpointer data,
GError **error)
{
subset_main_t *subset_main = (subset_main_t *) data;
if (!subset_main->face) {
// There is no face, which is needed to set up instancing. Skip parsing these options.
return true;
}
char* s;
while ((s = strtok((char *) arg, "=")))
{ {
def_val = max_val; arg = nullptr;
def_val_p = &def_val; unsigned len = strlen (s);
pp = pend + 1; if (len > 4) //Axis tags are 4 bytes.
max_val = strtof (pp, &pend);
if (errno || pp == pend || *pend != '\0')
{ {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing axis value at: '%s'", s); "Failed parsing axis tag at: '%s'", s);
return false; return false;
} }
}
if (!hb_subset_input_set_axis_range (subset_main->input, subset_main->face, axis_tag, min_val, max_val, def_val_p)) 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, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Error: axis: '%c%c%c%c', not present in fvar or invalid range with min:%.6f max:%.6f", "Value not specified for axis: %c%c%c%c", HB_UNTAG (axis_tag));
HB_UNTAG (axis_tag), (double) min_val, (double) max_val);
return false; return false;
} }
}
else gboolean drop;
{ float min, def, max;
#endif if (!parse_axis_position(s, &min, &def, &max, &drop, error))
if (strcmp (s, "drop") == 0) return false;
if (drop)
{ {
if (!hb_subset_input_pin_axis_to_default (subset_main->input, subset_main->face, axis_tag)) 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, 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)); "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
return false; return false;
} }
continue;
} }
else
{ if (min == def && def == max) {
errno = 0; if (!hb_subset_input_pin_axis_location (subset_main->input,
char *p; subset_main->face, axis_tag,
float axis_value = strtof (s, &p); def))
if (errno || s == p)
{ {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing axis value at: '%s'", s); "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
return false; return false;
} }
continue;
}
if (!hb_subset_input_pin_axis_location (subset_main->input, subset_main->face, axis_tag, axis_value)) #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, 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)); "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
return false; return false;
} }
} continue;
#ifdef HB_EXPERIMENTAL_API
}
#endif #endif
s = strtok(nullptr, "=");
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Partial instancing is not supported.");
return false;
} }
return true; return true;
@ -1028,7 +1080,7 @@ subset_main_t::add_options ()
{"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance, {"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
"(Partially|Fully) Instantiate a variable font. A location consists of the tag " "(Partially|Fully) Instantiate a variable font. A location consists of the tag "
"of a variation axis, followed by '=', followed by a number or the literal " "of a variation axis, followed by '=', followed by a number or the literal "
"string 'drop'. For example: --variations=\"wdth=100 wght=200\" or --instance=\"wdth=drop\"" "string 'drop'. For example: --variations=\"wdth=100 wght=200\" or --variations=\"wdth=drop\""
#ifndef HB_EXPERIMENTAL_API #ifndef HB_EXPERIMENTAL_API
"\n\nNote: currently only full instancing is supported unless this util has been compiled with experimental api enabled." "\n\nNote: currently only full instancing is supported unless this util has been compiled with experimental api enabled."
#endif #endif

Loading…
Cancel
Save