Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright  2010,2012  Google, Inc.
      3  *
      4  *  This is part of HarfBuzz, a text shaping library.
      5  *
      6  * Permission is hereby granted, without written agreement and without
      7  * license or royalty fees, to use, copy, modify, and distribute this
      8  * software and its documentation for any purpose, provided that the
      9  * above copyright notice and the following two paragraphs appear in
     10  * all copies of this software.
     11  *
     12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
     13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
     14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
     15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
     16  * DAMAGE.
     17  *
     18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
     19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
     20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
     21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
     22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
     23  *
     24  * Google Author(s): Behdad Esfahbod
     25  */
     26 
     27 #include "hb-private.hh"
     28 #include "hb-debug.hh"
     29 #include "hb-ot-shape-complex-arabic-private.hh"
     30 #include "hb-ot-shape-private.hh"
     31 
     32 
     33 /* buffer var allocations */
     34 #define arabic_shaping_action() complex_var_u8_0() /* arabic shaping action */
     35 
     36 #define HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH HB_BUFFER_SCRATCH_FLAG_COMPLEX0
     37 
     38 /* See:
     39  * https://github.com/harfbuzz/harfbuzz/commit/6e6f82b6f3dde0fc6c3c7d991d9ec6cfff57823d#commitcomment-14248516 */
     40 #define HB_ARABIC_GENERAL_CATEGORY_IS_WORD(gen_cat) \
     41 	(FLAG_UNSAFE (gen_cat) & \
     42 	 (FLAG (HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED) | \
     43 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE) | \
     44 	  /*FLAG (HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER) |*/ \
     45 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER) | \
     46 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER) | \
     47 	  /*FLAG (HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER) |*/ \
     48 	  /*FLAG (HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER) |*/ \
     49 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK) | \
     50 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) | \
     51 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) | \
     52 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) | \
     53 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER) | \
     54 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER) | \
     55 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL) | \
     56 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL) | \
     57 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL) | \
     58 	  FLAG (HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL)))
     59 
     60 
     61 /*
     62  * Joining types:
     63  */
     64 
     65 /*
     66  * Bits used in the joining tables
     67  */
     68 enum hb_arabic_joining_type_t {
     69   JOINING_TYPE_U		= 0,
     70   JOINING_TYPE_L		= 1,
     71   JOINING_TYPE_R		= 2,
     72   JOINING_TYPE_D		= 3,
     73   JOINING_TYPE_C		= JOINING_TYPE_D,
     74   JOINING_GROUP_ALAPH		= 4,
     75   JOINING_GROUP_DALATH_RISH	= 5,
     76   NUM_STATE_MACHINE_COLS	= 6,
     77 
     78   JOINING_TYPE_T = 7,
     79   JOINING_TYPE_X = 8  /* means: use general-category to choose between U or T. */
     80 };
     81 
     82 #include "hb-ot-shape-complex-arabic-table.hh"
     83 
     84 static unsigned int get_joining_type (hb_codepoint_t u, hb_unicode_general_category_t gen_cat)
     85 {
     86   unsigned int j_type = joining_type(u);
     87   if (likely (j_type != JOINING_TYPE_X))
     88     return j_type;
     89 
     90   return (FLAG_UNSAFE(gen_cat) &
     91 	  (FLAG(HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) |
     92 	   FLAG(HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) |
     93 	   FLAG(HB_UNICODE_GENERAL_CATEGORY_FORMAT))
     94 	 ) ?  JOINING_TYPE_T : JOINING_TYPE_U;
     95 }
     96 
     97 #define FEATURE_IS_SYRIAC(tag) hb_in_range<unsigned char> ((unsigned char) (tag), '2', '3')
     98 
     99 static const hb_tag_t arabic_features[] =
    100 {
    101   HB_TAG('i','s','o','l'),
    102   HB_TAG('f','i','n','a'),
    103   HB_TAG('f','i','n','2'),
    104   HB_TAG('f','i','n','3'),
    105   HB_TAG('m','e','d','i'),
    106   HB_TAG('m','e','d','2'),
    107   HB_TAG('i','n','i','t'),
    108   HB_TAG_NONE
    109 };
    110 
    111 
    112 /* Same order as the feature array */
    113 enum arabic_action_t {
    114   ISOL,
    115   FINA,
    116   FIN2,
    117   FIN3,
    118   MEDI,
    119   MED2,
    120   INIT,
    121 
    122   NONE,
    123 
    124   ARABIC_NUM_FEATURES = NONE,
    125 
    126   /* We abuse the same byte for other things... */
    127   STCH_FIXED,
    128   STCH_REPEATING,
    129 };
    130 
    131 static const struct arabic_state_table_entry {
    132 	uint8_t prev_action;
    133 	uint8_t curr_action;
    134 	uint16_t next_state;
    135 } arabic_state_table[][NUM_STATE_MACHINE_COLS] =
    136 {
    137   /*   jt_U,          jt_L,          jt_R,          jt_D,          jg_ALAPH,      jg_DALATH_RISH */
    138 
    139   /* State 0: prev was U, not willing to join. */
    140   { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,6}, },
    141 
    142   /* State 1: prev was R or ISOL/ALAPH, not willing to join. */
    143   { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN2,5}, {NONE,ISOL,6}, },
    144 
    145   /* State 2: prev was D/L in ISOL form, willing to join. */
    146   { {NONE,NONE,0}, {NONE,ISOL,2}, {INIT,FINA,1}, {INIT,FINA,3}, {INIT,FINA,4}, {INIT,FINA,6}, },
    147 
    148   /* State 3: prev was D in FINA form, willing to join. */
    149   { {NONE,NONE,0}, {NONE,ISOL,2}, {MEDI,FINA,1}, {MEDI,FINA,3}, {MEDI,FINA,4}, {MEDI,FINA,6}, },
    150 
    151   /* State 4: prev was FINA ALAPH, not willing to join. */
    152   { {NONE,NONE,0}, {NONE,ISOL,2}, {MED2,ISOL,1}, {MED2,ISOL,2}, {MED2,FIN2,5}, {MED2,ISOL,6}, },
    153 
    154   /* State 5: prev was FIN2/FIN3 ALAPH, not willing to join. */
    155   { {NONE,NONE,0}, {NONE,ISOL,2}, {ISOL,ISOL,1}, {ISOL,ISOL,2}, {ISOL,FIN2,5}, {ISOL,ISOL,6}, },
    156 
    157   /* State 6: prev was DALATH/RISH, not willing to join. */
    158   { {NONE,NONE,0}, {NONE,ISOL,2}, {NONE,ISOL,1}, {NONE,ISOL,2}, {NONE,FIN3,5}, {NONE,ISOL,6}, }
    159 };
    160 
    161 
    162 static void
    163 nuke_joiners (const hb_ot_shape_plan_t *plan,
    164 	      hb_font_t *font,
    165 	      hb_buffer_t *buffer);
    166 
    167 static void
    168 arabic_fallback_shape (const hb_ot_shape_plan_t *plan,
    169 		       hb_font_t *font,
    170 		       hb_buffer_t *buffer);
    171 
    172 static void
    173 record_stch (const hb_ot_shape_plan_t *plan,
    174 	     hb_font_t *font,
    175 	     hb_buffer_t *buffer);
    176 
    177 static void
    178 collect_features_arabic (hb_ot_shape_planner_t *plan)
    179 {
    180   hb_ot_map_builder_t *map = &plan->map;
    181 
    182   /* We apply features according to the Arabic spec, with pauses
    183    * in between most.
    184    *
    185    * The pause between init/medi/... and rlig is required.  See eg:
    186    * https://bugzilla.mozilla.org/show_bug.cgi?id=644184
    187    *
    188    * The pauses between init/medi/... themselves are not necessarily
    189    * needed as only one of those features is applied to any character.
    190    * The only difference it makes is when fonts have contextual
    191    * substitutions.  We now follow the order of the spec, which makes
    192    * for better experience if that's what Uniscribe is doing.
    193    *
    194    * At least for Arabic, looks like Uniscribe has a pause between
    195    * rlig and calt.  Otherwise the IranNastaliq's ALLAH ligature won't
    196    * work.  However, testing shows that rlig and calt are applied
    197    * together for Mongolian in Uniscribe.  As such, we only add a
    198    * pause for Arabic, not other scripts.
    199    *
    200    * A pause after calt is required to make KFGQPC Uthmanic Script HAFS
    201    * work correctly.  See https://github.com/harfbuzz/harfbuzz/issues/505
    202    */
    203 
    204   map->add_gsub_pause (nuke_joiners);
    205 
    206   map->add_global_bool_feature (HB_TAG('s','t','c','h'));
    207   map->add_gsub_pause (record_stch);
    208 
    209   map->add_global_bool_feature (HB_TAG('c','c','m','p'));
    210   map->add_global_bool_feature (HB_TAG('l','o','c','l'));
    211 
    212   map->add_gsub_pause (nullptr);
    213 
    214   for (unsigned int i = 0; i < ARABIC_NUM_FEATURES; i++)
    215   {
    216     bool has_fallback = plan->props.script == HB_SCRIPT_ARABIC && !FEATURE_IS_SYRIAC (arabic_features[i]);
    217     map->add_feature (arabic_features[i], 1, has_fallback ? F_HAS_FALLBACK : F_NONE);
    218     map->add_gsub_pause (nullptr);
    219   }
    220 
    221   map->add_feature (HB_TAG('r','l','i','g'), 1, F_GLOBAL|F_HAS_FALLBACK);
    222   if (plan->props.script == HB_SCRIPT_ARABIC)
    223     map->add_gsub_pause (arabic_fallback_shape);
    224 
    225   /* No pause after rclt.  See 98460779bae19e4d64d29461ff154b3527bf8420. */
    226   map->add_global_bool_feature (HB_TAG('r','c','l','t'));
    227   map->add_global_bool_feature (HB_TAG('c','a','l','t'));
    228   map->add_gsub_pause (nullptr);
    229 
    230   /* The spec includes 'cswh'.  Earlier versions of Windows
    231    * used to enable this by default, but testing suggests
    232    * that Windows 8 and later do not enable it by default,
    233    * and spec now says 'Off by default'.
    234    * We disabled this in ae23c24c32.
    235    * Note that IranNastaliq uses this feature extensively
    236    * to fixup broken glyph sequences.  Oh well...
    237    * Test case: U+0643,U+0640,U+0631. */
    238   //map->add_global_bool_feature (HB_TAG('c','s','w','h'));
    239   map->add_global_bool_feature (HB_TAG('m','s','e','t'));
    240 }
    241 
    242 #include "hb-ot-shape-complex-arabic-fallback.hh"
    243 
    244 struct arabic_shape_plan_t
    245 {
    246   ASSERT_POD ();
    247 
    248   /* The "+ 1" in the next array is to accommodate for the "NONE" command,
    249    * which is not an OpenType feature, but this simplifies the code by not
    250    * having to do a "if (... < NONE) ..." and just rely on the fact that
    251    * mask_array[NONE] == 0. */
    252   hb_mask_t mask_array[ARABIC_NUM_FEATURES + 1];
    253 
    254   arabic_fallback_plan_t *fallback_plan;
    255 
    256   unsigned int do_fallback : 1;
    257   unsigned int has_stch : 1;
    258 };
    259 
    260 void *
    261 data_create_arabic (const hb_ot_shape_plan_t *plan)
    262 {
    263   arabic_shape_plan_t *arabic_plan = (arabic_shape_plan_t *) calloc (1, sizeof (arabic_shape_plan_t));
    264   if (unlikely (!arabic_plan))
    265     return nullptr;
    266 
    267   arabic_plan->do_fallback = plan->props.script == HB_SCRIPT_ARABIC;
    268   arabic_plan->has_stch = !!plan->map.get_1_mask (HB_TAG ('s','t','c','h'));
    269   for (unsigned int i = 0; i < ARABIC_NUM_FEATURES; i++) {
    270     arabic_plan->mask_array[i] = plan->map.get_1_mask (arabic_features[i]);
    271     arabic_plan->do_fallback = arabic_plan->do_fallback &&
    272 			       (FEATURE_IS_SYRIAC (arabic_features[i]) ||
    273 			        plan->map.needs_fallback (arabic_features[i]));
    274   }
    275 
    276   return arabic_plan;
    277 }
    278 
    279 void
    280 data_destroy_arabic (void *data)
    281 {
    282   arabic_shape_plan_t *arabic_plan = (arabic_shape_plan_t *) data;
    283 
    284   arabic_fallback_plan_destroy (arabic_plan->fallback_plan);
    285 
    286   free (data);
    287 }
    288 
    289 static void
    290 arabic_joining (hb_buffer_t *buffer)
    291 {
    292   unsigned int count = buffer->len;
    293   hb_glyph_info_t *info = buffer->info;
    294   unsigned int prev = (unsigned int) -1, state = 0;
    295 
    296   /* Check pre-context */
    297   for (unsigned int i = 0; i < buffer->context_len[0]; i++)
    298   {
    299     unsigned int this_type = get_joining_type (buffer->context[0][i], buffer->unicode->general_category (buffer->context[0][i]));
    300 
    301     if (unlikely (this_type == JOINING_TYPE_T))
    302       continue;
    303 
    304     const arabic_state_table_entry *entry = &arabic_state_table[state][this_type];
    305     state = entry->next_state;
    306     break;
    307   }
    308 
    309   for (unsigned int i = 0; i < count; i++)
    310   {
    311     unsigned int this_type = get_joining_type (info[i].codepoint, _hb_glyph_info_get_general_category (&info[i]));
    312 
    313     if (unlikely (this_type == JOINING_TYPE_T)) {
    314       info[i].arabic_shaping_action() = NONE;
    315       continue;
    316     }
    317 
    318     const arabic_state_table_entry *entry = &arabic_state_table[state][this_type];
    319 
    320     if (entry->prev_action != NONE && prev != (unsigned int) -1)
    321     {
    322       info[prev].arabic_shaping_action() = entry->prev_action;
    323       buffer->unsafe_to_break (prev, i + 1);
    324     }
    325 
    326     info[i].arabic_shaping_action() = entry->curr_action;
    327 
    328     prev = i;
    329     state = entry->next_state;
    330   }
    331 
    332   for (unsigned int i = 0; i < buffer->context_len[1]; i++)
    333   {
    334     unsigned int this_type = get_joining_type (buffer->context[1][i], buffer->unicode->general_category (buffer->context[1][i]));
    335 
    336     if (unlikely (this_type == JOINING_TYPE_T))
    337       continue;
    338 
    339     const arabic_state_table_entry *entry = &arabic_state_table[state][this_type];
    340     if (entry->prev_action != NONE && prev != (unsigned int) -1)
    341       info[prev].arabic_shaping_action() = entry->prev_action;
    342     break;
    343   }
    344 }
    345 
    346 static void
    347 mongolian_variation_selectors (hb_buffer_t *buffer)
    348 {
    349   /* Copy arabic_shaping_action() from base to Mongolian variation selectors. */
    350   unsigned int count = buffer->len;
    351   hb_glyph_info_t *info = buffer->info;
    352   for (unsigned int i = 1; i < count; i++)
    353     if (unlikely (hb_in_range<hb_codepoint_t> (info[i].codepoint, 0x180Bu, 0x180Du)))
    354       info[i].arabic_shaping_action() = info[i - 1].arabic_shaping_action();
    355 }
    356 
    357 void
    358 setup_masks_arabic_plan (const arabic_shape_plan_t *arabic_plan,
    359 			 hb_buffer_t               *buffer,
    360 			 hb_script_t                script)
    361 {
    362   HB_BUFFER_ALLOCATE_VAR (buffer, arabic_shaping_action);
    363 
    364   arabic_joining (buffer);
    365   if (script == HB_SCRIPT_MONGOLIAN)
    366     mongolian_variation_selectors (buffer);
    367 
    368   unsigned int count = buffer->len;
    369   hb_glyph_info_t *info = buffer->info;
    370   for (unsigned int i = 0; i < count; i++)
    371     info[i].mask |= arabic_plan->mask_array[info[i].arabic_shaping_action()];
    372 }
    373 
    374 static void
    375 setup_masks_arabic (const hb_ot_shape_plan_t *plan,
    376 		    hb_buffer_t              *buffer,
    377 		    hb_font_t                *font HB_UNUSED)
    378 {
    379   const arabic_shape_plan_t *arabic_plan = (const arabic_shape_plan_t *) plan->data;
    380   setup_masks_arabic_plan (arabic_plan, buffer, plan->props.script);
    381 }
    382 
    383 
    384 static void
    385 nuke_joiners (const hb_ot_shape_plan_t *plan HB_UNUSED,
    386 	      hb_font_t *font HB_UNUSED,
    387 	      hb_buffer_t *buffer)
    388 {
    389   unsigned int count = buffer->len;
    390   hb_glyph_info_t *info = buffer->info;
    391   for (unsigned int i = 0; i < count; i++)
    392     if (_hb_glyph_info_is_zwj (&info[i]))
    393       _hb_glyph_info_flip_joiners (&info[i]);
    394 }
    395 
    396 static void
    397 arabic_fallback_shape (const hb_ot_shape_plan_t *plan,
    398 		       hb_font_t *font,
    399 		       hb_buffer_t *buffer)
    400 {
    401   const arabic_shape_plan_t *arabic_plan = (const arabic_shape_plan_t *) plan->data;
    402 
    403   if (!arabic_plan->do_fallback)
    404     return;
    405 
    406 retry:
    407   arabic_fallback_plan_t *fallback_plan = (arabic_fallback_plan_t *) hb_atomic_ptr_get (&arabic_plan->fallback_plan);
    408   if (unlikely (!fallback_plan))
    409   {
    410     /* This sucks.  We need a font to build the fallback plan... */
    411     fallback_plan = arabic_fallback_plan_create (plan, font);
    412     if (unlikely (!hb_atomic_ptr_cmpexch (&(const_cast<arabic_shape_plan_t *> (arabic_plan))->fallback_plan, nullptr, fallback_plan))) {
    413       arabic_fallback_plan_destroy (fallback_plan);
    414       goto retry;
    415     }
    416   }
    417 
    418   arabic_fallback_plan_shape (fallback_plan, font, buffer);
    419 }
    420 
    421 /*
    422  * Stretch feature: "stch".
    423  * See example here:
    424  * https://www.microsoft.com/typography/OpenTypeDev/syriac/intro.htm
    425  * We implement this in a generic way, such that the Arabic subtending
    426  * marks can use it as well.
    427  */
    428 
    429 static void
    430 record_stch (const hb_ot_shape_plan_t *plan,
    431 	     hb_font_t *font,
    432 	     hb_buffer_t *buffer)
    433 {
    434   const arabic_shape_plan_t *arabic_plan = (const arabic_shape_plan_t *) plan->data;
    435   if (!arabic_plan->has_stch)
    436     return;
    437 
    438   /* 'stch' feature was just applied.  Look for anything that multiplied,
    439    * and record it for stch treatment later.  Note that rtlm, frac, etc
    440    * are applied before stch, but we assume that they didn't result in
    441    * anything multiplying into 5 pieces, so it's safe-ish... */
    442 
    443   unsigned int count = buffer->len;
    444   hb_glyph_info_t *info = buffer->info;
    445   for (unsigned int i = 0; i < count; i++)
    446     if (unlikely (_hb_glyph_info_multiplied (&info[i])))
    447     {
    448       unsigned int comp = _hb_glyph_info_get_lig_comp (&info[i]);
    449       info[i].arabic_shaping_action() = comp % 2 ? STCH_REPEATING : STCH_FIXED;
    450       buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH;
    451     }
    452 }
    453 
    454 static void
    455 apply_stch (const hb_ot_shape_plan_t *plan,
    456 	    hb_buffer_t              *buffer,
    457 	    hb_font_t                *font)
    458 {
    459   if (likely (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH)))
    460     return;
    461 
    462   /* The Arabic shaper currently always processes in RTL mode, so we should
    463    * stretch / position the stretched pieces to the left / preceding glyphs. */
    464 
    465   /* We do a two pass implementation:
    466    * First pass calculates the exact number of extra glyphs we need,
    467    * We then enlarge buffer to have that much room,
    468    * Second pass applies the stretch, copying things to the end of buffer.
    469    */
    470 
    471   int sign = font->x_scale < 0 ? -1 : +1;
    472   unsigned int extra_glyphs_needed = 0; // Set during MEASURE, used during CUT
    473   typedef enum { MEASURE, CUT } step_t;
    474 
    475   for (step_t step = MEASURE; step <= CUT; step = (step_t) (step + 1))
    476   {
    477     unsigned int count = buffer->len;
    478     hb_glyph_info_t *info = buffer->info;
    479     hb_glyph_position_t *pos = buffer->pos;
    480     unsigned int new_len = count + extra_glyphs_needed; // write head during CUT
    481     unsigned int j = new_len;
    482     for (unsigned int i = count; i; i--)
    483     {
    484       if (!hb_in_range<unsigned> (info[i - 1].arabic_shaping_action(), STCH_FIXED, STCH_REPEATING))
    485       {
    486         if (step == CUT)
    487 	{
    488 	  --j;
    489 	  info[j] = info[i - 1];
    490 	  pos[j] = pos[i - 1];
    491 	}
    492         continue;
    493       }
    494 
    495       /* Yay, justification! */
    496 
    497       hb_position_t w_total = 0; // Total to be filled
    498       hb_position_t w_fixed = 0; // Sum of fixed tiles
    499       hb_position_t w_repeating = 0; // Sum of repeating tiles
    500       int n_fixed = 0;
    501       int n_repeating = 0;
    502 
    503       unsigned int end = i;
    504       while (i &&
    505 	     hb_in_range<unsigned> (info[i - 1].arabic_shaping_action(), STCH_FIXED, STCH_REPEATING))
    506       {
    507 	i--;
    508 	hb_position_t width = font->get_glyph_h_advance (info[i].codepoint);
    509 	if (info[i].arabic_shaping_action() == STCH_FIXED)
    510 	{
    511 	  w_fixed += width;
    512 	  n_fixed++;
    513 	}
    514 	else
    515 	{
    516 	  w_repeating += width;
    517 	  n_repeating++;
    518 	}
    519       }
    520       unsigned int start = i;
    521       unsigned int context = i;
    522       while (context &&
    523 	     !hb_in_range<unsigned> (info[context - 1].arabic_shaping_action(), STCH_FIXED, STCH_REPEATING) &&
    524 	     (_hb_glyph_info_is_default_ignorable (&info[context - 1]) ||
    525 	      HB_ARABIC_GENERAL_CATEGORY_IS_WORD (_hb_glyph_info_get_general_category (&info[context - 1]))))
    526       {
    527 	context--;
    528 	w_total += pos[context].x_advance;
    529       }
    530       i++; // Don't touch i again.
    531 
    532       DEBUG_MSG (ARABIC, nullptr, "%s stretch at (%d,%d,%d)",
    533 		 step == MEASURE ? "measuring" : "cutting", context, start, end);
    534       DEBUG_MSG (ARABIC, nullptr, "rest of word:    count=%d width %d", start - context, w_total);
    535       DEBUG_MSG (ARABIC, nullptr, "fixed tiles:     count=%d width=%d", n_fixed, w_fixed);
    536       DEBUG_MSG (ARABIC, nullptr, "repeating tiles: count=%d width=%d", n_repeating, w_repeating);
    537 
    538       /* Number of additional times to repeat each repeating tile. */
    539       int n_copies = 0;
    540 
    541       hb_position_t w_remaining = w_total - w_fixed;
    542       if (sign * w_remaining > sign * w_repeating && sign * w_repeating > 0)
    543 	n_copies = (sign * w_remaining) / (sign * w_repeating) - 1;
    544 
    545       /* See if we can improve the fit by adding an extra repeat and squeezing them together a bit. */
    546       hb_position_t extra_repeat_overlap = 0;
    547       hb_position_t shortfall = sign * w_remaining - sign * w_repeating * (n_copies + 1);
    548       if (shortfall > 0 && n_repeating > 0)
    549       {
    550         ++n_copies;
    551         hb_position_t excess = (n_copies + 1) * sign * w_repeating - sign * w_remaining;
    552         if (excess > 0)
    553           extra_repeat_overlap = excess / (n_copies * n_repeating);
    554       }
    555 
    556       if (step == MEASURE)
    557       {
    558 	extra_glyphs_needed += n_copies * n_repeating;
    559 	DEBUG_MSG (ARABIC, nullptr, "will add extra %d copies of repeating tiles", n_copies);
    560       }
    561       else
    562       {
    563 	buffer->unsafe_to_break (context, end);
    564 	hb_position_t x_offset = 0;
    565 	for (unsigned int k = end; k > start; k--)
    566 	{
    567 	  hb_position_t width = font->get_glyph_h_advance (info[k - 1].codepoint);
    568 
    569 	  unsigned int repeat = 1;
    570 	  if (info[k - 1].arabic_shaping_action() == STCH_REPEATING)
    571 	    repeat += n_copies;
    572 
    573 	  DEBUG_MSG (ARABIC, nullptr, "appending %d copies of glyph %d; j=%d",
    574 		     repeat, info[k - 1].codepoint, j);
    575 	  for (unsigned int n = 0; n < repeat; n++)
    576 	  {
    577 	    x_offset -= width;
    578 	    if (n > 0)
    579 	      x_offset += extra_repeat_overlap;
    580 	    pos[k - 1].x_offset = x_offset;
    581 	    /* Append copy. */
    582 	    --j;
    583 	    info[j] = info[k - 1];
    584 	    pos[j] = pos[k - 1];
    585 	  }
    586 	}
    587       }
    588     }
    589 
    590     if (step == MEASURE)
    591     {
    592       if (unlikely (!buffer->ensure (count + extra_glyphs_needed)))
    593         break;
    594     }
    595     else
    596     {
    597       assert (j == 0);
    598       buffer->len = new_len;
    599     }
    600   }
    601 }
    602 
    603 
    604 static void
    605 postprocess_glyphs_arabic (const hb_ot_shape_plan_t *plan,
    606 			   hb_buffer_t              *buffer,
    607 			   hb_font_t                *font)
    608 {
    609   apply_stch (plan, buffer, font);
    610 
    611   HB_BUFFER_DEALLOCATE_VAR (buffer, arabic_shaping_action);
    612 }
    613 
    614 /* http://www.unicode.org/reports/tr53/tr53-1.pdf */
    615 
    616 static hb_codepoint_t
    617 modifier_combining_marks[] =
    618 {
    619   0x0654u, /* ARABIC HAMZA ABOVE */
    620   0x0655u, /* ARABIC HAMZA BELOW */
    621   0x0658u, /* ARABIC MARK NOON GHUNNA */
    622   0x06DCu, /* ARABIC SMALL HIGH SEEN */
    623   0x06E3u, /* ARABIC SMALL LOW SEEN */
    624   0x06E7u, /* ARABIC SMALL HIGH YEH */
    625   0x06E8u, /* ARABIC SMALL HIGH NOON */
    626   0x08F3u, /* ARABIC SMALL HIGH WAW */
    627 };
    628 
    629 static inline bool
    630 info_is_mcm (const hb_glyph_info_t &info)
    631 {
    632   hb_codepoint_t u = info.codepoint;
    633   for (unsigned int i = 0; i < ARRAY_LENGTH (modifier_combining_marks); i++)
    634     if (u == modifier_combining_marks[i])
    635       return true;
    636   return false;
    637 }
    638 
    639 static void
    640 reorder_marks_arabic (const hb_ot_shape_plan_t *plan,
    641 		      hb_buffer_t              *buffer,
    642 		      unsigned int              start,
    643 		      unsigned int              end)
    644 {
    645   hb_glyph_info_t *info = buffer->info;
    646 
    647   unsigned int i = start;
    648   for (unsigned int cc = 220; cc <= 230; cc += 10)
    649   {
    650     DEBUG_MSG (ARABIC, buffer, "Looking for %d's starting at %d\n", cc, i);
    651     while (i < end && info_cc(info[i]) < cc)
    652       i++;
    653     DEBUG_MSG (ARABIC, buffer, "Looking for %d's stopped at %d\n", cc, i);
    654 
    655     if (i == end)
    656       break;
    657 
    658     if (info_cc(info[i]) > cc)
    659       continue;
    660 
    661     /* Technically we should also check "info_cc(info[j]) == cc"
    662      * in the following loop.  But not doing it is safe; we might
    663      * end up moving all the 220 MCMs and 230 MCMs together in one
    664      * move and be done. */
    665     unsigned int j = i;
    666     while (j < end && info_is_mcm (info[j]))
    667       j++;
    668     DEBUG_MSG (ARABIC, buffer, "Found %d's from %d to %d\n", cc, i, j);
    669 
    670     if (i == j)
    671       continue;
    672 
    673     /* Shift it! */
    674     DEBUG_MSG (ARABIC, buffer, "Shifting %d's: %d %d\n", cc, i, j);
    675     hb_glyph_info_t temp[HB_OT_SHAPE_COMPLEX_MAX_COMBINING_MARKS];
    676     assert (j - i <= ARRAY_LENGTH (temp));
    677     buffer->merge_clusters (start, j);
    678     memmove (temp, &info[i], (j - i) * sizeof (hb_glyph_info_t));
    679     memmove (&info[start + j - i], &info[start], (i - start) * sizeof (hb_glyph_info_t));
    680     memmove (&info[start], temp, (j - i) * sizeof (hb_glyph_info_t));
    681 
    682     start += j - i;
    683 
    684     i = j;
    685   }
    686 }
    687 
    688 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_arabic =
    689 {
    690   collect_features_arabic,
    691   nullptr, /* override_features */
    692   data_create_arabic,
    693   data_destroy_arabic,
    694   nullptr, /* preprocess_text */
    695   postprocess_glyphs_arabic,
    696   HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT,
    697   nullptr, /* decompose */
    698   nullptr, /* compose */
    699   setup_masks_arabic,
    700   nullptr, /* disable_otl */
    701   reorder_marks_arabic,
    702   HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
    703   true, /* fallback_position */
    704 };
    705