blob: 9a59c0855689564753a91206729f5e163016453e [file] [log] [blame]
/*
* Copyright © 2009 Red Hat, Inc.
* Copyright © 2012 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.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod
*/
#include "hb-private.hh"
#include "hb-shaper-private.hh"
#include "hb-shape-plan-private.hh"
#include "hb-buffer-private.hh"
#include "hb-font-private.hh"
static bool
parse_space (const char **pp, const char *end)
{
while (*pp < end && ISSPACE (**pp))
(*pp)++;
return true;
}
static bool
parse_char (const char **pp, const char *end, char c)
{
parse_space (pp, end);
if (*pp == end || **pp != c)
return false;
(*pp)++;
return true;
}
static bool
parse_uint (const char **pp, const char *end, unsigned int *pv)
{
char buf[32];
unsigned int len = MIN (ARRAY_LENGTH (buf) - 1, (unsigned int) (end - *pp));
strncpy (buf, *pp, len);
buf[len] = '\0';
char *p = buf;
char *pend = p;
unsigned int v;
/* Intentionally use strtol instead of strtoul, such that
* -1 turns into "big number"... */
errno = 0;
v = strtol (p, &pend, 0);
if (errno || p == pend)
return false;
*pv = v;
*pp += pend - p;
return true;
}
static bool
parse_bool (const char **pp, const char *end, unsigned int *pv)
{
parse_space (pp, end);
const char *p = *pp;
while (*pp < end && ISALPHA(**pp))
(*pp)++;
/* CSS allows on/off as aliases 1/0. */
if (*pp - p == 2 || 0 == strncmp (p, "on", 2))
*pv = 1;
else if (*pp - p == 3 || 0 == strncmp (p, "off", 2))
*pv = 0;
else
return false;
return true;
}
static bool
parse_feature_value_prefix (const char **pp, const char *end, hb_feature_t *feature)
{
if (parse_char (pp, end, '-'))
feature->value = 0;
else {
parse_char (pp, end, '+');
feature->value = 1;
}
return true;
}
static bool
parse_feature_tag (const char **pp, const char *end, hb_feature_t *feature)
{
parse_space (pp, end);
char quote = 0;
if (*pp < end && (**pp == '\'' || **pp == '"'))
{
quote = **pp;
(*pp)++;
}
const char *p = *pp;
while (*pp < end && ISALNUM(**pp))
(*pp)++;
if (p == *pp || *pp - p > 4)
return false;
feature->tag = hb_tag_from_string (p, *pp - p);
if (quote)
{
/* CSS expects exactly four bytes. And we only allow quotations for
* CSS compatibility. So, enforce the length. */
if (*pp - p != 4)
return false;
if (*pp == end || **pp != quote)
return false;
(*pp)++;
}
return true;
}
static bool
parse_feature_indices (const char **pp, const char *end, hb_feature_t *feature)
{
parse_space (pp, end);
bool has_start;
feature->start = 0;
feature->end = (unsigned int) -1;
if (!parse_char (pp, end, '['))
return true;
has_start = parse_uint (pp, end, &feature->start);
if (parse_char (pp, end, ':')) {
parse_uint (pp, end, &feature->end);
} else {
if (has_start)
feature->end = feature->start + 1;
}
return parse_char (pp, end, ']');
}
static bool
parse_feature_value_postfix (const char **pp, const char *end, hb_feature_t *feature)
{
bool had_equal = parse_char (pp, end, '=');
bool had_value = parse_uint (pp, end, &feature->value) ||
parse_bool (pp, end, &feature->value);
/* CSS doesn't use equal-sign between tag and value.
* If there was an equal-sign, then there *must* be a value.
* A value without an eqaul-sign is ok, but not required. */
return !had_equal || had_value;
}
static bool
parse_one_feature (const char **pp, const char *end, hb_feature_t *feature)
{
return parse_feature_value_prefix (pp, end, feature) &&
parse_feature_tag (pp, end, feature) &&
parse_feature_indices (pp, end, feature) &&
parse_feature_value_postfix (pp, end, feature) &&
parse_space (pp, end) &&
*pp == end;
}
/**
* hb_feature_from_string:
* @str: (array length=len):
* @len:
* @feature: (out) (optional):
*
*
*
* Return value:
*
* Since: 1.0
**/
hb_bool_t
hb_feature_from_string (const char *str, int len,
hb_feature_t *feature)
{
hb_feature_t feat;
if (len < 0)
len = strlen (str);
if (likely (parse_one_feature (&str, str + len, &feat)))
{
if (feature)
*feature = feat;
return true;
}
if (feature)
memset (feature, 0, sizeof (*feature));
return false;
}
/**
* hb_feature_to_string:
* @feature:
* @buf: (array length=size):
* @size:
*
*
*
* Since: 1.0
**/
void
hb_feature_to_string (hb_feature_t *feature,
char *buf, unsigned int size)
{
if (unlikely (!size)) return;
char s[128];
unsigned int len = 0;
if (feature->value == 0)
s[len++] = '-';
hb_tag_to_string (feature->tag, s + len);
len += 4;
while (len && s[len - 1] == ' ')
len--;
if (feature->start != 0 || feature->end != (unsigned int) -1)
{
s[len++] = '[';
if (feature->start)
len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->start));
if (feature->end != feature->start + 1) {
s[len++] = ':';
if (feature->end != (unsigned int) -1)
len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->end));
}
s[len++] = ']';
}
if (feature->value > 1)
{
s[len++] = '=';
len += MAX (0, snprintf (s + len, ARRAY_LENGTH (s) - len, "%u", feature->value));
}
assert (len < ARRAY_LENGTH (s));
len = MIN (len, size - 1);
memcpy (buf, s, len);
buf[len] = '\0';
}
static const char **static_shaper_list;
#ifdef HB_USE_ATEXIT
static
void free_static_shaper_list (void)
{
free (static_shaper_list);
}
#endif
/**
* hb_shape_list_shapers:
*
*
*
* Return value: (transfer none):
*
* Since: 1.0
**/
const char **
hb_shape_list_shapers (void)
{
retry:
const char **shaper_list = (const char **) hb_atomic_ptr_get (&static_shaper_list);
if (unlikely (!shaper_list))
{
/* Not found; allocate one. */
shaper_list = (const char **) calloc (1 + HB_SHAPERS_COUNT, sizeof (const char *));
if (unlikely (!shaper_list)) {
static const char *nil_shaper_list[] = {NULL};
return nil_shaper_list;
}
const hb_shaper_pair_t *shapers = _hb_shapers_get ();
unsigned int i;
for (i = 0; i < HB_SHAPERS_COUNT; i++)
shaper_list[i] = shapers[i].name;
shaper_list[i] = NULL;
if (!hb_atomic_ptr_cmpexch (&static_shaper_list, NULL, shaper_list)) {
free (shaper_list);
goto retry;
}
#ifdef HB_USE_ATEXIT
atexit (free_static_shaper_list); /* First person registers atexit() callback. */
#endif
}
return shaper_list;
}
/**
* hb_shape_full:
* @font: a font.
* @buffer: a buffer.
* @features: (array length=num_features):
* @num_features:
* @shaper_list: (array zero-terminated=1):
*
*
*
* Return value:
*
* Since: 1.0
**/
hb_bool_t
hb_shape_full (hb_font_t *font,
hb_buffer_t *buffer,
const hb_feature_t *features,
unsigned int num_features,
const char * const *shaper_list)
{
if (unlikely (!buffer->len))
return true;
assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE);
hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached (font->face, &buffer->props, features, num_features, shaper_list);
hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
hb_shape_plan_destroy (shape_plan);
if (res)
buffer->content_type = HB_BUFFER_CONTENT_TYPE_GLYPHS;
return res;
}
/**
* hb_shape:
* @font: a font.
* @buffer: a buffer.
* @features: (array length=num_features):
* @num_features:
*
*
*
* Since: 1.0
**/
void
hb_shape (hb_font_t *font,
hb_buffer_t *buffer,
const hb_feature_t *features,
unsigned int num_features)
{
hb_shape_full (font, buffer, features, num_features, NULL);
}