*** empty log message ***
[m17n/m17n-lib.git] / src / mtext.c
index 8b0e72e..f117cf2 100644 (file)
@@ -51,8 +51,8 @@
 
     @brief M-text ¥ª¥Ö¥¸¥§¥¯¥È¤È¤½¤ì¤Ë´Ø¤¹¤ë API.
 
-    m17n ¥é¥¤¥Ö¥é¥ê¤Ï¡¢ C-string¡Ê<tt>char *</tt> ¤ä <tt>unsigned char
-    *</tt>¡Ë¤Ç¤Ï¤Ê¤¯ @e M-text ¤È¸Æ¤Ö¥ª¥Ö¥¸¥§¥¯¥È¤Ç¥Æ¥­¥¹¥È¤òɽ¸½¤¹¤ë¡£
+    m17n ¥é¥¤¥Ö¥é¥ê¤Ï¡¢ C-string¡Ê<tt>char *</tt> ¤ä <tt>unsigned
+    char *</tt>¡Ë¤Ç¤Ï¤Ê¤¯ @e M-text ¤È¸Æ¤Ö¥ª¥Ö¥¸¥§¥¯¥È¤Ç¥Æ¥­¥¹¥È¤òɽ¸½¤¹¤ë¡£
     M-text ¤ÏŤµ 0 °Ê¾å¤Îʸ»úÎó¤Ç¤¢¤ê¡¢¼ï¡¹¤Îʸ»ú¥½¡¼¥¹¡Ê¤¿¤È¤¨¤Ð 
     C-string¡¢¥Õ¥¡¥¤¥ë¡¢Ê¸»ú¥³¡¼¥ÉÅù¡Ë¤«¤éºîÀ®¤Ç¤­¤ë¡£
 
@@ -70,7 +70,7 @@
     ¥àÃæ¤Î³Æ´Ø¿ô¤ò´ÊÁDz½¤¹¤ë¤³¤È¤¬¤Ç¤­¤ë¡£
 
     ¤µ¤é¤Ëm17n ¥é¥¤¥Ö¥é¥ê¤Ï¡¢ C-string ¤òÁàºî¤¹¤ë¤¿¤á¤ËÄ󶡤µ¤ì¤ë¼ï¡¹
-    ¤Î´Ø¿ô¤ÈƱÅù¤â¤Î¤ò M-text ¤òÁàºî¤¹¤ë¤¿¤á¤Ë¥µ¥Ý¡¼¥È¤·¤Æ¤¤¤ë¡£  */
+    ¤Î´Ø¿ô¤ÈƱÅù¤Î¤â¤Î¤ò M-text ¤òÁàºî¤¹¤ë¤¿¤á¤Ë¥µ¥Ý¡¼¥È¤·¤Æ¤¤¤ë¡£  */
 
 /*=*/
 
@@ -104,75 +104,89 @@ static enum MTextFormat default_utf_16 = MTEXT_FORMAT_UTF_16LE;
 static enum MTextFormat default_utf_32 = MTEXT_FORMAT_UTF_32LE;
 #endif
 
-/** Increment character position CHAR_POS and byte position BYTE_POS
+/** Increment character position CHAR_POS and unit position UNIT_POS
     so that they point to the next character in M-text MT.  No range
-    check for CHAR_POS and BYTE_POS.  */
+    check for CHAR_POS and UNIT_POS.  */
 
-#define INC_POSITION(mt, char_pos, byte_pos)                   \
+#define INC_POSITION(mt, char_pos, unit_pos)                   \
   do {                                                         \
     int c;                                                     \
                                                                \
-    if ((mt)->format == MTEXT_FORMAT_UTF_8)                    \
+    if ((mt)->format <= MTEXT_FORMAT_UTF_8)                    \
       {                                                                \
-       c = (mt)->data[(byte_pos)];                             \
-       (byte_pos) += CHAR_UNITS_BY_HEAD_UTF8 (c);              \
+       c = (mt)->data[(unit_pos)];                             \
+       (unit_pos) += CHAR_UNITS_BY_HEAD_UTF8 (c);              \
       }                                                                \
-    else                                                       \
+    else if ((mt)->format <= MTEXT_FORMAT_UTF_16BE)            \
       {                                                                \
-       c = ((unsigned short *) ((mt)->data))[(byte_pos)];      \
+       c = ((unsigned short *) ((mt)->data))[(unit_pos)];      \
                                                                \
        if ((mt)->format != default_utf_16)                     \
          c = SWAP_16 (c);                                      \
-       (byte_pos) += (c < 0xD800 || c >= 0xE000) ? 1 : 2;      \
+       (unit_pos) += CHAR_UNITS_BY_HEAD_UTF16 (c);             \
       }                                                                \
+    else                                                       \
+      (unit_pos)++;                                            \
     (char_pos)++;                                              \
   } while (0)
 
 
-/** Decrement character position CHAR_POS and byte position BYTE_POS
+/** Decrement character position CHAR_POS and unit position UNIT_POS
     so that they point to the previous character in M-text MT.  No
-    range check for CHAR_POS and BYTE_POS.  */
+    range check for CHAR_POS and UNIT_POS.  */
 
-#define DEC_POSITION(mt, char_pos, byte_pos)                           \
+#define DEC_POSITION(mt, char_pos, unit_pos)                           \
   do {                                                                 \
-    if ((mt)->format == MTEXT_FORMAT_UTF_8)                            \
+    if ((mt)->format <= MTEXT_FORMAT_UTF_8)                            \
       {                                                                        \
-       unsigned char *p1 = (mt)->data + (byte_pos);                    \
+       unsigned char *p1 = (mt)->data + (unit_pos);                    \
        unsigned char *p0 = p1 - 1;                                     \
                                                                        \
        while (! CHAR_HEAD_P (p0)) p0--;                                \
-       (byte_pos) -= (p1 - p0);                                        \
+       (unit_pos) -= (p1 - p0);                                        \
       }                                                                        \
-    else                                                               \
+    else if ((mt)->format <= MTEXT_FORMAT_UTF_16BE)                    \
       {                                                                        \
-       int c = ((unsigned short *) ((mt)->data))[(byte_pos) - 1];      \
+       int c = ((unsigned short *) ((mt)->data))[(unit_pos) - 1];      \
                                                                        \
        if ((mt)->format != default_utf_16)                             \
          c = SWAP_16 (c);                                              \
-       (byte_pos) -= (c < 0xD800 || c >= 0xE000) ? 1 : 2;              \
+       (unit_pos) -= 2 - (c < 0xD800 || c >= 0xE000);                  \
       }                                                                        \
+    else                                                               \
+      (unit_pos)--;                                                    \
     (char_pos)--;                                                      \
   } while (0)
 
 
+/* Compoare sub-texts in MT1 (range FROM1 and TO1) and MT2 (range
+   FROM2 to TO2). */
+
 static int
 compare (MText *mt1, int from1, int to1, MText *mt2, int from2, int to2)
 {
   if (mt1->format == mt2->format
-      && (mt1->format < MTEXT_FORMAT_UTF_8))
+      && (mt1->format <= MTEXT_FORMAT_UTF_8))
     {
       unsigned char *p1, *pend1, *p2, *pend2;
+      int unit_bytes = UNIT_BYTES (mt1->format);
+      int nbytes;
+      int result;
 
-      p1 = mt1->data + mtext__char_to_byte (mt1, from1);
-      pend1 = mt1->data + mtext__char_to_byte (mt1, to1);
+      p1 = mt1->data + mtext__char_to_byte (mt1, from1) * unit_bytes;
+      pend1 = mt1->data + mtext__char_to_byte (mt1, to1) * unit_bytes;
 
-      p2 = mt2->data + mtext__char_to_byte (mt2, from2);
-      pend2 = mt2->data + mtext__char_to_byte (mt2, to2);
+      p2 = mt2->data + mtext__char_to_byte (mt2, from2) * unit_bytes;
+      pend2 = mt2->data + mtext__char_to_byte (mt2, to2) * unit_bytes;
 
-      for (; p1 < pend1 && p2 < pend2; p1++, p2++)
-       if (*p1 != *p2)
-         return (*p1 > *p2 ? 1 : -1);
-      return (p2 == pend2 ? (p1 < pend1) : -1);
+      if (pend1 - p1 < pend2 - p2)
+       nbytes = pend1 - p1;
+      else
+       nbytes = pend2 - p2;
+      result = memcmp (p1, p2, nbytes);
+      if (result)
+       return result;
+      return ((pend1 - p1) - (pend2 - p2));
     }
   for (; from1 < to1 && from2 < to2; from1++, from2++)
     {
@@ -185,68 +199,173 @@ compare (MText *mt1, int from1, int to1, MText *mt2, int from2, int to2)
   return (from2 == to2 ? (from1 < to1) : -1);
 }
 
-static MText *
-copy (MText *mt1, int pos, MText *mt2, int from, int to)
+
+/* Return how many units are required in UTF-8 to represent characters
+   between FROM and TO of MT.  */
+
+static int
+count_by_utf_8 (MText *mt, int from, int to)
 {
-  int pos_byte = POS_CHAR_TO_BYTE (mt1, pos);
-  int nbytes;
-  struct MTextPlist *plist;
-  unsigned char *p;
+  int n, c;
 
-  if (mt2->format <= MTEXT_FORMAT_UTF_8)
+  for (n = 0; from < to; from++)
     {
-      int from_byte = POS_CHAR_TO_BYTE (mt2, from);
-
-      p = mt2->data + from_byte;
-      nbytes = POS_CHAR_TO_BYTE (mt2, to) - from_byte;
+      c = mtext_ref_char (mt, from);
+      n += CHAR_UNITS_UTF8 (c);
     }
-  else
+  return n;
+}
+
+
+/* Return how many units are required in UTF-16 to represent
+   characters between FROM and TO of MT.  */
+
+static int
+count_by_utf_16 (MText *mt, int from, int to)
+{
+  int n, c;
+
+  for (n = 0; from < to; from++)
     {
-      unsigned char *p1;
-      int pos1;
+      c = mtext_ref_char (mt, from);
+      n += CHAR_UNITS_UTF16 (c);
+    }
+  return n;
+}
 
-      p = p1 = alloca (MAX_UNICODE_CHAR_BYTES * (to - from));
-      for (pos1 = from; pos1 < to; pos1++)
+
+/* Insert text between FROM and TO of MT2 at POS of MT1.  */
+
+static MText *
+insert (MText *mt1, int pos, MText *mt2, int from, int to)
+{
+  int pos_unit = POS_CHAR_TO_BYTE (mt1, pos);
+  int from_unit = POS_CHAR_TO_BYTE (mt2, from);
+  int new_units = POS_CHAR_TO_BYTE (mt2, to) - from_unit;
+  int unit_bytes;
+
+  if (mt1->nchars == 0)
+    mt1->format = mt2->format;
+  else if (mt1->format != mt2->format)
+    {
+      /* Be sure to make mt1->format sufficient to contain all
+        characters in mt2.  */
+      if (mt1->format == MTEXT_FORMAT_UTF_8
+         || mt1->format == default_utf_32
+         || (mt1->format == default_utf_16
+             && mt2->format <= MTEXT_FORMAT_UTF_16BE
+             && mt2->format != MTEXT_FORMAT_UTF_8))
+       ;
+      else if (mt1->format == MTEXT_FORMAT_US_ASCII)
+       {
+         if (mt2->format == MTEXT_FORMAT_UTF_8)
+           mt1->format = MTEXT_FORMAT_UTF_8;
+         else if (mt2->format == default_utf_16
+                  || mt2->format == default_utf_32)
+           mtext__adjust_format (mt1, mt2->format);
+         else
+           mtext__adjust_format (mt1, MTEXT_FORMAT_UTF_8);
+       }
+      else
        {
-         int c = mtext_ref_char (mt2, pos1);
-         p1 += CHAR_STRING (c, p1);
+         mtext__adjust_format (mt1, MTEXT_FORMAT_UTF_8);
+         pos_unit = POS_CHAR_TO_BYTE (mt1, pos);
        }
-      nbytes = p1 - p;
     }
 
-  if (mt1->cache_char_pos > pos)
+  unit_bytes = UNIT_BYTES (mt1->format);
+
+  if (mt1->format == mt2->format)
     {
-      mt1->cache_char_pos = pos;
-      mt1->cache_byte_pos = pos_byte;
-    }
+      int pos_byte = pos_unit * unit_bytes;
+      int total_bytes = (mt1->nbytes + new_units) * unit_bytes;
+      int new_bytes = new_units * unit_bytes;
 
-  if (pos_byte + nbytes >= mt1->allocated)
+      if (total_bytes + unit_bytes > mt1->allocated)
+       {
+         mt1->allocated = total_bytes + unit_bytes;
+         MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
+       }
+      if (pos < mt1->nchars)
+       memmove (mt1->data + pos_byte + new_bytes, mt1->data + pos_byte,
+                (mt1->nbytes - pos_unit + 1) * unit_bytes);
+      memcpy (mt1->data + pos_byte, mt2->data + from_unit * unit_bytes,
+             new_bytes);
+    }
+  else if (mt1->format == MTEXT_FORMAT_UTF_8)
     {
-      mt1->allocated = pos_byte + nbytes + 1;
-      MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
+      unsigned char *p;
+      int total_bytes, i, c;
+
+      new_units = count_by_utf_8 (mt2, from, to);
+      total_bytes = mt1->nbytes + new_units;
+
+      if (total_bytes + 1 > mt1->allocated)
+       {
+         mt1->allocated = total_bytes + 1;
+         MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
+       }
+      p = mt1->data + pos_unit;
+      memmove (p + new_units, p, mt1->nbytes - pos_unit + 1);
+      for (i = from; i < to; i++)
+       {
+         c = mtext_ref_char (mt2, i);
+         p += CHAR_STRING_UTF8 (c, p);
+       }
     }
-  memcpy (mt1->data + pos_byte, p, nbytes);
-  mt1->nbytes = pos_byte + nbytes;
-  mt1->data[mt1->nbytes] = 0;
+  else if (mt1->format == default_utf_16)
+    {
+      unsigned short *p;
+      int total_bytes, i, c;
 
-  plist = mtext__copy_plist (mt2->plist, from, to, mt1, pos);
-  if (pos == 0)
+      new_units = count_by_utf_16 (mt2, from, to);
+      total_bytes = (mt1->nbytes + new_units) * USHORT_SIZE;
+
+      if (total_bytes + USHORT_SIZE > mt1->allocated)
+       {
+         mt1->allocated = total_bytes + USHORT_SIZE;
+         MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
+       }
+      p = (unsigned short *) mt1->data + pos_unit;
+      memmove (p + new_units, p,
+              (mt1->nbytes - pos_unit + 1) * USHORT_SIZE);
+      for (i = from; i < to; i++)
+       {
+         c = mtext_ref_char (mt2, i);
+         p += CHAR_STRING_UTF16 (c, p);
+       }
+    }
+  else                         /* default_utf_32 */
     {
-      if (mt1->plist)
-       mtext__free_plist (mt1);
-      mt1->plist = plist;
+      unsigned int *p;
+      int total_bytes, i;
+
+      new_units = to - from;
+      total_bytes = (mt1->nbytes + new_units) * UINT_SIZE;
+
+      if (total_bytes + UINT_SIZE > mt1->allocated)
+       {
+         mt1->allocated = total_bytes + UINT_SIZE;
+         MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
+       }
+      p = (unsigned *) mt1->data + pos_unit;
+      memmove (p + new_units, p,
+              (mt1->nbytes - pos_unit + 1) * UINT_SIZE);
+      for (i = from; i < to; i++)
+       *p++ = mtext_ref_char (mt2, i);
     }
-  else
+
+  mtext__adjust_plist_for_insert
+    (mt1, pos, to - from,
+     mtext__copy_plist (mt2->plist, from, to, mt1, pos));
+  mt1->nchars += to - from;
+  mt1->nbytes += new_units;
+  if (mt1->cache_char_pos > pos)
     {
-      if (pos < mt1->nchars)
-       mtext__adjust_plist_for_delete (mt1, pos, mt1->nchars - pos);
-      if (from < to)
-       mtext__adjust_plist_for_insert (mt1, pos, to - from, plist);
+      mt1->cache_char_pos += to - from;
+      mt1->cache_byte_pos += new_units;
     }
 
-  mt1->nchars = pos + (to - from);
-  if (mt1->nchars < mt1->nbytes)
-    mt1->format = MTEXT_FORMAT_UTF_8;
   return mt1;
 }
 
@@ -294,7 +413,7 @@ span (MText *mt1, MText *mt2, int pos, MSymbol not)
 
 
 static int
-count_utf_8_chars (void *data, int nitems)
+count_utf_8_chars (const void *data, int nitems)
 {
   unsigned char *p = (unsigned char *) data;
   unsigned char *pend = p + nitems;
@@ -322,39 +441,38 @@ count_utf_8_chars (void *data, int nitems)
 }
 
 static int
-count_utf_16_chars (void *data, int nitems, int swap)
+count_utf_16_chars (const void *data, int nitems, int swap)
 {
   unsigned short *p = (unsigned short *) data;
   unsigned short *pend = p + nitems;
   int nchars = 0;
+  int prev_surrogate = 0;
 
-  while (p < pend)
+  for (; p < pend; p++)
     {
-      unsigned b;
+      int c = *p;
 
-      for (; p < pend; nchars++, p++)
+      if (swap)
+       c = SWAP_16 (c);
+      if (prev_surrogate)
        {
-         b = swap ? *p & 0xFF : *p >> 8;
-
-         if (b >= 0xD8 && b < 0xE0)
-           {
-             if (b >= 0xDC)
-               return -1;
-             break;
-           }
+         if (c < 0xDC00 || c >= 0xE000)
+           return -1;
+         prev_surrogate = 0;
+       }
+      else
+       {
+         if (c < 0xD800)
+           ;
+         else if (c < 0xDC00)
+           prev_surrogate = 1;
+         else if (c < 0xE000)
+           return -1;
+         nchars++;
        }
-      if (p == pend)
-       break;
-      if (p + 1 == pend)
-       return -1;
-      p++;
-      b = swap ? *p & 0xFF : *p >> 8;
-      if (b < 0xDC || b >= 0xE0)
-       return -1;
-      nchars++;
-      p++;
     }
-
+  if (prev_surrogate)
+    return -1;
   return nchars;
 }
 
@@ -370,16 +488,12 @@ find_char_forward (MText *mt, int from, int to, int c)
 
       while (from < to && STRING_CHAR_ADVANCE_UTF8 (p) != c) from++;
     }
-  else if (mt->format <= MTEXT_FORMAT_UTF_16LE)
+  else if (mt->format <= MTEXT_FORMAT_UTF_16BE)
     {
       unsigned short *p = (unsigned short *) (mt->data) + from_byte;
 
       if (mt->format == default_utf_16)
-       {
-         unsigned short *p = (unsigned short *) (mt->data) + from_byte;
-
-         while (from < to && STRING_CHAR_ADVANCE_UTF16 (p) != c) from++;
-       }
+       while (from < to && STRING_CHAR_ADVANCE_UTF16 (p) != c) from++;
       else if (c < 0x10000)
        {
          c = SWAP_16 (c);
@@ -402,8 +516,10 @@ find_char_forward (MText *mt, int from, int to, int c)
              p += ((*p & 0xFF) < 0xD8 || (*p & 0xFF) >= 0xE0) ? 1 : 2;
            }
        }
+      else
+       from = to;
     }
-  else if (c < 0x110000)
+  else
     {
       unsigned *p = (unsigned *) (mt->data) + from_byte;
       unsigned c1 = c;
@@ -464,8 +580,8 @@ find_char_backward (MText *mt, int from, int to, int c)
          int c1 = (c >> 10) + 0xD800;
          int c2 = (c & 0x3FF) + 0xDC00;
 
-         c1 = SWAP_32 (c1);
-         c2 = SWAP_32 (c2);
+         c1 = SWAP_16 (c1);
+         c2 = SWAP_16 (c2);
          while (from < to && (p[-1] != c2 || p[-2] != c1))
            {
              to--;
@@ -473,7 +589,7 @@ find_char_backward (MText *mt, int from, int to, int c)
            }
        }
     }
-  else if (c < 0x110000)
+  else
     {
       unsigned *p = (unsigned *) (mt->data) + to_byte;
       unsigned c1 = c;
@@ -745,60 +861,54 @@ mtext__cat_data (MText *mt, unsigned char *p, int nbytes,
 }
 
 MText *
-mtext__from_data (void *data, int nitems, enum MTextFormat format,
+mtext__from_data (const void *data, int nitems, enum MTextFormat format,
                  int need_copy)
 {
   MText *mt;
-  int nchars = nitems;
-  int bytes = nitems;
+  int nchars, nbytes, unit_bytes;
 
   if (format == MTEXT_FORMAT_US_ASCII)
     {
-      char *p = (char *) data, *pend = p + nitems;
+      const char *p = (char *) data, *pend = p + nitems;
 
       while (p < pend)
        if (*p++ < 0)
          MERROR (MERROR_MTEXT, NULL);
+      nchars = nbytes = nitems;
+      unit_bytes = 1;
     }
   else if (format == MTEXT_FORMAT_UTF_8)
     {
       if ((nchars = count_utf_8_chars (data, nitems)) < 0)
        MERROR (MERROR_MTEXT, NULL);
+      nbytes = nitems;
+      unit_bytes = 1;
     }
   else if (format <= MTEXT_FORMAT_UTF_16BE)
     {
       if ((nchars = count_utf_16_chars (data, nitems,
                                        format != default_utf_16)) < 0)
        MERROR (MERROR_MTEXT, NULL);
-      bytes = sizeof (short) * nitems;
+      nbytes = USHORT_SIZE * nitems;
+      unit_bytes = USHORT_SIZE;
     }
-  else if (format <= MTEXT_FORMAT_UTF_32BE)
+  else                         /* MTEXT_FORMAT_UTF_32XX */
     {
-      unsigned *p = (unsigned *) data, *pend = p + nitems;
-      int swap = format != default_utf_32;
-
-      for (; p < pend; p++)
-       {
-         unsigned c = swap ? SWAP_32 (*p) : *p;
-
-         if ((c >= 0xD800 && c < 0xE000) || (c >= 0x110000))
-           MERROR (MERROR_MTEXT, NULL);
-       }
-      bytes = sizeof (unsigned) * nitems;
+      nchars = nitems;
+      nbytes = UINT_SIZE * nitems;
+      unit_bytes = UINT_SIZE;
     }
-  else
-    MERROR (MERROR_MTEXT, NULL);
 
   mt = mtext ();
   mt->format = format;
-  mt->allocated = need_copy ? bytes : -1;
+  mt->allocated = need_copy ? nbytes + unit_bytes : -1;
   mt->nchars = nchars;
   mt->nbytes = nitems;
   if (need_copy)
     {
-      mt->data = malloc (bytes + 1);
-      memcpy (mt->data, data, bytes);
-      mt->data[bytes] = 0;
+      MTABLE_MALLOC (mt->data, mt->allocated, MERROR_MTEXT);
+      memcpy (mt->data, data, nbytes);
+      mt->data[nbytes] = 0;
     }
   else
     mt->data = (unsigned char *) data;
@@ -806,79 +916,81 @@ mtext__from_data (void *data, int nitems, enum MTextFormat format,
 }
 
 
-/* Not yet implemented.  */
-
-int
+void
 mtext__adjust_format (MText *mt, enum MTextFormat format)
 {
-  if (mt->format == format)
-    return 0;
-  if (mt->format == MTEXT_FORMAT_US_ASCII)
-    {
-      if (format == MTEXT_FORMAT_UTF_8)
-       mt->format = MTEXT_FORMAT_UTF_8;
-      MERROR (MERROR_MTEXT, -1);
-    }
-  else if (mt->format == MTEXT_FORMAT_UTF_8)
-    {
-      MERROR (MERROR_MTEXT, -1);
-    }
-  else if (mt->format <= MTEXT_FORMAT_UTF_16BE)
-    {
-      MERROR (MERROR_MTEXT, -1);
-    }
-  else
-    {
-      MERROR (MERROR_MTEXT, -1);
-    }
-  return 0;
-}
-
-
-int
-mtext__replace (MText *mt, int from, int to, char *from_str, char *to_str)
-{
-  int from_byte = POS_CHAR_TO_BYTE (mt, from);
-  int to_byte = POS_CHAR_TO_BYTE (mt, to);
-  unsigned char *p = MTEXT_DATA (mt) + from_byte;
-  unsigned char *endp = MTEXT_DATA (mt) + to_byte;
-  int from_str_len = strlen (from_str);
-  int to_str_len = strlen (to_str);
-  int diff = to_str_len - from_str_len;
-  unsigned char saved_byte;
-  int pos, pos_byte;
-
-  if (mtext_nchars (mt) == 0
-      || from_str_len == 0)
-    return 0;
-  M_CHECK_READONLY (mt, -1);
-  M_CHECK_RANGE (mt, from, to, -1, 0);
+  int i, c;
 
-  saved_byte = *endp;
-  *endp = '\0';
-  while ((p = (unsigned char *) strstr ((char *) p, from_str)) != NULL)
-    {
-      if (diff < 0)
+  if (mt->nchars > 0)
+    switch (format)
+      {
+      case MTEXT_FORMAT_US_ASCII:
        {
-         pos_byte = p - MTEXT_DATA (mt);
-         pos = POS_BYTE_TO_CHAR (mt, pos_byte);
-         mtext_del (mt, pos, pos - diff);
+         unsigned char *p = mt->data;
+
+         for (i = 0; i < mt->nchars; i++)
+           *p++ = mtext_ref_char (mt, i);
+         mt->nbytes = mt->nchars;
+         mt->cache_byte_pos = mt->cache_char_pos;
+         break;
        }
-      else if (diff > 0)
+
+      case MTEXT_FORMAT_UTF_8:
        {
-         pos_byte = p - MTEXT_DATA (mt);
-         pos = POS_BYTE_TO_CHAR (mt, pos_byte);
-         mtext_ins_char (mt, pos, ' ', diff);
-         /* The above may relocate mt->data.  */
-         endp += (MTEXT_DATA (mt) + pos_byte) - p;
-         p = MTEXT_DATA (mt) + pos_byte;
+         unsigned char *p0, *p1;
+
+         i = count_by_utf_8 (mt, 0, mt->nchars) + 1;
+         MTABLE_MALLOC (p0, i, MERROR_MTEXT);
+         mt->allocated = i;
+         for (i = 0, p1 = p0; i < mt->nchars; i++)
+           {
+             c = mtext_ref_char (mt, i);
+             p1 += CHAR_STRING_UTF8 (c, p1);
+           }
+         *p1 = '\0';
+         free (mt->data);
+         mt->data = p0;
+         mt->nbytes = p1 - p0;
+         mt->cache_char_pos = mt->cache_byte_pos = 0;
+         break;
        }
-      memmove (p, to_str, to_str_len);
-      p += to_str_len;
-      endp += diff;
-    }
-  *endp = saved_byte;
-  return 0;
+
+      default:
+       if (format == default_utf_16)
+         {
+           unsigned short *p0, *p1;
+
+           i = (count_by_utf_16 (mt, 0, mt->nchars) + 1) * USHORT_SIZE;
+           MTABLE_MALLOC (p0, i, MERROR_MTEXT);
+           mt->allocated = i;
+           for (i = 0, p1 = p0; i < mt->nchars; i++)
+             {
+               c = mtext_ref_char (mt, i);
+               p1 += CHAR_STRING_UTF16 (c, p1);
+             }
+           *p1 = 0;
+           free (mt->data);
+           mt->data = (unsigned char *) p0;
+           mt->nbytes = p1 - p0;
+           mt->cache_char_pos = mt->cache_byte_pos = 0;
+           break;
+         }
+       else
+         {
+           unsigned int *p;
+
+           mt->allocated = (mt->nchars + 1) * UINT_SIZE;
+           MTABLE_MALLOC (p, mt->allocated, MERROR_MTEXT);
+           for (i = 0; i < mt->nchars; i++)
+             p[i] = mtext_ref_char (mt, i);
+           p[i] = 0;
+           free (mt->data);
+           mt->data = (unsigned char *) p;
+           mt->nbytes = mt->nchars;
+           mt->cache_byte_pos = mt->cache_char_pos;
+         }
+      }
+  mt->format = format;
 }
 
 
@@ -1104,34 +1216,11 @@ mtext ()
     @c MERROR_MTEXT  */
 
 MText *
-mtext_from_data (void *data, int nitems, enum MTextFormat format)
+mtext_from_data (const void *data, int nitems, enum MTextFormat format)
 {
-  if (nitems < 0)
+  if (nitems < 0
+      || format < MTEXT_FORMAT_US_ASCII || format >= MTEXT_FORMAT_MAX)
     MERROR (MERROR_MTEXT, NULL);
-  if (nitems == 0)
-    {
-      if (format == MTEXT_FORMAT_US_ASCII
-         || format == MTEXT_FORMAT_UTF_8)
-       {
-         unsigned char *p = data;
-
-         while (*p++) nitems++;
-       }
-      else if (format <= MTEXT_FORMAT_UTF_16BE)
-       {
-         unsigned short *p = data;
-
-         while (*p++) nitems++;
-       }
-      else if (format <= MTEXT_FORMAT_UTF_32BE)
-       {
-         unsigned *p = data;
-
-         while (*p++) nitems++;
-       }
-      else
-       MERROR (MERROR_MTEXT, NULL);
-    }
   return mtext__from_data (data, nitems, format, 0);
 }
 
@@ -1188,33 +1277,28 @@ mtext_ref_char (MText *mt, int pos)
     {
       unsigned char *p = mt->data + POS_CHAR_TO_BYTE (mt, pos);
 
-      c = STRING_CHAR (p);
+      c = STRING_CHAR_UTF8 (p);
     }
   else if (mt->format <= MTEXT_FORMAT_UTF_16BE)
     {
       unsigned short *p
        = (unsigned short *) (mt->data) + POS_CHAR_TO_BYTE (mt, pos);
+      unsigned short p1[2];
 
-      if (mt->format == default_utf_16)
-       c = STRING_CHAR_UTF16 (p);
-      else
+      if (mt->format != default_utf_16)
        {
-         c = (*p >> 8) | ((*p & 0xFF) << 8);
-         if (c >= 0xD800 && c < 0xE000)
-           {
-             int c1 = (p[1] >> 8) | ((p[1] & 0xFF) << 8);
-             c = ((c - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000;
-           }
+         p1[0] = SWAP_16 (*p);
+         if (p1[0] >= 0xD800 || p1[0] < 0xDC00)
+           p1[1] = SWAP_16 (p[1]);
+         p = p1;
        }
+      c = STRING_CHAR_UTF16 (p);
     }
   else
     {
-      unsigned *p = (unsigned *) (mt->data) + POS_CHAR_TO_BYTE (mt, pos);
-
-      if (mt->format == default_utf_32)
-       c = *p;
-      else
-       c = SWAP_32 (*p);
+      c = ((unsigned *) (mt->data))[pos];
+      if (mt->format != default_utf_32)
+       c = SWAP_32 (c);
     }
   return c;
 }
@@ -1251,45 +1335,77 @@ mtext_ref_char (MText *mt, int pos)
 int
 mtext_set_char (MText *mt, int pos, int c)
 {
-  int byte_pos;
-  int bytes_old, bytes_new;
+  int pos_unit;
+  int old_units, new_units;
   int delta;
-  unsigned char str[MAX_UTF8_CHAR_BYTES];
   unsigned char *p;
-  int i;
+  int unit_bytes;
 
   M_CHECK_POS (mt, pos, -1);
   M_CHECK_READONLY (mt, -1);
 
-  byte_pos = POS_CHAR_TO_BYTE (mt, pos);
-  p = mt->data + byte_pos;
-  bytes_old = CHAR_BYTES_AT (p);
-  bytes_new = CHAR_STRING (c, str);
-  delta = bytes_new - bytes_old;
+  mtext__adjust_plist_for_change (mt, pos, pos + 1);
 
-  /* mtext__adjust_plist_for_change (mt, pos, pos + 1);*/
+  if (mt->format <= MTEXT_FORMAT_UTF_8)
+    {
+      if (c >= 0x80)
+       mt->format = MTEXT_FORMAT_UTF_8;
+    }
+  else if (mt->format <= MTEXT_FORMAT_UTF_16BE)
+    {
+      if (c >= 0x110000)
+       mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
+      else if (mt->format != default_utf_16)
+       mtext__adjust_format (mt, default_utf_16);
+    }
+  else if (mt->format != default_utf_32)
+    mtext__adjust_format (mt, default_utf_32);
+
+  unit_bytes = UNIT_BYTES (mt->format);
+  pos_unit = POS_CHAR_TO_BYTE (mt, pos);
+  p = mt->data + pos_unit * unit_bytes;
+  old_units = CHAR_UNITS_AT (mt, p);
+  new_units = CHAR_UNITS (c, mt->format);
+  delta = new_units - old_units;
 
   if (delta)
     {
-      int byte_pos_old = byte_pos + bytes_old;
-      int byte_pos_new = byte_pos + bytes_new;
-
       if (mt->cache_char_pos > pos)
        mt->cache_byte_pos += delta;
 
-      if ((mt->allocated - mt->nbytes) <= delta)
+      if ((mt->nbytes + delta + 1) * unit_bytes > mt->allocated)
        {
-         mt->allocated = mt->nbytes + delta + 1;
+         mt->allocated = (mt->nbytes + delta + 1) * unit_bytes;
          MTABLE_REALLOC (mt->data, mt->allocated, MERROR_MTEXT);
        }
 
-      memmove (mt->data + byte_pos_old, mt->data + byte_pos_new,
-              mt->nbytes - byte_pos_old);
+      memmove (mt->data + (pos_unit + new_units) * unit_bytes, 
+              mt->data + (pos_unit + old_units) * unit_bytes,
+              (mt->nbytes - pos_unit - old_units + 1) * unit_bytes);
       mt->nbytes += delta;
-      mt->data[mt->nbytes] = 0;
+      mt->data[mt->nbytes * unit_bytes] = 0;
+    }
+  switch (mt->format)
+    {
+    case MTEXT_FORMAT_US_ASCII:
+      mt->data[pos_unit] = c;
+      break;
+    case MTEXT_FORMAT_UTF_8:
+      {
+       unsigned char *p = mt->data + pos_unit;
+       CHAR_STRING_UTF8 (c, p);
+       break;
+      }
+    default:
+      if (mt->format == default_utf_16)
+       {
+         unsigned short *p = (unsigned short *) mt->data + pos_unit;
+
+         CHAR_STRING_UTF16 (c, p);
+       }
+      else
+       ((unsigned *) mt->data)[pos_unit] = c;
     }
-  for (i = 0; i < bytes_new; i++)
-    mt->data[byte_pos + i] = str[i];
   return 0;
 }
 
@@ -1322,28 +1438,63 @@ mtext_set_char (MText *mt, int pos, int c)
 MText *
 mtext_cat_char (MText *mt, int c)
 {
-  unsigned char buf[MAX_UTF8_CHAR_BYTES];
-  int nbytes;
-  int total_bytes;
+  int nunits;
+  int unit_bytes = UNIT_BYTES (mt->format);
 
   M_CHECK_READONLY (mt, NULL);
   if (c < 0 || c > MCHAR_MAX)
     return NULL;
-  nbytes = CHAR_STRING (c, buf);
+  mtext__adjust_plist_for_insert (mt, mt->nchars, 1, NULL);
 
-  total_bytes = mt->nbytes + nbytes;
+  if (c >= 0x80
+      && (mt->format == MTEXT_FORMAT_US_ASCII
+         || (c >= 0x10000
+             && (mt->format == MTEXT_FORMAT_UTF_16LE
+                 || mt->format == MTEXT_FORMAT_UTF_16BE))))
 
-  mtext__adjust_plist_for_insert (mt, mt->nchars, 1, NULL);
+    {
+      mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
+      unit_bytes = 1;
+    }
+  else if (mt->format >= MTEXT_FORMAT_UTF_32LE)
+    {
+      if (mt->format != default_utf_32)
+       mtext__adjust_format (mt, default_utf_32);
+    }
+  else if (mt->format >= MTEXT_FORMAT_UTF_16LE)
+    {
+      if (mt->format != default_utf_16)
+       mtext__adjust_format (mt, default_utf_16);
+    }
 
-  if (total_bytes >= mt->allocated)
+  nunits = CHAR_UNITS (c, mt->format);
+  if ((mt->nbytes + nunits + 1) * unit_bytes > mt->allocated)
     {
-      mt->allocated = total_bytes + 1;
+      mt->allocated = (mt->nbytes + nunits + 1) * unit_bytes;
       MTABLE_REALLOC (mt->data, mt->allocated, MERROR_MTEXT);
     }
-  memcpy (mt->data + mt->nbytes, buf, nbytes);
-  mt->nbytes = total_bytes;
+  
+  if (mt->format <= MTEXT_FORMAT_UTF_8)
+    {
+      unsigned char *p = mt->data + mt->nbytes;
+      p += CHAR_STRING_UTF8 (c, p);
+      *p = 0;
+    }
+  else if (mt->format == default_utf_16)
+    {
+      unsigned short *p = (unsigned short *) mt->data + mt->nbytes;
+      p += CHAR_STRING_UTF16 (c, p);
+      *p = 0;
+    }
+  else
+    {
+      unsigned *p = (unsigned *) mt->data + mt->nbytes;
+      *p++ = c;
+      *p = 0;
+    }
+
   mt->nchars++;
-  mt->data[total_bytes] = 0;
+  mt->nbytes += nunits;
   return mt;
 }
 
@@ -1358,7 +1509,7 @@ mtext_cat_char (MText *mt, int c)
     @return
     This function returns a pointer to the created copy.  */
 
-/***oldja
+/***ja
     @brief M-text ¤Î¥³¥Ô¡¼¤òºî¤ë.
 
     ´Ø¿ô mtext_dup () ¤Ï¡¢M-text $MT ¤Î¥³¥Ô¡¼¤òºî¤ë¡£$MT ¤Î¥Æ¥­¥¹¥È¥×
@@ -1376,7 +1527,19 @@ mtext_cat_char (MText *mt, int c)
 MText *
 mtext_dup (MText *mt)
 {
-  return copy (mtext (), 0, mt, 0, mt->nchars);
+  MText *new = mtext ();
+  int unit_bytes = UNIT_BYTES (mt->format);
+
+  *new = *mt;
+  if (mt->nchars > 0)
+    {
+      new->allocated = (mt->nbytes + 1) * unit_bytes;
+      MTABLE_MALLOC (new->data, new->allocated, MERROR_MTEXT);
+      memcpy (new->data, mt->data, new->allocated);
+      if (mt->plist)
+       new->plist = mtext__copy_plist (mt->plist, 0, mt->nchars, new, 0);
+    }
+  return new;
 }
 
 /*=*/
@@ -1412,7 +1575,9 @@ mtext_cat (MText *mt1, MText *mt2)
 {
   M_CHECK_READONLY (mt1, NULL);
 
-  return copy (mt1, mt1->nchars, mt2, 0, mt2->nchars);
+  if (mt2->nchars > 0)
+    insert (mt1, mt1->nchars, mt2, 0, mt2->nchars);
+  return mt1;
 }
 
 
@@ -1426,11 +1591,11 @@ mtext_cat (MText *mt1, MText *mt2)
     text properties.  If the length of $MT2 is less than $N, all
     characters are copied.  $MT2 is not modified.  
 
-    @return
-    If the operation was successful, mtext_ncat () returns a pointer
-    to the resulting M-text $MT1.  If an error is detected, it returns
-    @c NULL and assigns an error code to the global variable @c
-    merror_code.  */
+    @return 
+    If the operation was successful, mtext_ncat () returns a
+    pointer to the resulting M-text $MT1.  If an error is detected, it
+    returns @c NULL and assigns an error code to the global variable
+    #merror_code.  */
 
 
 /***ja
@@ -1443,8 +1608,8 @@ mtext_cat (MText *mt1, MText *mt2)
 
     @return
     ½èÍý¤¬À®¸ù¤·¤¿¾ì¹ç¡¢mtext_ncat () ¤ÏÊѹ¹¤µ¤ì¤¿ M-text $MT1 ¤Ø¤Î¥Ý
-    ¥¤¥ó¥¿¤òÊÖ¤¹¡£¥¨¥é¡¼¤¬¸¡½Ð¤µ¤ì¤¿¾ì¹ç¤Ï @c NULL ¤òÊÖ¤·¡¢³°ÉôÊÑ¿ô @c
-    merror_code ¤Ë¥¨¥é¡¼¥³¡¼¥É¤òÀßÄꤹ¤ë¡£
+    ¥¤¥ó¥¿¤òÊÖ¤¹¡£¥¨¥é¡¼¤¬¸¡½Ð¤µ¤ì¤¿¾ì¹ç¤Ï @c NULL ¤òÊÖ¤·¡¢³°ÉôÊÑ¿ô
+    #merror_code ¤Ë¥¨¥é¡¼¥³¡¼¥É¤òÀßÄꤹ¤ë¡£
 
     @latexonly \IPAlabel{mtext_ncat} @endlatexonly  */
 
@@ -1461,7 +1626,9 @@ mtext_ncat (MText *mt1, MText *mt2, int n)
   M_CHECK_READONLY (mt1, NULL);
   if (n < 0)
     MERROR (MERROR_RANGE, NULL);
-  return copy (mt1, mt1->nchars, mt2, 0, mt2->nchars < n ? mt2->nchars : n);
+  if (mt2->nchars > 0)
+    insert (mt1, mt1->nchars, mt2, 0, mt2->nchars < n ? mt2->nchars : n);
+  return mt1;
 }
 
 
@@ -1498,7 +1665,10 @@ MText *
 mtext_cpy (MText *mt1, MText *mt2)
 {
   M_CHECK_READONLY (mt1, NULL);
-  return copy (mt1, 0, mt2, 0, mt2->nchars);
+  mtext_del (mt1, 0, mt1->nchars);
+  if (mt2->nchars > 0)
+    insert (mt1, 0, mt2, 0, mt2->nchars);
+  return mt1;
 }
 
 /*=*/
@@ -1515,8 +1685,8 @@ mtext_cpy (MText *mt1, MText *mt2)
     @return
     If the operation was successful, mtext_ncpy () returns a pointer
     to the resulting M-text $MT1.  If an error is detected, it returns
-    @c NULL and assigns an error code to the global variable @c
-    merror_code.  */
+    @c NULL and assigns an error code to the global variable 
+    #merror_code.  */
 
 /***ja
     @brief M-text ¤Ë´Þ¤Þ¤ì¤ëºÇ½é¤Î²¿Ê¸»ú¤«¤ò¥³¥Ô¡¼¤¹¤ë.
@@ -1528,8 +1698,8 @@ mtext_cpy (MText *mt1, MText *mt2)
 
     @return 
     ½èÍý¤¬À®¸ù¤·¤¿¾ì¹ç¡¢mtext_ncpy () ¤ÏÊѹ¹¤µ¤ì¤¿ M-text $MT1 ¤Ø¤Î¥Ý
-    ¥¤¥ó¥¿¤òÊÖ¤¹¡£¥¨¥é¡¼¤¬¸¡½Ð¤µ¤ì¤¿¾ì¹ç¤Ï @c NULL ¤òÊÖ¤·¡¢³°ÉôÊÑ¿ô @c
-    merror_code ¤Ë¥¨¥é¡¼¥³¡¼¥É¤òÀßÄꤹ¤ë¡£
+    ¥¤¥ó¥¿¤òÊÖ¤¹¡£¥¨¥é¡¼¤¬¸¡½Ð¤µ¤ì¤¿¾ì¹ç¤Ï @c NULL ¤òÊÖ¤·¡¢³°ÉôÊÑ¿ô 
+    #merror_code ¤Ë¥¨¥é¡¼¥³¡¼¥É¤òÀßÄꤹ¤ë¡£
 
     @latexonly \IPAlabel{mtext_ncpy} @endlatexonly  */
 
@@ -1546,7 +1716,10 @@ mtext_ncpy (MText *mt1, MText *mt2, int n)
   M_CHECK_READONLY (mt1, NULL);
   if (n < 0)
     MERROR (MERROR_RANGE, NULL);
-  return (copy (mt1, 0, mt2, 0, mt2->nchars < n ? mt2->nchars : n));
+  mtext_del (mt1, 0, mt1->nchars);
+  if (mt2->nchars > 0)
+    insert (mt1, 0, mt2, 0, mt2->nchars < n ? mt2->nchars : n);
+  return mt1;
 }
 
 /*=*/
@@ -1588,10 +1761,14 @@ mtext_ncpy (MText *mt1, MText *mt2, int n)
 MText *
 mtext_duplicate (MText *mt, int from, int to)
 {
-  MText *new = mtext ();
+  MText *new;
 
-  M_CHECK_RANGE (mt, from, to, NULL, new);
-  return copy (new, 0, mt, from, to);
+  M_CHECK_RANGE_X (mt, from, to, NULL);
+  new = mtext ();
+  new->format = mt->format;
+  if (from < to)
+    insert (new, 0, mt, from, to);
+  return new;
 }
 
 /*=*/
@@ -1611,7 +1788,7 @@ mtext_duplicate (MText *mt, int from, int to)
     an error code to the external variable #merror_code.  */
 
 /***ja
-    @brief M-text ¤Î»ØÄêÈϰϤÎʸ»ú¤ò¥³¥Ô¡¼¤¹¤ë.
+    @brief M-text ¤Ë»ØÄêÈϰϤÎʸ»ú¤ò¥³¥Ô¡¼¤¹¤ë.
 
     ´Ø¿ô mtext_copy () ¤Ï¡¢ M-text $MT2 ¤Î $FROM ¡Ê´Þ¤à¡Ë¤«¤é $TO ¡Ê´Þ
     ¤Þ¤Ê¤¤¡Ë¤Þ¤Ç¤ÎÈϰϤΥƥ­¥¹¥È¤ò M-text $MT1 ¤Î°ÌÃÖ $POS ¤«¤é¾å½ñ¤­
@@ -1637,8 +1814,9 @@ mtext_copy (MText *mt1, int pos, MText *mt2, int from, int to)
 {
   M_CHECK_POS_X (mt1, pos, NULL);
   M_CHECK_READONLY (mt1, NULL);
-  M_CHECK_RANGE (mt2, from, to, NULL, mt1);
-  return copy (mt1, pos, mt2, from, to);
+  M_CHECK_RANGE_X (mt2, from, to, NULL);
+  mtext_del (mt1, pos, mt1->nchars);
+  return insert (mt1, pos, mt2, from, to);
 }
 
 /*=*/
@@ -1679,6 +1857,7 @@ int
 mtext_del (MText *mt, int from, int to)
 {
   int from_byte, to_byte;
+  int unit_bytes = UNIT_BYTES (mt->format);
 
   M_CHECK_READONLY (mt, -1);
   M_CHECK_RANGE (mt, from, to, -1, 0);
@@ -1698,7 +1877,9 @@ mtext_del (MText *mt, int from, int to)
     }
 
   mtext__adjust_plist_for_delete (mt, from, to - from);
-  memmove (mt->data + from_byte, mt->data + to_byte, mt->nbytes - to_byte + 1);
+  memmove (mt->data + from_byte * unit_bytes, 
+          mt->data + to_byte * unit_bytes,
+          (mt->nbytes - to_byte + 1) * unit_bytes);
   mt->nchars -= (to - from);
   mt->nbytes -= (to_byte - from_byte);
   mt->cache_char_pos = from;
@@ -1744,45 +1925,53 @@ mtext_del (MText *mt, int from, int to)
 int
 mtext_ins (MText *mt1, int pos, MText *mt2)
 {
-  int byte_pos;
-  int total_bytes;
-
   M_CHECK_READONLY (mt1, -1);
   M_CHECK_POS_X (mt1, pos, -1);
 
   if (mt2->nchars == 0)
     return 0;
-  mtext__adjust_plist_for_insert
-    (mt1, pos, mt2->nchars,
-     mtext__copy_plist (mt2->plist, 0, mt2->nchars, mt1, pos));
-
-  total_bytes = mt1->nbytes + mt2->nbytes;
-  if (total_bytes >= mt1->allocated)
-    {
-      mt1->allocated = total_bytes + 1;
-      MTABLE_REALLOC (mt1->data, mt1->allocated, MERROR_MTEXT);
-    }
-  byte_pos = POS_CHAR_TO_BYTE (mt1, pos);
-  if (mt1->cache_char_pos > pos)
-    {
-      mt1->cache_char_pos += mt2->nchars;
-      mt1->cache_byte_pos += mt2->nbytes;
-    }
-  memmove (mt1->data + byte_pos + mt2->nbytes, mt1->data + byte_pos,
-          mt1->nbytes - byte_pos + 1);
-  memcpy (mt1->data + byte_pos, mt2->data, mt2->nbytes);
-  mt1->nbytes += mt2->nbytes;
-  mt1->nchars += mt2->nchars;
+  insert (mt1, pos, mt2, 0, mt2->nchars);
   return 0;
 }
 
 
+/*=*/
+
+/***en
+    @brief Insert a character into an M-text.
+
+    The mtext_ins_char () function inserts $N copies of character $C
+    into M-text $MT at position $POS.  As a result, $MT is lengthen by
+    $N.
+
+    @return
+    If the operation was successful, mtext_ins () returns 0.
+    Otherwise, it returns -1 and assigns an error code to the external
+    variable #merror_code.  */
+
+/***ja
+    @brief M-text ¤Ëʸ»ú¤òÁÞÆþ¤¹¤ë.
+
+    ´Ø¿ô mtext_ins_char () ¤Ï M-text $MT ¤Î $POS ¤Î°ÌÃÖ¤Ëʸ»ú $C ¤ò $N
+    ¸ÄÁÞÆþ¤¹¤ë¡£¤³¤Î·ë²Ì $MT1 ¤ÎŤµ¤Ï $N ¤À¤±Áý¤¨¤ë¡£
+
+    @return
+    ½èÍý¤¬À®¸ù¤¹¤ì¤Ð mtext_ins_char () ¤Ï 0 ¤òÊÖ¤¹¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð -1
+    ¤òÊÖ¤·¡¢³°ÉôÊÑ¿ô #merror_code ¤Ë¥¨¥é¡¼¥³¡¼¥É¤òÀßÄꤹ¤ë¡£  */
+
+/***
+    @errors
+    @c MERROR_RANGE
+
+    @seealso
+    mtext_ins, mtext_del ()  */
+
 int
 mtext_ins_char (MText *mt, int pos, int c, int n)
 {
-  int byte_pos;
-  int nbytes, total_bytes;
-  unsigned char *buf;
+  int nunits;
+  int unit_bytes = UNIT_BYTES (mt->format);
+  int pos_unit;
   int i;
 
   M_CHECK_READONLY (mt, -1);
@@ -1792,26 +1981,64 @@ mtext_ins_char (MText *mt, int pos, int c, int n)
   if (n <= 0)
     return 0;
   mtext__adjust_plist_for_insert (mt, pos, n, NULL);
-  buf = alloca (MAX_UTF8_CHAR_BYTES * n);
-  for (i = 0, nbytes = 0; i < n; i++)
-    nbytes += CHAR_STRING (c, buf + nbytes);
-  total_bytes = mt->nbytes + nbytes;
-  if (total_bytes >= mt->allocated)
+
+  if (c >= 0x80
+      && (mt->format == MTEXT_FORMAT_US_ASCII
+         || (c >= 0x10000 && (mt->format == MTEXT_FORMAT_UTF_16LE
+                              || mt->format == MTEXT_FORMAT_UTF_16BE))))
     {
-      mt->allocated = total_bytes + 1;
+      mtext__adjust_format (mt, MTEXT_FORMAT_UTF_8);
+      unit_bytes = 1;
+    }
+  else if (mt->format >= MTEXT_FORMAT_UTF_32LE)
+    {
+      if (mt->format != default_utf_32)
+       mtext__adjust_format (mt, default_utf_32);
+    }
+  else if (mt->format >= MTEXT_FORMAT_UTF_16LE)
+    {
+      if (mt->format != default_utf_16)
+       mtext__adjust_format (mt, default_utf_16);
+    }
+
+  nunits = CHAR_UNITS (c, mt->format);
+  if ((mt->nbytes + nunits * n + 1) * unit_bytes > mt->allocated)
+    {
+      mt->allocated = (mt->nbytes + nunits * n + 1) * unit_bytes;
       MTABLE_REALLOC (mt->data, mt->allocated, MERROR_MTEXT);
     }
-  byte_pos = POS_CHAR_TO_BYTE (mt, pos);
+  pos_unit = POS_CHAR_TO_BYTE (mt, pos);
   if (mt->cache_char_pos > pos)
     {
-      mt->cache_char_pos++;
-      mt->cache_byte_pos += nbytes;
+      mt->cache_char_pos += n;
+      mt->cache_byte_pos += nunits + n;
+    }
+  memmove (mt->data + (pos_unit + nunits * n) * unit_bytes,
+          mt->data + pos_unit * unit_bytes,
+          (mt->nbytes - pos_unit + 1) * unit_bytes);
+  if (mt->format <= MTEXT_FORMAT_UTF_8)
+    {
+      unsigned char *p = mt->data + pos_unit;
+
+      for (i = 0; i < n; i++)
+       p += CHAR_STRING_UTF8 (c, p);
+    }
+  else if (mt->format == default_utf_16)
+    {
+      unsigned short *p = (unsigned short *) mt->data + pos_unit;
+
+      for (i = 0; i < n; i++)
+       p += CHAR_STRING_UTF16 (c, p);
+    }
+  else
+    {
+      unsigned *p = (unsigned *) mt->data + pos_unit;
+
+      for (i = 0; i < n; i++)
+       *p++ = c;
     }
-  memmove (mt->data + byte_pos + nbytes, mt->data + byte_pos,
-          mt->nbytes - byte_pos + 1);
-  memcpy (mt->data + byte_pos, buf, nbytes);
-  mt->nbytes += nbytes;
   mt->nchars += n;
+  mt->nbytes += nunits * n;
   return 0;
 }
 
@@ -1821,11 +2048,11 @@ mtext_ins_char (MText *mt, int pos, int c, int n)
     @brief Search a character in an M-text.
 
     The mtext_character () function searches M-text $MT for character
-    $C.  If $FROM < $TO, search begins at position $FROM and goes
-    forward but does not exceed ($TO - 1).  Otherwise, search begins
-    at position ($FROM - 1) and goes backward but does not exceed $TO.
-    An invalid position specification is regarded as both $FROM and
-    $TO being 0.
+    $C.  If $FROM is less than $TO, the search begins at position $FROM
+    and goes forward but does not exceed ($TO - 1).  Otherwise, the search
+    begins at position ($FROM - 1) and goes backward but does not
+    exceed $TO.  An invalid position specification is regarded as both
+    $FROM and $TO being 0.
 
     @return
     If $C is found, mtext_character () returns the position of its
@@ -1837,10 +2064,10 @@ mtext_ins_char (MText *mt, int pos, int c, int n)
     @brief M-text Ãæ¤Çʸ»ú¤òõ¤¹.
 
     ´Ø¿ô mtext_character () ¤Ï M-text $MT Ãæ¤Çʸ»ú $C ¤òõ¤¹¡£¤â¤· 
-    $FROM < $TO ¤Ê¤é¤Ð¡¢Ãµº÷¤Ï°ÌÃÖ $FROM ¤«¤éËöÈøÊý¸þ¤Ø¡¢ºÇÂç ($TO -
-    1) ¤Þ¤Ç¿Ê¤à¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð°ÌÃÖ ($FROM - 1) ¤«¤éÀèƬÊý¸þ¤Ø¡¢ºÇÂç 
-    $TO ¤Þ¤Ç¿Ê¤à¡£°ÌÃ֤λØÄê¤Ë¸í¤ê¤¬¤¢¤ë¾ì¹ç¤Ï¡¢$FROM ¤È $TO ¤ÎξÊý¤Ë 
-    0 ¤¬»ØÄꤵ¤ì¤¿¤â¤Î¤È¸«¤Ê¤¹¡£
+    $FROM ¤¬ $TO ¤è¤ê¾®¤µ¤±¤ì¤Ð¡¢Ãµº÷¤Ï°ÌÃÖ $FROM ¤«¤éËöÈøÊý¸þ¤Ø¡¢ºÇÂç 
+    ($TO - 1) ¤Þ¤Ç¿Ê¤à¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð°ÌÃÖ ($FROM - 1) ¤«¤éÀèƬÊý¸þ¤Ø¡¢
+    ºÇÂç $TO ¤Þ¤Ç¿Ê¤à¡£°ÌÃ֤λØÄê¤Ë¸í¤ê¤¬¤¢¤ë¾ì¹ç¤Ï¡¢$FROM ¤È $TO ¤Îξ
+    Êý¤Ë 0 ¤¬»ØÄꤵ¤ì¤¿¤â¤Î¤È¸«¤Ê¤¹¡£
 
     @return
     ¤â¤· $C ¤¬¸«¤Ä¤«¤ì¤Ð¡¢mtext_character () ¤Ï¤½¤ÎºÇ½é¤Î½Ð¸½°ÌÃÖ¤òÊÖ
@@ -1879,11 +2106,11 @@ mtext_character (MText *mt, int from, int to, int c)
     @brief Return the position of the first occurrence of a character in an M-text.
 
     The mtext_chr () function searches M-text $MT for character $C.
-    Search starts from the beginning of $MT and goes toward the end.
+    The search starts from the beginning of $MT and goes toward the end.
 
     @return
     If $C is found, mtext_chr () returns its position; otherwise it
-    returns.  */
+    returns -1.  */
 
 /***ja
     @brief M-text Ãæ¤Ç»ØÄꤵ¤ì¤¿Ê¸»ú¤¬ºÇ½é¤Ë¸½¤ì¤ë°ÌÃÖ¤òÊÖ¤¹.
@@ -1916,7 +2143,7 @@ mtext_chr (MText *mt, int c)
     @brief Return the position of the last occurrence of a character in an M-text.
 
     The mtext_rchr () function searches M-text $MT for character $C.
-    Search starts from the end of $MT and goes backwardly toward the
+    The search starts from the end of $MT and goes backwardly toward the
     beginning.
 
     @return
@@ -2220,7 +2447,7 @@ mtext_tok (MText *mt, MText *delim, int *pos)
     return NULL;
 
   *pos = pos2 + span (mt, delim, pos2, Mt);
-  return (copy (mtext (), 0, mt, pos2, *pos));
+  return (insert (mtext (), 0, mt, pos2, *pos));
 }
 
 /*=*/
@@ -2262,9 +2489,7 @@ mtext_text (MText *mt1, int pos, MText *mt2)
   int use_memcmp = (mt1->format == mt2->format
                    || (mt1->format < MTEXT_FORMAT_UTF_8
                        && mt2->format == MTEXT_FORMAT_UTF_8));
-  int unit_bytes = (mt1->format <= MTEXT_FORMAT_UTF_8 ? 1
-                   : mt1->format <= MTEXT_FORMAT_UTF_16BE ? 2
-                   : 4);
+  int unit_bytes = UNIT_BYTES (mt1->format);
 
   if (nbytes2 > pos_byte + nbytes1)
     return -1;
@@ -2286,6 +2511,34 @@ mtext_text (MText *mt1, int pos, MText *mt2)
   return pos;
 }
 
+/***en
+    @brief Locate an M-text in a specific range of another.
+
+    The mtext_search () function searches for the first occurrence of
+    M-text $MT2 in M-text $MT1 in the region $FROM and $TO while
+    ignoring difference of the text properties.  If $FROM is less than
+    $TO, the forward search starts from $FROM, otherwise the backward
+    search starts from $TO.
+
+    @return
+    If $MT2 is found in $MT1, mtext_search () returns the position of the
+    first occurrence.  Otherwise it returns -1.  If $MT2 is empty, it
+    returns 0.  */
+
+/***ja
+    @brief M-text Ãæ¤ÎÆÃÄê¤ÎÎΰè¤ÇÊ̤ΠM-text ¤òõ¤¹.
+
+    ´Ø¿ô mtext_search () ¤Ï¡¢M-text $MT1 Ãæ¤Î $FROM ¤«¤é $TO ¤Þ¤Ç¤Î´Ö¤Î
+    Îΰè¤ÇM-text $MT2 ¤¬ºÇ½é¤Ë¸½¤ï¤ì¤ë°ÌÃÖ¤òÄ´¤Ù¤ë¡£¥Æ¥­¥¹¥È¥×¥í¥Ñ¥Æ¥£
+    ¤Î°ã¤¤¤Ï̵»ë¤µ¤ì¤ë¡£¤â¤· $FROM ¤¬ $TO ¤è¤ê¾®¤µ¤±¤ì¤Ðõº÷¤Ï°ÌÃÖ 
+    $FROM ¤«¤éËöÈøÊý¸þ¤Ø¡¢¤½¤¦¤Ç¤Ê¤±¤ì¤Ð $TO ¤«¤éÀèƬÊý¸þ¤ØºÇÂç $TO ¤Þ
+    ¤Ç¿Ê¤à¡£
+
+    @return
+    $MT1 Ãæ¤Ë $MT2 ¤¬¸«¤Ä¤«¤ì¤Ð¡¢mtext_search() ¤Ï¤½¤ÎºÇ½é¤Î½Ð¸½°ÌÃÖ¤òÊÖ
+    ¤¹¡£¸«¤Ä¤«¤é¤Ê¤¤¾ì¹ç¤Ï -1 ¤òÊÖ¤¹¡£¤â¤· $MT2 ¤¬¶õ¤Ê¤é¤Ð 0 ¤òÊÖ¤¹¡£
+    */
+
 int
 mtext_search (MText *mt1, int from, int to, MText *mt2)
 {
@@ -2427,7 +2680,7 @@ mtext_ncasecmp (MText *mt1, MText *mt2, int n)
     @brief Æó¤Ä¤Î M-text ¤Î»ØÄꤷ¤¿Îΰè¤ò¡¢Âçʸ»ú¡¿¾®Ê¸»ú¤Î¶èÊ̤ò̵»ë¤·¤ÆÈæ³Ó¤¹¤ë.
 
     ´Ø¿ô mtext_compare () ¤ÏÆó¤Ä¤Î M-text $MT1 ¤È $MT2 ¤ò¡¢Âçʸ»ú¡¿¾®
-    Ê¸»ú¤Î¶èÊ̤ò̵»ë¤·¤Ä¤Äʸ»úñ°Ì¤ÇÈæ³Ó¤¹¤ë¡£Èæ³ÓÂоݤȤʤë¤Î¤Ï $MT1 
+    Ê¸»ú¤Î¶èÊ̤ò̵»ë¤·¤Æʸ»úñ°Ì¤ÇÈæ³Ó¤¹¤ë¡£Èæ³ÓÂоݤȤʤë¤Î¤Ï $MT1 
     ¤Ç¤Ï $FROM1 ¤«¤é $TO1 ¤Þ¤Ç¡¢$MT2 ¤Ç¤Ï $FROM2 ¤«¤é $TO2 ¤Þ¤Ç¤Ç¤¢¤ë¡£
     $FROM1 ¤È $FROM2 ¤Ï´Þ¤Þ¤ì¡¢$TO1 ¤È $TO2 ¤Ï´Þ¤Þ¤ì¤Ê¤¤¡£$FROM1 ¤È 
     $TO1 ¡Ê¤¢¤ë¤¤¤Ï $FROM2 ¤È $TO2 ¡Ë¤¬Åù¤·¤¤¾ì¹ç¤ÏŤµ¥¼¥í¤Î M-text 
@@ -2482,7 +2735,7 @@ mtext_case_compare (MText *mt1, int from1, int to1,
     @brief M-text ¤ò¥À¥ó¥×¤¹¤ë.
 
     ´Ø¿ô mdebug_dump_mtext () ¤Ï M-text $MT ¤ò stderr ¤Ë¿Í´Ö¤Ë²ÄÆɤʠ
-    ·Á¤Ç°õºþ¤¹¤ë¡£ $UNDENT ¤Ï£²¹ÔÌܰʹߤΥ¤¥ó¥Ç¥ó¥È¤ò»ØÄꤹ¤ë¡£$FULLP 
+    ·Á¤Ç°õºþ¤¹¤ë¡£ $INDENT ¤Ï£²¹ÔÌܰʹߤΥ¤¥ó¥Ç¥ó¥È¤ò»ØÄꤹ¤ë¡£$FULLP 
     ¤¬ 0 ¤Ê¤é¤Ð¡¢Ê¸»ú¥³¡¼¥ÉÎó¤À¤±¤ò°õºþ¤¹¤ë¡£¤½¤¦¤Ç¤Ê¤±¤ì¤Ð¡¢ÆâÉô¥Ð¥¤
     ¥ÈÎó¤È¥Æ¥­¥¹¥È¥×¥í¥Ñ¥Æ¥£¤â°õºþ¤¹¤ë¡£