tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

hb-ot-shaper-thai.cc (12169B)


      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.hh"
     28 
     29 #ifndef HB_NO_OT_SHAPE
     30 
     31 #include "hb-ot-shaper.hh"
     32 
     33 
     34 /* Thai / Lao shaper */
     35 
     36 
     37 /* PUA shaping */
     38 
     39 
     40 enum thai_consonant_type_t
     41 {
     42  NC,
     43  AC,
     44  RC,
     45  DC,
     46  NOT_CONSONANT,
     47  NUM_CONSONANT_TYPES = NOT_CONSONANT
     48 };
     49 
     50 static thai_consonant_type_t
     51 get_consonant_type (hb_codepoint_t u)
     52 {
     53  if (u == 0x0E1Bu || u == 0x0E1Du || u == 0x0E1Fu/* || u == 0x0E2Cu*/)
     54    return AC;
     55  if (u == 0x0E0Du || u == 0x0E10u)
     56    return RC;
     57  if (u == 0x0E0Eu || u == 0x0E0Fu)
     58    return DC;
     59  if (hb_in_range<hb_codepoint_t> (u, 0x0E01u, 0x0E2Eu))
     60    return NC;
     61  return NOT_CONSONANT;
     62 }
     63 
     64 
     65 enum thai_mark_type_t
     66 {
     67  AV,
     68  BV,
     69  T,
     70  NOT_MARK,
     71  NUM_MARK_TYPES = NOT_MARK
     72 };
     73 
     74 static thai_mark_type_t
     75 get_mark_type (hb_codepoint_t u)
     76 {
     77  if (u == 0x0E31u || hb_in_range<hb_codepoint_t> (u, 0x0E34u, 0x0E37u) ||
     78      u == 0x0E47u || hb_in_range<hb_codepoint_t> (u, 0x0E4Du, 0x0E4Eu))
     79    return AV;
     80  if (hb_in_range<hb_codepoint_t> (u, 0x0E38u, 0x0E3Au))
     81    return BV;
     82  if (hb_in_range<hb_codepoint_t> (u, 0x0E48u, 0x0E4Cu))
     83    return T;
     84  return NOT_MARK;
     85 }
     86 
     87 
     88 enum thai_action_t
     89 {
     90  NOP,
     91  SD,  /* Shift combining-mark down */
     92  SL,  /* Shift combining-mark left */
     93  SDL, /* Shift combining-mark down-left */
     94  RD   /* Remove descender from base */
     95 };
     96 
     97 static hb_codepoint_t
     98 thai_pua_shape (hb_codepoint_t u, thai_action_t action, hb_font_t *font)
     99 {
    100  struct thai_pua_mapping_t {
    101    uint16_t u;
    102    uint16_t win_pua;
    103    uint16_t mac_pua;
    104  } const *pua_mappings = nullptr;
    105  static const thai_pua_mapping_t SD_mappings[] = {
    106    {0x0E48u, 0xF70Au, 0xF88Bu}, /* MAI EK */
    107    {0x0E49u, 0xF70Bu, 0xF88Eu}, /* MAI THO */
    108    {0x0E4Au, 0xF70Cu, 0xF891u}, /* MAI TRI */
    109    {0x0E4Bu, 0xF70Du, 0xF894u}, /* MAI CHATTAWA */
    110    {0x0E4Cu, 0xF70Eu, 0xF897u}, /* THANTHAKHAT */
    111    {0x0E38u, 0xF718u, 0xF89Bu}, /* SARA U */
    112    {0x0E39u, 0xF719u, 0xF89Cu}, /* SARA UU */
    113    {0x0E3Au, 0xF71Au, 0xF89Du}, /* PHINTHU */
    114    {0x0000u, 0x0000u, 0x0000u}
    115  };
    116  static const thai_pua_mapping_t SDL_mappings[] = {
    117    {0x0E48u, 0xF705u, 0xF88Cu}, /* MAI EK */
    118    {0x0E49u, 0xF706u, 0xF88Fu}, /* MAI THO */
    119    {0x0E4Au, 0xF707u, 0xF892u}, /* MAI TRI */
    120    {0x0E4Bu, 0xF708u, 0xF895u}, /* MAI CHATTAWA */
    121    {0x0E4Cu, 0xF709u, 0xF898u}, /* THANTHAKHAT */
    122    {0x0000u, 0x0000u, 0x0000u}
    123  };
    124  static const thai_pua_mapping_t SL_mappings[] = {
    125    {0x0E48u, 0xF713u, 0xF88Au}, /* MAI EK */
    126    {0x0E49u, 0xF714u, 0xF88Du}, /* MAI THO */
    127    {0x0E4Au, 0xF715u, 0xF890u}, /* MAI TRI */
    128    {0x0E4Bu, 0xF716u, 0xF893u}, /* MAI CHATTAWA */
    129    {0x0E4Cu, 0xF717u, 0xF896u}, /* THANTHAKHAT */
    130    {0x0E31u, 0xF710u, 0xF884u}, /* MAI HAN-AKAT */
    131    {0x0E34u, 0xF701u, 0xF885u}, /* SARA I */
    132    {0x0E35u, 0xF702u, 0xF886u}, /* SARA II */
    133    {0x0E36u, 0xF703u, 0xF887u}, /* SARA UE */
    134    {0x0E37u, 0xF704u, 0xF888u}, /* SARA UEE */
    135    {0x0E47u, 0xF712u, 0xF889u}, /* MAITAIKHU */
    136    {0x0E4Du, 0xF711u, 0xF899u}, /* NIKHAHIT */
    137    {0x0000u, 0x0000u, 0x0000u}
    138  };
    139  static const thai_pua_mapping_t RD_mappings[] = {
    140    {0x0E0Du, 0xF70Fu, 0xF89Au}, /* YO YING */
    141    {0x0E10u, 0xF700u, 0xF89Eu}, /* THO THAN */
    142    {0x0000u, 0x0000u, 0x0000u}
    143  };
    144 
    145  switch (action) {
    146    case NOP: return u;
    147    case SD:  pua_mappings = SD_mappings; break;
    148    case SDL: pua_mappings = SDL_mappings; break;
    149    case SL:  pua_mappings = SL_mappings; break;
    150    case RD:  pua_mappings = RD_mappings; break;
    151  }
    152  for (; pua_mappings->u; pua_mappings++)
    153    if (pua_mappings->u == u)
    154    {
    155      hb_codepoint_t glyph;
    156      if (hb_font_get_glyph (font, pua_mappings->win_pua, 0, &glyph))
    157 return pua_mappings->win_pua;
    158      if (hb_font_get_glyph (font, pua_mappings->mac_pua, 0, &glyph))
    159 return pua_mappings->mac_pua;
    160      break;
    161    }
    162  return u;
    163 }
    164 
    165 
    166 static const enum thai_above_state_t
    167 {     /* Cluster above looks like: */
    168  T0, /*  ⣤                      */
    169  T1, /*     ⣼                   */
    170  T2, /*        ⣾                */
    171  T3, /*           ⣿             */
    172  NUM_ABOVE_STATES
    173 } thai_above_start_state[NUM_CONSONANT_TYPES + 1/* For NOT_CONSONANT */] =
    174 {
    175  T0, /* NC */
    176  T1, /* AC */
    177  T0, /* RC */
    178  T0, /* DC */
    179  T3, /* NOT_CONSONANT */
    180 };
    181 
    182 static const struct thai_above_state_machine_edge_t {
    183  thai_action_t action;
    184  thai_above_state_t next_state;
    185 } thai_above_state_machine[NUM_ABOVE_STATES][NUM_MARK_TYPES] =
    186 {        /*AV*/    /*BV*/    /*T*/
    187 /*T0*/ {{NOP,T3}, {NOP,T0}, {SD, T3}},
    188 /*T1*/ {{SL, T2}, {NOP,T1}, {SDL,T2}},
    189 /*T2*/ {{NOP,T3}, {NOP,T2}, {SL, T3}},
    190 /*T3*/ {{NOP,T3}, {NOP,T3}, {NOP,T3}},
    191 };
    192 
    193 
    194 static const enum thai_below_state_t
    195 {
    196  B0, /* No descender */
    197  B1, /* Removable descender */
    198  B2, /* Strict descender */
    199  NUM_BELOW_STATES
    200 } thai_below_start_state[NUM_CONSONANT_TYPES + 1/* For NOT_CONSONANT */] =
    201 {
    202  B0, /* NC */
    203  B0, /* AC */
    204  B1, /* RC */
    205  B2, /* DC */
    206  B2, /* NOT_CONSONANT */
    207 };
    208 
    209 static const struct thai_below_state_machine_edge_t {
    210  thai_action_t action;
    211  thai_below_state_t next_state;
    212 } thai_below_state_machine[NUM_BELOW_STATES][NUM_MARK_TYPES] =
    213 {        /*AV*/    /*BV*/    /*T*/
    214 /*B0*/ {{NOP,B0}, {NOP,B2}, {NOP, B0}},
    215 /*B1*/ {{NOP,B1}, {RD, B2}, {NOP, B1}},
    216 /*B2*/ {{NOP,B2}, {SD, B2}, {NOP, B2}},
    217 };
    218 
    219 
    220 static void
    221 do_thai_pua_shaping (const hb_ot_shape_plan_t *plan HB_UNUSED,
    222 	     hb_buffer_t              *buffer,
    223 	     hb_font_t                *font)
    224 {
    225 #ifdef HB_NO_OT_SHAPER_THAI_FALLBACK
    226  return;
    227 #endif
    228 
    229  thai_above_state_t above_state = thai_above_start_state[NOT_CONSONANT];
    230  thai_below_state_t below_state = thai_below_start_state[NOT_CONSONANT];
    231  unsigned int base = 0;
    232 
    233  hb_glyph_info_t *info = buffer->info;
    234  unsigned int count = buffer->len;
    235  for (unsigned int i = 0; i < count; i++)
    236  {
    237    thai_mark_type_t mt = get_mark_type (info[i].codepoint);
    238 
    239    if (mt == NOT_MARK) {
    240      thai_consonant_type_t ct = get_consonant_type (info[i].codepoint);
    241      above_state = thai_above_start_state[ct];
    242      below_state = thai_below_start_state[ct];
    243      base = i;
    244      continue;
    245    }
    246 
    247    const thai_above_state_machine_edge_t &above_edge = thai_above_state_machine[above_state][mt];
    248    const thai_below_state_machine_edge_t &below_edge = thai_below_state_machine[below_state][mt];
    249    above_state = above_edge.next_state;
    250    below_state = below_edge.next_state;
    251 
    252    /* At least one of the above/below actions is NOP. */
    253    thai_action_t action = above_edge.action != NOP ? above_edge.action : below_edge.action;
    254 
    255    buffer->unsafe_to_break (base, i);
    256    if (action == RD)
    257      info[base].codepoint = thai_pua_shape (info[base].codepoint, action, font);
    258    else
    259      info[i].codepoint = thai_pua_shape (info[i].codepoint, action, font);
    260  }
    261 }
    262 
    263 
    264 static void
    265 preprocess_text_thai (const hb_ot_shape_plan_t *plan,
    266 	      hb_buffer_t              *buffer,
    267 	      hb_font_t                *font)
    268 {
    269  /* This function implements the shaping logic documented here:
    270   *
    271   *   https://linux.thai.net/~thep/th-otf/shaping.html
    272   *
    273   * The first shaping rule listed there is needed even if the font has Thai
    274   * OpenType tables.  The rest do fallback positioning based on PUA codepoints.
    275   * We implement that only if there exist no Thai GSUB in the font.
    276   */
    277 
    278  /* The following is NOT specified in the MS OT Thai spec, however, it seems
    279   * to be what Uniscribe and other engines implement.  According to Eric Muller:
    280   *
    281   * When you have a SARA AM, decompose it in NIKHAHIT + SARA AA, *and* move the
    282   * NIKHAHIT backwards over any above-base marks.
    283   *
    284   * <0E14, 0E4B, 0E33> -> <0E14, 0E4D, 0E4B, 0E32>
    285   *
    286   * This reordering is legit only when the NIKHAHIT comes from a SARA AM, not
    287   * when it's there to start with. The string <0E14, 0E4B, 0E4D> is probably
    288   * not what a user wanted, but the rendering is nevertheless nikhahit above
    289   * chattawa.
    290   *
    291   * Same for Lao.
    292   *
    293   * Note:
    294   *
    295   * Uniscribe also does some below-marks reordering.  Namely, it positions U+0E3A
    296   * after U+0E38 and U+0E39.  We do that by modifying the ccc for U+0E3A.
    297   * See unicode->modified_combining_class ().  Lao does NOT have a U+0E3A
    298   * equivalent.
    299   */
    300 
    301 
    302  /*
    303   * Here are the characters of significance:
    304   *
    305   *			Thai	Lao
    306   * SARA AM:		U+0E33	U+0EB3
    307   * SARA AA:		U+0E32	U+0EB2
    308   * Nikhahit:		U+0E4D	U+0ECD
    309   *
    310   * Testing shows that Uniscribe reorder the following marks:
    311   * Thai:	<0E31,0E34..0E37,     0E47..0E4E>
    312   * Lao:	<0EB1,0EB4..0EB7,0EBB,0EC8..0ECD>
    313   *
    314   * Note how the Lao versions are the same as Thai + 0x80.
    315   */
    316 
    317  /* We only get one script at a time, so a script-agnostic implementation
    318   * is adequate here. */
    319 #define IS_SARA_AM(x) (((x) & ~0x0080u) == 0x0E33u)
    320 #define NIKHAHIT_FROM_SARA_AM(x) ((x) - 0x0E33u + 0x0E4Du)
    321 #define SARA_AA_FROM_SARA_AM(x) ((x) - 1)
    322 #define IS_ABOVE_BASE_MARK(x) (hb_in_ranges<hb_codepoint_t> ((x) & ~0x0080u, 0x0E34u, 0x0E37u, 0x0E47u, 0x0E4Eu, 0x0E31u, 0x0E31u, 0x0E3Bu, 0x0E3Bu))
    323 
    324  buffer->clear_output ();
    325  unsigned int count = buffer->len;
    326  for (buffer->idx = 0; buffer->idx < count /* No need for: && buffer->successful */;)
    327  {
    328    hb_codepoint_t u = buffer->cur().codepoint;
    329    if (likely (!IS_SARA_AM (u)))
    330    {
    331      if (unlikely (!buffer->next_glyph ())) break;
    332      continue;
    333    }
    334 
    335    /* Is SARA AM. Decompose and reorder. */
    336    (void) buffer->output_glyph (NIKHAHIT_FROM_SARA_AM (u));
    337    _hb_glyph_info_set_continuation (&buffer->prev(), buffer);
    338    if (unlikely (!buffer->replace_glyph (SARA_AA_FROM_SARA_AM (u)))) break;
    339 
    340    /* Make Nikhahit be recognized as a ccc=0 mark when zeroing widths. */
    341    unsigned int end = buffer->out_len;
    342    _hb_glyph_info_set_general_category (&buffer->out_info[end - 2], HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK);
    343 
    344    /* Ok, let's see... */
    345    unsigned int start = end - 2;
    346    while (start > 0 && IS_ABOVE_BASE_MARK (buffer->out_info[start - 1].codepoint))
    347      start--;
    348 
    349    if (start + 2 < end)
    350    {
    351      /* Move Nikhahit (end-2) to the beginning */
    352      buffer->merge_out_clusters (start, end);
    353      hb_glyph_info_t t = buffer->out_info[end - 2];
    354      memmove (buffer->out_info + start + 1,
    355        buffer->out_info + start,
    356        sizeof (buffer->out_info[0]) * (end - start - 2));
    357      buffer->out_info[start] = t;
    358    }
    359    else
    360    {
    361      /* Since we decomposed, and NIKHAHIT is combining, merge clusters with the
    362       * previous cluster. */
    363      if (start)
    364 buffer->merge_out_clusters (start - 1, end);
    365    }
    366  }
    367  buffer->sync ();
    368 
    369  /* If font has Thai GSUB, we are done. */
    370  if (plan->props.script == HB_SCRIPT_THAI && !plan->map.found_script[0])
    371    do_thai_pua_shaping (plan, buffer, font);
    372 }
    373 
    374 const hb_ot_shaper_t _hb_ot_shaper_thai =
    375 {
    376  nullptr, /* collect_features */
    377  nullptr, /* override_features */
    378  nullptr, /* data_create */
    379  nullptr, /* data_destroy */
    380  preprocess_text_thai,
    381  nullptr, /* postprocess_glyphs */
    382  nullptr, /* decompose */
    383  nullptr, /* compose */
    384  nullptr, /* setup_masks */
    385  nullptr, /* reorder_marks */
    386  HB_TAG_NONE, /* gpos_tag */
    387  HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT,
    388  HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
    389  false,/* fallback_position */
    390 };
    391 
    392 
    393 #endif