1 /*
   2  * Copyright © 2011,2012,2013  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-ot-shape-complex-myanmar.hh"
  28 
  29 
  30 /*
  31  * Myanmar shaper.
  32  */
  33 
  34 static const hb_tag_t
  35 basic_features[] =
  36 {
  37   /*
  38    * Basic features.
  39    * These features are applied in order, one at a time, after reordering.
  40    */
  41   HB_TAG('r','p','h','f'),
  42   HB_TAG('p','r','e','f'),
  43   HB_TAG('b','l','w','f'),
  44   HB_TAG('p','s','t','f'),
  45 };
  46 static const hb_tag_t
  47 other_features[] =
  48 {
  49   /*
  50    * Other features.
  51    * These features are applied all at once, after clearing syllables.
  52    */
  53   HB_TAG('p','r','e','s'),
  54   HB_TAG('a','b','v','s'),
  55   HB_TAG('b','l','w','s'),
  56   HB_TAG('p','s','t','s'),
  57 };
  58 static const hb_tag_t
  59 positioning_features[] =
  60 {
  61   /*
  62    * Positioning features.
  63    * We don't care about the types.
  64    */
  65   HB_TAG('d','i','s','t'),
  66   /* Pre-release version of Windows 8 Myanmar font had abvm,blwm
  67    * features.  The released Windows 8 version of the font (as well
  68    * as the released spec) used 'mark' instead.  The Windows 8
  69    * shaper however didn't apply 'mark' but did apply 'mkmk'.
  70    * Perhaps it applied abvm/blwm.  This was fixed in a Windows 8
  71    * update, so now it applies mark/mkmk.  We are guessing that
  72    * it still applies abvm/blwm too.
  73    */
  74   HB_TAG('a','b','v','m'),
  75   HB_TAG('b','l','w','m'),
  76 };
  77 
  78 static void
  79 setup_syllables (const hb_ot_shape_plan_t *plan,
  80                  hb_font_t *font,
  81                  hb_buffer_t *buffer);
  82 static void
  83 reorder (const hb_ot_shape_plan_t *plan,
  84          hb_font_t *font,
  85          hb_buffer_t *buffer);
  86 static void
  87 clear_syllables (const hb_ot_shape_plan_t *plan,
  88                  hb_font_t *font,
  89                  hb_buffer_t *buffer);
  90 
  91 static void
  92 collect_features_myanmar (hb_ot_shape_planner_t *plan)
  93 {
  94   hb_ot_map_builder_t *map = &plan->map;
  95 
  96   /* Do this before any lookups have been applied. */
  97   map->add_gsub_pause (setup_syllables);
  98 
  99   map->enable_feature (HB_TAG('l','o','c','l'));
 100   /* The Indic specs do not require ccmp, but we apply it here since if
 101    * there is a use of it, it's typically at the beginning. */
 102   map->enable_feature (HB_TAG('c','c','m','p'));
 103 
 104 
 105   map->add_gsub_pause (reorder);
 106 
 107   for (unsigned int i = 0; i < ARRAY_LENGTH (basic_features); i++)
 108   {
 109     map->enable_feature (basic_features[i], F_MANUAL_ZWJ);
 110     map->add_gsub_pause (nullptr);
 111   }
 112 
 113   map->add_gsub_pause (clear_syllables);
 114 
 115   for (unsigned int i = 0; i < ARRAY_LENGTH (other_features); i++)
 116     map->enable_feature (other_features[i], F_MANUAL_ZWJ);
 117 
 118   for (unsigned int i = 0; i < ARRAY_LENGTH (positioning_features); i++)
 119     map->enable_feature (positioning_features[i]);
 120 }
 121 
 122 static void
 123 override_features_myanmar (hb_ot_shape_planner_t *plan)
 124 {
 125   plan->map.disable_feature (HB_TAG('l','i','g','a'));
 126 }
 127 
 128 
 129 enum syllable_type_t {
 130   consonant_syllable,
 131   punctuation_cluster,
 132   broken_cluster,
 133   non_myanmar_cluster,
 134 };
 135 
 136 #include "hb-ot-shape-complex-myanmar-machine.hh"
 137 
 138 
 139 static void
 140 setup_masks_myanmar (const hb_ot_shape_plan_t *plan HB_UNUSED,
 141                    hb_buffer_t              *buffer,
 142                    hb_font_t                *font HB_UNUSED)
 143 {
 144   HB_BUFFER_ALLOCATE_VAR (buffer, myanmar_category);
 145   HB_BUFFER_ALLOCATE_VAR (buffer, myanmar_position);
 146 
 147   /* We cannot setup masks here.  We save information about characters
 148    * and setup masks later on in a pause-callback. */
 149 
 150   unsigned int count = buffer->len;
 151   hb_glyph_info_t *info = buffer->info;
 152   for (unsigned int i = 0; i < count; i++)
 153     set_myanmar_properties (info[i]);
 154 }
 155 
 156 static void
 157 setup_syllables (const hb_ot_shape_plan_t *plan HB_UNUSED,
 158                  hb_font_t *font HB_UNUSED,
 159                  hb_buffer_t *buffer)
 160 {
 161   find_syllables (buffer);
 162   foreach_syllable (buffer, start, end)
 163     buffer->unsafe_to_break (start, end);
 164 }
 165 
 166 static int
 167 compare_myanmar_order (const hb_glyph_info_t *pa, const hb_glyph_info_t *pb)
 168 {
 169   int a = pa->myanmar_position();
 170   int b = pb->myanmar_position();
 171 
 172   return a < b ? -1 : a == b ? 0 : +1;
 173 }
 174 
 175 
 176 /* Rules from:
 177  * https://docs.microsoft.com/en-us/typography/script-development/myanmar */
 178 
 179 static void
 180 initial_reordering_consonant_syllable (hb_buffer_t *buffer,
 181                                        unsigned int start, unsigned int end)
 182 {
 183   hb_glyph_info_t *info = buffer->info;
 184 
 185   unsigned int base = end;
 186   bool has_reph = false;
 187 
 188   {
 189     unsigned int limit = start;
 190     if (start + 3 <= end &&
 191         info[start  ].myanmar_category() == OT_Ra &&
 192         info[start+1].myanmar_category() == OT_As &&
 193         info[start+2].myanmar_category() == OT_H)
 194     {
 195       limit += 3;
 196       base = start;
 197       has_reph = true;
 198     }
 199 
 200     {
 201       if (!has_reph)
 202         base = limit;
 203 
 204       for (unsigned int i = limit; i < end; i++)
 205         if (is_consonant (info[i]))
 206         {
 207           base = i;
 208           break;
 209         }
 210     }
 211   }
 212 
 213   /* Reorder! */
 214   {
 215     unsigned int i = start;
 216     for (; i < start + (has_reph ? 3 : 0); i++)
 217       info[i].myanmar_position() = POS_AFTER_MAIN;
 218     for (; i < base; i++)
 219       info[i].myanmar_position() = POS_PRE_C;
 220     if (i < end)
 221     {
 222       info[i].myanmar_position() = POS_BASE_C;
 223       i++;
 224     }
 225     indic_position_t pos = POS_AFTER_MAIN;
 226     /* The following loop may be ugly, but it implements all of
 227      * Myanmar reordering! */
 228     for (; i < end; i++)
 229     {
 230       if (info[i].myanmar_category() == OT_MR) /* Pre-base reordering */
 231       {
 232         info[i].myanmar_position() = POS_PRE_C;
 233         continue;
 234       }
 235       if (info[i].myanmar_position() < POS_BASE_C) /* Left matra */
 236       {
 237         continue;
 238       }
 239       if (info[i].myanmar_category() == OT_VS)
 240       {
 241         info[i].myanmar_position() = info[i - 1].myanmar_position();
 242         continue;
 243       }
 244 
 245       if (pos == POS_AFTER_MAIN && info[i].myanmar_category() == OT_VBlw)
 246       {
 247         pos = POS_BELOW_C;
 248         info[i].myanmar_position() = pos;
 249         continue;
 250       }
 251 
 252       if (pos == POS_BELOW_C && info[i].myanmar_category() == OT_A)
 253       {
 254         info[i].myanmar_position() = POS_BEFORE_SUB;
 255         continue;
 256       }
 257       if (pos == POS_BELOW_C && info[i].myanmar_category() == OT_VBlw)
 258       {
 259         info[i].myanmar_position() = pos;
 260         continue;
 261       }
 262       if (pos == POS_BELOW_C && info[i].myanmar_category() != OT_A)
 263       {
 264         pos = POS_AFTER_SUB;
 265         info[i].myanmar_position() = pos;
 266         continue;
 267       }
 268       info[i].myanmar_position() = pos;
 269     }
 270   }
 271 
 272   /* Sit tight, rock 'n roll! */
 273   buffer->sort (start, end, compare_myanmar_order);
 274 }
 275 
 276 static void
 277 initial_reordering_syllable (const hb_ot_shape_plan_t *plan HB_UNUSED,
 278                              hb_face_t *face HB_UNUSED,
 279                              hb_buffer_t *buffer,
 280                              unsigned int start, unsigned int end)
 281 {
 282   syllable_type_t syllable_type = (syllable_type_t) (buffer->info[start].syllable() & 0x0F);
 283   switch (syllable_type) {
 284 
 285     case broken_cluster: /* We already inserted dotted-circles, so just call the consonant_syllable. */
 286     case consonant_syllable:
 287       initial_reordering_consonant_syllable  (buffer, start, end);
 288       break;
 289 
 290     case punctuation_cluster:
 291     case non_myanmar_cluster:
 292       break;
 293   }
 294 }
 295 
 296 static inline void
 297 insert_dotted_circles (const hb_ot_shape_plan_t *plan HB_UNUSED,
 298                        hb_font_t *font,
 299                        hb_buffer_t *buffer)
 300 {
 301   /* Note: This loop is extra overhead, but should not be measurable. */
 302   bool has_broken_syllables = false;
 303   unsigned int count = buffer->len;
 304   hb_glyph_info_t *info = buffer->info;
 305   for (unsigned int i = 0; i < count; i++)
 306     if ((info[i].syllable() & 0x0F) == broken_cluster)
 307     {
 308       has_broken_syllables = true;
 309       break;
 310     }
 311   if (likely (!has_broken_syllables))
 312     return;
 313 
 314 
 315   hb_codepoint_t dottedcircle_glyph;
 316   if (!font->get_nominal_glyph (0x25CCu, &dottedcircle_glyph))
 317     return;
 318 
 319   hb_glyph_info_t dottedcircle = {0};
 320   dottedcircle.codepoint = 0x25CCu;
 321   set_myanmar_properties (dottedcircle);
 322   dottedcircle.codepoint = dottedcircle_glyph;
 323 
 324   buffer->clear_output ();
 325 
 326   buffer->idx = 0;
 327   unsigned int last_syllable = 0;
 328   while (buffer->idx < buffer->len && buffer->successful)
 329   {
 330     unsigned int syllable = buffer->cur().syllable();
 331     syllable_type_t syllable_type = (syllable_type_t) (syllable & 0x0F);
 332     if (unlikely (last_syllable != syllable && syllable_type == broken_cluster))
 333     {
 334       last_syllable = syllable;
 335 
 336       hb_glyph_info_t ginfo = dottedcircle;
 337       ginfo.cluster = buffer->cur().cluster;
 338       ginfo.mask = buffer->cur().mask;
 339       ginfo.syllable() = buffer->cur().syllable();
 340 
 341       buffer->output_info (ginfo);
 342     }
 343     else
 344       buffer->next_glyph ();
 345   }
 346   buffer->swap_buffers ();
 347 }
 348 
 349 static void
 350 reorder (const hb_ot_shape_plan_t *plan,
 351          hb_font_t *font,
 352          hb_buffer_t *buffer)
 353 {
 354   insert_dotted_circles (plan, font, buffer);
 355 
 356   foreach_syllable (buffer, start, end)
 357     initial_reordering_syllable (plan, font->face, buffer, start, end);
 358 
 359   HB_BUFFER_DEALLOCATE_VAR (buffer, myanmar_category);
 360   HB_BUFFER_DEALLOCATE_VAR (buffer, myanmar_position);
 361 }
 362 
 363 static void
 364 clear_syllables (const hb_ot_shape_plan_t *plan HB_UNUSED,
 365                  hb_font_t *font HB_UNUSED,
 366                  hb_buffer_t *buffer)
 367 {
 368   hb_glyph_info_t *info = buffer->info;
 369   unsigned int count = buffer->len;
 370   for (unsigned int i = 0; i < count; i++)
 371     info[i].syllable() = 0;
 372 }
 373 
 374 
 375 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_myanmar =
 376 {
 377   collect_features_myanmar,
 378   override_features_myanmar,
 379   nullptr, /* data_create */
 380   nullptr, /* data_destroy */
 381   nullptr, /* preprocess_text */
 382   nullptr, /* postprocess_glyphs */
 383   HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
 384   nullptr, /* decompose */
 385   nullptr, /* compose */
 386   setup_masks_myanmar,
 387   HB_TAG_NONE, /* gpos_tag */
 388   nullptr, /* reorder_marks */
 389   HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
 390   false, /* fallback_position */
 391 };
 392 
 393 
 394 /* Ugly Zawgyi encoding.
 395  * Disable all auto processing.
 396  * https://github.com/harfbuzz/harfbuzz/issues/1162 */
 397 const hb_ot_complex_shaper_t _hb_ot_complex_shaper_myanmar_zawgyi =
 398 {
 399   nullptr, /* collect_features */
 400   nullptr, /* override_features */
 401   nullptr, /* data_create */
 402   nullptr, /* data_destroy */
 403   nullptr, /* preprocess_text */
 404   nullptr, /* postprocess_glyphs */
 405   HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
 406   nullptr, /* decompose */
 407   nullptr, /* compose */
 408   nullptr, /* setup_masks */
 409   HB_TAG_NONE, /* gpos_tag */
 410   nullptr, /* reorder_marks */
 411   HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
 412   false, /* fallback_position */
 413 };