*/
#include "hb-ot-shape-normalize-private.hh"
+#include "hb-ot-shape-complex-private.hh"
#include "hb-ot-shape-private.hh"
* with previous base, use that. This needs the itemizer to have this
* knowledge too. We need to provide assistance to the itemizer.
*
- * - When a font does not support a character but supports its decomposition,
- * well, use the decomposition (preferring the canonical decomposition, but
- * falling back to the compatibility decomposition if necessary). The
- * compatibility decomposition is really nice to have, for characters like
- * ellipsis, or various-sized space characters.
+ * - When a font does not support a character but supports its canonical
+ * decomposition, well, use the decomposition.
*
* - The complex shapers can customize the compose and decompose functions to
* offload some of their requirements to the normalizer. For example, the
* Indic shaper may want to disallow recomposing of two matras.
- *
- * - We try compatibility decomposition if decomposing through canonical
- * decomposition alone failed to find a sequence that the font supports.
- * We don't try compatibility decomposition recursively during the canonical
- * decomposition phase. This has minimal impact. There are only a handful
- * of Greek letter that have canonical decompositions that include characters
- * with compatibility decomposition. Those can be found using this command:
- *
- * egrep "`echo -n ';('; grep ';<' UnicodeData.txt | cut -d';' -f1 | tr '\n' '|'; echo ') '`" UnicodeData.txt
*/
-static hb_bool_t
-decompose_func (hb_unicode_funcs_t *unicode,
- hb_codepoint_t ab,
- hb_codepoint_t *a,
- hb_codepoint_t *b)
+static bool
+decompose_unicode (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t ab,
+ hb_codepoint_t *a,
+ hb_codepoint_t *b)
{
- /* XXX FIXME, move these to complex shapers and propagage to normalizer.*/
- switch (ab) {
- case 0x0AC9 : return false;
-
- case 0x0931 : return false;
- case 0x0B94 : return false;
-
- /* These ones have Unicode decompositions, but we do it
- * this way to be close to what Uniscribe does. */
- case 0x0DDA : *a = 0x0DD9; *b= 0x0DDA; return true;
- case 0x0DDC : *a = 0x0DD9; *b= 0x0DDC; return true;
- case 0x0DDD : *a = 0x0DD9; *b= 0x0DDD; return true;
- case 0x0DDE : *a = 0x0DD9; *b= 0x0DDE; return true;
-
- case 0x0F77 : *a = 0x0FB2; *b= 0x0F81; return true;
- case 0x0F79 : *a = 0x0FB3; *b= 0x0F81; return true;
- case 0x17BE : *a = 0x17C1; *b= 0x17BE; return true;
- case 0x17BF : *a = 0x17C1; *b= 0x17BF; return true;
- case 0x17C0 : *a = 0x17C1; *b= 0x17C0; return true;
- case 0x17C4 : *a = 0x17C1; *b= 0x17C4; return true;
- case 0x17C5 : *a = 0x17C1; *b= 0x17C5; return true;
- case 0x1925 : *a = 0x1920; *b= 0x1923; return true;
- case 0x1926 : *a = 0x1920; *b= 0x1924; return true;
- case 0x1B3C : *a = 0x1B42; *b= 0x1B3C; return true;
- case 0x1112E : *a = 0x11127; *b= 0x11131; return true;
- case 0x1112F : *a = 0x11127; *b= 0x11132; return true;
-#if 0
- case 0x0B57 : *a = 0xno decomp, -> RIGHT; return true;
- case 0x1C29 : *a = 0xno decomp, -> LEFT; return true;
- case 0xA9C0 : *a = 0xno decomp, -> RIGHT; return true;
- case 0x111BF : *a = 0xno decomp, -> ABOVE; return true;
-#endif
- }
- return unicode->decompose (ab, a, b);
+ return (bool) c->unicode->decompose (ab, a, b);
}
-static hb_bool_t
-compose_func (hb_unicode_funcs_t *unicode,
- hb_codepoint_t a,
- hb_codepoint_t b,
- hb_codepoint_t *ab)
+static bool
+compose_unicode (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t a,
+ hb_codepoint_t b,
+ hb_codepoint_t *ab)
{
- /* XXX, this belongs to indic normalizer. */
- if ((FLAG (unicode->general_category (a)) &
- (FLAG (HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK) |
- FLAG (HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) |
- FLAG (HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK))))
- return false;
- /* XXX, add composition-exclusion exceptions to Indic shaper. */
- if (a == 0x09AF && b == 0x09BC) { *ab = 0x09DF; return true; }
-
- /* XXX, these belong to the hebew / default shaper. */
- /* Hebrew presentation-form shaping.
- * https://bugzilla.mozilla.org/show_bug.cgi?id=728866 */
- // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA;
- // note that some letters do not have a dagesh presForm encoded
- static const hb_codepoint_t sDageshForms[0x05EA - 0x05D0 + 1] = {
- 0xFB30, // ALEF
- 0xFB31, // BET
- 0xFB32, // GIMEL
- 0xFB33, // DALET
- 0xFB34, // HE
- 0xFB35, // VAV
- 0xFB36, // ZAYIN
- 0, // HET
- 0xFB38, // TET
- 0xFB39, // YOD
- 0xFB3A, // FINAL KAF
- 0xFB3B, // KAF
- 0xFB3C, // LAMED
- 0, // FINAL MEM
- 0xFB3E, // MEM
- 0, // FINAL NUN
- 0xFB40, // NUN
- 0xFB41, // SAMEKH
- 0, // AYIN
- 0xFB43, // FINAL PE
- 0xFB44, // PE
- 0, // FINAL TSADI
- 0xFB46, // TSADI
- 0xFB47, // QOF
- 0xFB48, // RESH
- 0xFB49, // SHIN
- 0xFB4A // TAV
- };
-
- hb_bool_t found = unicode->compose (a, b, ab);
-
- if (!found && (b & ~0x7F) == 0x0580) {
- // special-case Hebrew presentation forms that are excluded from
- // standard normalization, but wanted for old fonts
- switch (b) {
- case 0x05B4: // HIRIQ
- if (a == 0x05D9) { // YOD
- *ab = 0xFB1D;
- found = true;
- }
- break;
- case 0x05B7: // patah
- if (a == 0x05F2) { // YIDDISH YOD YOD
- *ab = 0xFB1F;
- found = true;
- } else if (a == 0x05D0) { // ALEF
- *ab = 0xFB2E;
- found = true;
- }
- break;
- case 0x05B8: // QAMATS
- if (a == 0x05D0) { // ALEF
- *ab = 0xFB2F;
- found = true;
- }
- break;
- case 0x05B9: // HOLAM
- if (a == 0x05D5) { // VAV
- *ab = 0xFB4B;
- found = true;
- }
- break;
- case 0x05BC: // DAGESH
- if (a >= 0x05D0 && a <= 0x05EA) {
- *ab = sDageshForms[a - 0x05D0];
- found = (*ab != 0);
- } else if (a == 0xFB2A) { // SHIN WITH SHIN DOT
- *ab = 0xFB2C;
- found = true;
- } else if (a == 0xFB2B) { // SHIN WITH SIN DOT
- *ab = 0xFB2D;
- found = true;
- }
- break;
- case 0x05BF: // RAFE
- switch (a) {
- case 0x05D1: // BET
- *ab = 0xFB4C;
- found = true;
- break;
- case 0x05DB: // KAF
- *ab = 0xFB4D;
- found = true;
- break;
- case 0x05E4: // PE
- *ab = 0xFB4E;
- found = true;
- break;
- }
- break;
- case 0x05C1: // SHIN DOT
- if (a == 0x05E9) { // SHIN
- *ab = 0xFB2A;
- found = true;
- } else if (a == 0xFB49) { // SHIN WITH DAGESH
- *ab = 0xFB2C;
- found = true;
- }
- break;
- case 0x05C2: // SIN DOT
- if (a == 0x05E9) { // SHIN
- *ab = 0xFB2B;
- found = true;
- } else if (a == 0xFB49) { // SHIN WITH DAGESH
- *ab = 0xFB2D;
- found = true;
- }
- break;
- }
- }
-
- return found;
+ return (bool) c->unicode->compose (a, b, ab);
}
-
static inline void
set_glyph (hb_glyph_info_t &info, hb_font_t *font)
{
- font->get_glyph (info.codepoint, 0, &info.glyph_index());
+ (void) font->get_nominal_glyph (info.codepoint, &info.glyph_index());
}
static inline void
output_char (hb_buffer_t *buffer, hb_codepoint_t unichar, hb_codepoint_t glyph)
{
buffer->cur().glyph_index() = glyph;
- buffer->output_glyph (unichar);
- _hb_glyph_info_set_unicode_props (&buffer->prev(), buffer->unicode);
+ buffer->output_glyph (unichar); /* This is very confusing indeed. */
+ _hb_glyph_info_set_unicode_props (&buffer->prev(), buffer);
}
static inline void
/* Returns 0 if didn't decompose, number of resulting characters otherwise. */
static inline unsigned int
-decompose (hb_font_t *font, hb_buffer_t *buffer, bool shortest, hb_codepoint_t ab)
+decompose (const hb_ot_shape_normalize_context_t *c, bool shortest, hb_codepoint_t ab)
{
hb_codepoint_t a, b, a_glyph, b_glyph;
+ hb_buffer_t * const buffer = c->buffer;
+ hb_font_t * const font = c->font;
- if (!decompose_func (buffer->unicode, ab, &a, &b) ||
- (b && !font->get_glyph (b, 0, &b_glyph)))
+ if (!c->decompose (c, ab, &a, &b) ||
+ (b && !font->get_nominal_glyph (b, &b_glyph)))
return 0;
- bool has_a = font->get_glyph (a, 0, &a_glyph);
+ bool has_a = (bool) font->get_nominal_glyph (a, &a_glyph);
if (shortest && has_a) {
/* Output a and b */
output_char (buffer, a, a_glyph);
}
unsigned int ret;
- if ((ret = decompose (font, buffer, shortest, a))) {
+ if ((ret = decompose (c, shortest, a))) {
if (b) {
output_char (buffer, b, b_glyph);
return ret + 1;
return 0;
}
-/* Returns 0 if didn't decompose, number of resulting characters otherwise. */
-static inline bool
-decompose_compatibility (hb_font_t *font, hb_buffer_t *buffer, hb_codepoint_t u)
-{
- unsigned int len, i;
- hb_codepoint_t decomposed[HB_UNICODE_MAX_DECOMPOSITION_LEN];
- hb_codepoint_t glyphs[HB_UNICODE_MAX_DECOMPOSITION_LEN];
-
- len = buffer->unicode->decompose_compatibility (u, decomposed);
- if (!len)
- return 0;
-
- for (i = 0; i < len; i++)
- if (!font->get_glyph (decomposed[i], 0, &glyphs[i]))
- return 0;
-
- for (i = 0; i < len; i++)
- output_char (buffer, decomposed[i], glyphs[i]);
-
- return len;
-}
-
-/* Returns true if recomposition may be benefitial. */
-static inline bool
-decompose_current_character (hb_font_t *font, hb_buffer_t *buffer, bool shortest)
+static inline void
+decompose_current_character (const hb_ot_shape_normalize_context_t *c, bool shortest)
{
+ hb_buffer_t * const buffer = c->buffer;
+ hb_codepoint_t u = buffer->cur().codepoint;
hb_codepoint_t glyph;
- unsigned int len = 1;
- /* Kind of a cute waterfall here... */
- if (shortest && font->get_glyph (buffer->cur().codepoint, 0, &glyph))
+ if (shortest && c->font->get_nominal_glyph (u, &glyph))
+ {
next_char (buffer, glyph);
- else if ((len = decompose (font, buffer, shortest, buffer->cur().codepoint)))
+ return;
+ }
+
+ if (decompose (c, shortest, u))
+ {
skip_char (buffer);
- else if (!shortest && font->get_glyph (buffer->cur().codepoint, 0, &glyph))
+ return;
+ }
+
+ if (!shortest && c->font->get_nominal_glyph (u, &glyph))
+ {
next_char (buffer, glyph);
- else if ((len = decompose_compatibility (font, buffer, buffer->cur().codepoint)))
- skip_char (buffer);
- else
- next_char (buffer, glyph); /* glyph is initialized in earlier branches. */
+ return;
+ }
+
+ if (_hb_glyph_info_is_unicode_space (&buffer->cur()))
+ {
+ hb_codepoint_t space_glyph;
+ hb_unicode_funcs_t::space_t space_type = buffer->unicode->space_fallback_type (u);
+ if (space_type != hb_unicode_funcs_t::NOT_SPACE && c->font->get_nominal_glyph (0x0020u, &space_glyph))
+ {
+ _hb_glyph_info_set_unicode_space_fallback_type (&buffer->cur(), space_type);
+ next_char (buffer, space_glyph);
+ buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK;
+ return;
+ }
+ }
+
+ if (u == 0x2011u)
+ {
+ /* U+2011 is the only sensible character that is a no-break version of another character
+ * and not a space. The space ones are handled already. Handle this lone one. */
+ hb_codepoint_t other_glyph;
+ if (c->font->get_nominal_glyph (0x2010u, &other_glyph))
+ {
+ next_char (buffer, other_glyph);
+ return;
+ }
+ }
- /*
- * A recomposition would only be useful if we decomposed into at least three
- * characters...
- */
- return len > 2;
+ next_char (buffer, glyph); /* glyph is initialized in earlier branches. */
}
static inline void
-handle_variation_selector_cluster (hb_font_t *font, hb_buffer_t *buffer, unsigned int end)
+handle_variation_selector_cluster (const hb_ot_shape_normalize_context_t *c, unsigned int end, bool short_circuit)
{
- for (; buffer->idx < end - 1;) {
+ /* TODO Currently if there's a variation-selector we give-up, it's just too hard. */
+ hb_buffer_t * const buffer = c->buffer;
+ hb_font_t * const font = c->font;
+ for (; buffer->idx < end - 1 && !buffer->in_error;) {
if (unlikely (buffer->unicode->is_variation_selector (buffer->cur(+1).codepoint))) {
/* The next two lines are some ugly lines... But work. */
- font->get_glyph (buffer->cur().codepoint, buffer->cur(+1).codepoint, &buffer->cur().glyph_index());
- buffer->replace_glyphs (2, 1, &buffer->cur().codepoint);
+ if (font->get_variation_glyph (buffer->cur().codepoint, buffer->cur(+1).codepoint, &buffer->cur().glyph_index()))
+ {
+ buffer->replace_glyphs (2, 1, &buffer->cur().codepoint);
+ }
+ else
+ {
+ /* Just pass on the two characters separately, let GSUB do its magic. */
+ set_glyph (buffer->cur(), font);
+ buffer->next_glyph ();
+ set_glyph (buffer->cur(), font);
+ buffer->next_glyph ();
+ }
+ /* Skip any further variation selectors. */
+ while (buffer->idx < end && unlikely (buffer->unicode->is_variation_selector (buffer->cur().codepoint)))
+ {
+ set_glyph (buffer->cur(), font);
+ buffer->next_glyph ();
+ }
} else {
set_glyph (buffer->cur(), font);
buffer->next_glyph ();
}
}
-/* Returns true if recomposition may be benefitial. */
-static inline bool
-decompose_multi_char_cluster (hb_font_t *font, hb_buffer_t *buffer, unsigned int end)
+static inline void
+decompose_multi_char_cluster (const hb_ot_shape_normalize_context_t *c, unsigned int end, bool short_circuit)
{
- /* TODO Currently if there's a variation-selector we give-up, it's just too hard. */
- for (unsigned int i = buffer->idx; i < end; i++)
+ hb_buffer_t * const buffer = c->buffer;
+ for (unsigned int i = buffer->idx; i < end && !buffer->in_error; i++)
if (unlikely (buffer->unicode->is_variation_selector (buffer->info[i].codepoint))) {
- handle_variation_selector_cluster (font, buffer, end);
- return false;
+ handle_variation_selector_cluster (c, end, short_circuit);
+ return;
}
- while (buffer->idx < end)
- decompose_current_character (font, buffer, false);
- /* We can be smarter here and only return true if there are at least two ccc!=0 marks.
- * But does not matter. */
- return true;
+ while (buffer->idx < end && !buffer->in_error)
+ decompose_current_character (c, short_circuit);
}
-static inline bool
-decompose_cluster (hb_font_t *font, hb_buffer_t *buffer, bool recompose, unsigned int end)
+static inline void
+decompose_cluster (const hb_ot_shape_normalize_context_t *c, unsigned int end, bool might_short_circuit, bool always_short_circuit)
{
- if (likely (buffer->idx + 1 == end))
- return decompose_current_character (font, buffer, recompose);
+ if (likely (c->buffer->idx + 1 == end))
+ decompose_current_character (c, might_short_circuit);
else
- return decompose_multi_char_cluster (font, buffer, end);
+ decompose_multi_char_cluster (c, end, always_short_circuit);
}
void
-_hb_ot_shape_normalize (hb_font_t *font, hb_buffer_t *buffer,
- hb_ot_shape_normalization_mode_t mode)
+_hb_ot_shape_normalize (const hb_ot_shape_plan_t *plan,
+ hb_buffer_t *buffer,
+ hb_font_t *font)
{
- bool recompose = mode != HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED;
- bool can_use_recompose = false;
+ if (unlikely (!buffer->len)) return;
+
+ _hb_buffer_assert_unicode_vars (buffer);
+
+ hb_ot_shape_normalization_mode_t mode = plan->shaper->normalization_preference;
+ const hb_ot_shape_normalize_context_t c = {
+ plan,
+ buffer,
+ font,
+ buffer->unicode,
+ plan->shaper->decompose ? plan->shaper->decompose : decompose_unicode,
+ plan->shaper->compose ? plan->shaper->compose : compose_unicode
+ };
+
+ bool always_short_circuit = mode == HB_OT_SHAPE_NORMALIZATION_MODE_NONE;
+ bool might_short_circuit = always_short_circuit ||
+ (mode != HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED &&
+ mode != HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT);
unsigned int count;
/* We do a fairly straightforward yet custom normalization process in three
buffer->clear_output ();
count = buffer->len;
- for (buffer->idx = 0; buffer->idx < count;)
+ for (buffer->idx = 0; buffer->idx < count && !buffer->in_error;)
{
unsigned int end;
for (end = buffer->idx + 1; end < count; end++)
- if (buffer->cur().cluster != buffer->info[end].cluster)
+ if (likely (!HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&buffer->info[end]))))
break;
- can_use_recompose = decompose_cluster (font, buffer, recompose, end) || can_use_recompose;
+ decompose_cluster (&c, end, might_short_circuit, always_short_circuit);
}
buffer->swap_buffers ();
- if (mode != HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_FULL && !can_use_recompose)
- return; /* Done! */
-
-
/* Second round, reorder (inplace) */
count = buffer->len;
if (_hb_glyph_info_get_modified_combining_class (&buffer->info[end]) == 0)
break;
- /* We are going to do a bubble-sort. Only do this if the
- * sequence is short. Doing it on long sequences can result
- * in an O(n^2) DoS. */
+ /* We are going to do a O(n^2). Only do this if the sequence is short. */
if (end - i > 10) {
i = end;
continue;
}
- hb_bubble_sort (buffer->info + i, end - i, compare_combining_class);
+ buffer->sort (i, end, compare_combining_class);
i = end;
}
- if (!recompose)
+ if (mode == HB_OT_SHAPE_NORMALIZATION_MODE_NONE ||
+ mode == HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED)
return;
/* Third round, recompose */
count = buffer->len;
unsigned int starter = 0;
buffer->next_glyph ();
- while (buffer->idx < count)
+ while (buffer->idx < count && !buffer->in_error)
{
hb_codepoint_t composed, glyph;
- if (/* If mode is NOT COMPOSED_FULL (ie. it's COMPOSED_DIACRITICS), we don't try to
- * compose a CCC=0 character with it's preceding starter. */
- (mode == HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_FULL ||
- _hb_glyph_info_get_modified_combining_class (&buffer->cur()) != 0) &&
+ if (/* We don't try to compose a non-mark character with it's preceding starter.
+ * This is both an optimization to avoid trying to compose every two neighboring
+ * glyphs in most scripts AND a desired feature for Hangul. Apparently Hangul
+ * fonts are not designed to mix-and-match pre-composed syllables and Jamo. */
+ HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&buffer->cur())) &&
/* If there's anything between the starter and this char, they should have CCC
* smaller than this character's. */
(starter == buffer->out_len - 1 ||
_hb_glyph_info_get_modified_combining_class (&buffer->prev()) < _hb_glyph_info_get_modified_combining_class (&buffer->cur())) &&
/* And compose. */
- compose_func (buffer->unicode,
- buffer->out_info[starter].codepoint,
- buffer->cur().codepoint,
- &composed) &&
+ c.compose (&c,
+ buffer->out_info[starter].codepoint,
+ buffer->cur().codepoint,
+ &composed) &&
/* And the font has glyph for the composite. */
- font->get_glyph (composed, 0, &glyph))
+ font->get_nominal_glyph (composed, &glyph))
{
/* Composes. */
buffer->next_glyph (); /* Copy to out-buffer. */
return;
buffer->merge_out_clusters (starter, buffer->out_len);
buffer->out_len--; /* Remove the second composable. */
- buffer->out_info[starter].codepoint = composed; /* Modify starter and carry on. */
- set_glyph (buffer->out_info[starter], font);
- _hb_glyph_info_set_unicode_props (&buffer->out_info[starter], buffer->unicode);
+ /* Modify starter and carry on. */
+ buffer->out_info[starter].codepoint = composed;
+ buffer->out_info[starter].glyph_index() = glyph;
+ _hb_glyph_info_set_unicode_props (&buffer->out_info[starter], buffer);
continue;
}