]> code.delx.au - gnu-emacs/blobdiff - src/charset.c
Lots of comments fixed.
[gnu-emacs] / src / charset.c
index a65de79c8a648310597a091c34811d376a0e71c6..91f510476a0813cf74734928cc5c4b7f2e4372d8 100644 (file)
@@ -41,6 +41,7 @@ Boston, MA 02111-1307, USA.  */
 #endif /* emacs */
 
 Lisp_Object Qcharset, Qascii, Qcomposition;
+Lisp_Object Qunknown;
 
 /* Declaration of special leading-codes.  */
 int leading_code_composition;  /* for composite characters */
@@ -123,13 +124,104 @@ invalid_character (c)
   error ("Invalid character: 0%o, %d, 0x%x", c, c, c);
 }
 
+/* Parse composite character string STR of length LENGTH (>= 2) and
+   set BYTES, CHARSET, C1, and C2 as below.
+
+   It is assumed that *STR is LEADING_CODE_COMPOSITION and the
+   following (LENGTH - 1) bytes satisfy !CHAR_HEAD_P.
+
+   If there is a valid composite character, set CHARSET, C1, and C2 to
+   such values that MAKE_CHAR can make the composite character from
+   them.  Otherwise, set CHARSET to CHARSET_COMPOSITION, set C1 to the
+   second byte of the sequence, C2 to -1 so that MAKE_CHAR can make
+   the invalid multibyte character whose string representation is two
+   bytes of STR[0] and STR[1].  In any case, set BYTES to LENGTH.
+
+   This macro should be called only from SPLIT_MULTIBYTE_SEQ.  */
+
+#define SPLIT_COMPOSITE_SEQ(str, length, bytes, charset, c1, c2)       \
+  do {                                                                 \
+    int cmpchar_id = str_cmpchar_id ((str), (length));                 \
+                                                                       \
+    (charset) = CHARSET_COMPOSITION;                                   \
+    (bytes) = (length);                                                        \
+    if (cmpchar_id >= 0)                                               \
+      {                                                                        \
+       (c1) = CHAR_FIELD2 (cmpchar_id);                                \
+       (c2) = CHAR_FIELD3 (cmpchar_id);                                \
+      }                                                                        \
+    else                                                               \
+      {                                                                        \
+       (c1) = (str)[1] & 0x7F;                                         \
+       (c2) = -1;                                                      \
+      }                                                                        \
+  } while (0)
+
+/* Parse non-composite multibyte character string STR of length LENGTH
+   (>= 2) and set BYTES to the length of actual multibyte sequence,
+   CHARSET, C1, and C2 to such values that MAKE_CHAR can make the
+   multibyte character from them.
+
+   It is assumed that *STR is one of base leading codes (excluding
+   LEADING_CODE_COMPOSITION) and the following (LENGTH - 1) bytes
+   satisfy !CHAR_HEAD_P.
+
+   This macro should be called only from SPLIT_MULTIBYTE_SEQ.  */
+
+#define SPLIT_CHARACTER_SEQ(str, length, bytes, charset, c1, c2)       \
+  do {                                                                 \
+    (bytes) = 1;                                                       \
+    (charset) = (str)[0];                                              \
+    if ((charset) >= LEADING_CODE_PRIVATE_11                           \
+       && (charset) <= LEADING_CODE_PRIVATE_22)                        \
+      (charset) = (str)[(bytes)++];                                    \
+    if ((bytes) < (length))                                            \
+      {                                                                        \
+       (c1) = (str)[(bytes)++] & 0x7F;                                 \
+       if ((bytes) < (length))                                         \
+         (c2) = (str)[(bytes)++] & 0x7F;                               \
+       else                                                            \
+         (c2) = -1;                                                    \
+      }                                                                        \
+    else                                                               \
+      (c1) = (c2) = -1;                                                        \
+  } while (0)
+
+/* Parse string STR of length LENGTH and check if a multibyte
+   characters is at STR.  set BYTES to the actual length, CHARSET, C1,
+   C2 to proper values for that character.  */
+
+#define SPLIT_MULTIBYTE_SEQ(str, length, bytes, charset, c1, c2)       \
+  do {                                                                 \
+    int i;                                                             \
+    if (ASCII_BYTE_P ((str)[0]))                                       \
+      i = 1;                                                           \
+    else                                                               \
+      for (i = 1; i < (length) && ! CHAR_HEAD_P ((str)[i]); i++);      \
+    if (i == 1)                                                                \
+      (bytes) = 1, (charset) = CHARSET_ASCII, (c1) = (str)[0] ;                \
+    else if ((str)[0] == LEADING_CODE_COMPOSITION)                     \
+      SPLIT_COMPOSITE_SEQ (str, i, bytes, charset, c1, c2);            \
+    else                                                               \
+      {                                                                        \
+       if (i > BYTES_BY_CHAR_HEAD ((str)[0]))                          \
+         i = BYTES_BY_CHAR_HEAD ((str)[0]);                            \
+       SPLIT_CHARACTER_SEQ (str, i, bytes, charset, c1, c2);           \
+      }                                                                        \
+  } while (0)
+
+/* 1 if CHARSET, C1, and C2 compose a valid character, else 0.  */
+#define CHAR_COMPONENTS_VALID_P(charset, c1, c2)       \
+  (CHARSET_DIMENSION (charset) == 1            \
+   ? ((c1) >= 0x20 && (c1) <= 0x7F)            \
+   : ((c1) >= 0x20 && (c1) <= 0x7F && (c2) >= 0x20 && (c2) <= 0x7F))
 
 /* Set STR a pointer to the multi-byte form of the character C.  If C
    is not a composite character, the multi-byte form is set in WORKBUF
    and STR points WORKBUF.  The caller should allocate at least 4-byte
    area at WORKBUF in advance.  Returns the length of the multi-byte
-   form.  If C is an invalid character to have a multi-byte form,
-   signal an error.
+   form.  If C is an invalid character, store (C & 0xFF) in WORKBUF[0]
+   and return 1.
 
    Use macro `CHAR_STRING (C, WORKBUF, STR)' instead of calling this
    function directly if C can be an ASCII character.  */
@@ -139,162 +231,184 @@ non_ascii_char_to_string (c, workbuf, str)
      int c;
      unsigned char *workbuf, **str;
 {
-  int charset, c1, c2;
-
-  if (COMPOSITE_CHAR_P (c))
+  if (c & CHAR_MODIFIER_MASK)  /* This includes the case C is negative.  */
     {
-      int cmpchar_id = COMPOSITE_CHAR_ID (c);
+      /* Multibyte character can't have a modifier bit.  */
+      if (! SINGLE_BYTE_CHAR_P ((c & ~CHAR_MODIFIER_MASK)))
+       invalid_character (c);
 
-      if (cmpchar_id < n_cmpchars)
+      /* For Meta, Shift, and Control modifiers, we need special care.  */
+      if (c & CHAR_META)
        {
-         *str = cmpchar_table[cmpchar_id]->data;
-         return cmpchar_table[cmpchar_id]->len;
+         /* Move the meta bit to the right place for a string.  */
+         c = (c & ~CHAR_META) | 0x80;
        }
-      else
+      if (c & CHAR_SHIFT)
        {
-         invalid_character (c);
+         /* Shift modifier is valid only with [A-Za-z].  */
+         if ((c & 0377) >= 'A' && (c & 0377) <= 'Z')
+           c &= ~CHAR_SHIFT;
+         else if ((c & 0377) >= 'a' && (c & 0377) <= 'z')
+           c = (c & ~CHAR_SHIFT) - ('a' - 'A');
+       }
+      if (c & CHAR_CTL)
+       {
+         /* Simulate the code in lread.c.  */
+         /* Allow `\C- ' and `\C-?'.  */
+         if (c == (CHAR_CTL | ' '))
+           c = 0;
+         else if (c == (CHAR_CTL | '?'))
+           c = 127;
+         /* ASCII control chars are made from letters (both cases),
+            as well as the non-letters within 0100...0137.  */
+         else if ((c & 0137) >= 0101 && (c & 0137) <= 0132)
+           c &= (037 | (~0177 & ~CHAR_CTL));
+         else if ((c & 0177) >= 0100 && (c & 0177) <= 0137)
+           c &= (037 | (~0177 & ~CHAR_CTL));
        }
-    }
-
-  SPLIT_NON_ASCII_CHAR (c, charset, c1, c2);
-  if (!charset
-      || ! CHARSET_DEFINED_P (charset)
-      || c1 >= 0 && c1 < 32
-      || c2 >= 0 && c2 < 32)
-    invalid_character (c);
-
-  *str = workbuf;
-  *workbuf++ = CHARSET_LEADING_CODE_BASE (charset);
-  if (*workbuf = CHARSET_LEADING_CODE_EXT (charset))
-    workbuf++;
-  *workbuf++ = c1 | 0x80;
-  if (c2 >= 0)
-    *workbuf++ = c2 | 0x80;
-
-  return (workbuf - *str);
-}
-
-/* Return a non-ASCII character of which multi-byte form is at STR of
-   length LEN.  If ACTUAL_LEN is not NULL, the actual length of the
-   multibyte form is set to the address ACTUAL_LEN.
-
-   If exclude_tail_garbage is nonzero, ACTUAL_LEN excludes gabage
-   bytes following the non-ASCII character.
-
-   Use macro `STRING_CHAR (STR, LEN)' instead of calling this function
-   directly if STR can hold an ASCII character.  */
-
-int
-string_to_non_ascii_char (str, len, actual_len, exclude_tail_garbage)
-     const unsigned char *str;
-     int len, *actual_len, exclude_tail_garbage;
-{
-  int charset;
-  unsigned char c1, c2;
-  int c, bytes;
-  const unsigned char *begp = str;
 
-  c = *str++;
-  bytes = 1;
+      /* If C still has any modifier bits, it is an invalid character.  */
+      if (c & CHAR_MODIFIER_MASK)
+       invalid_character (c);
 
-  if (BASE_LEADING_CODE_P (c))
-    do {
-      while (bytes < len && ! CHAR_HEAD_P (begp[bytes])) bytes++;
+      *str = workbuf;
+      *workbuf++ = c;
+    }
+  else
+    {
+      int charset, c1, c2;
 
-      if (c == LEADING_CODE_COMPOSITION)
+      SPLIT_NON_ASCII_CHAR (c, charset, c1, c2);
+      if (charset == CHARSET_COMPOSITION)
        {
-         int cmpchar_id = str_cmpchar_id (begp, bytes);
-
-         if (cmpchar_id >= 0)
+         if (c >= MAX_CHAR)
+           invalid_character (c);
+         if (c >= MIN_CHAR_COMPOSITION)
            {
-             c = MAKE_COMPOSITE_CHAR (cmpchar_id);
-             str += cmpchar_table[cmpchar_id]->len - 1;
+             /* Valid composite character.  */
+             *str = cmpchar_table[COMPOSITE_CHAR_ID (c)]->data;
+             workbuf = *str + cmpchar_table[COMPOSITE_CHAR_ID (c)]->len;
            }
          else
-           str += bytes - 1;
+           {
+             /* Invalid but can have multibyte form.  */
+             *str = workbuf;
+             *workbuf++ = LEADING_CODE_COMPOSITION;
+             *workbuf++ = c1 | 0x80;
+           }
        }
-      else
+      else if (charset > CHARSET_COMPOSITION)
        {
-         const unsigned char *endp = begp + bytes;
-         int charset = c, c1, c2 = 0;
-
-         if (str >= endp) break;
-         if (c >= LEADING_CODE_PRIVATE_11 && c <= LEADING_CODE_PRIVATE_22)
+         *str = workbuf;
+         if (charset >= LEADING_CODE_EXT_11)
+           *workbuf++ = (charset < LEADING_CODE_EXT_12
+                         ? LEADING_CODE_PRIVATE_11
+                         : (charset < LEADING_CODE_EXT_21
+                            ? LEADING_CODE_PRIVATE_12
+                            : (charset < LEADING_CODE_EXT_22
+                               ? LEADING_CODE_PRIVATE_21
+                               : LEADING_CODE_PRIVATE_22)));
+         *workbuf++ = charset;
+         if (c1 > 0 && c1 < 32 || c2 > 0 && c2 < 32)
+           invalid_character (c);
+         if (c1)
            {
-             charset = *str++;
-             if (str < endp)
-               c1 = *str++ & 0x7F;
-             else
-               c1 = charset, charset = c;
+             *workbuf++ = c1 | 0x80;
+             if (c2 > 0)
+               *workbuf++ = c2 | 0x80;
            }
-         else
-           c1 = *str++ & 0x7f;
-         if (CHARSET_DEFINED_P (charset)
-             && CHARSET_DIMENSION (charset) == 2
-             && str < endp)
-           c2 = *str++ & 0x7F;
-         c = MAKE_NON_ASCII_CHAR (charset, c1, c2);
        }
-    } while (0);
+      else if (charset == CHARSET_ASCII)
+       *workbuf++= c & 0x7F;
+      else
+       invalid_character (c);
+    }
+
+  return (workbuf - *str);
+}
 
+/* Return the non-ASCII character corresponding to multi-byte form at
+   STR of length LEN.  If ACTUAL_LEN is not NULL, store the byte
+   length of the multibyte form in *ACTUAL_LEN.
+
+   Use macro `STRING_CHAR (STR, LEN)' instead of calling this function
+   directly if you want ot handle ASCII characters as well.  */
+
+int
+string_to_non_ascii_char (str, len, actual_len)
+     const unsigned char *str;
+     int len, *actual_len;
+{
+  int c, bytes, charset, c1, c2;
+
+  SPLIT_MULTIBYTE_SEQ (str, len, bytes, charset, c1, c2);
+  c = MAKE_CHAR (charset, c1, c2);
   if (actual_len)
-    *actual_len = exclude_tail_garbage ? str - begp : bytes;
+    *actual_len = bytes;
   return c;
 }
 
-/* Return the length of the multi-byte form at string STR of length LEN.  */
+/* Return the length of the multi-byte form at string STR of length LEN.
+   Use the macro MULTIBYTE_FORM_LENGTH instead.  */
 int
 multibyte_form_length (str, len)
      const unsigned char *str;
      int len;
 {
-  int bytes = 1;
-
-  if (BASE_LEADING_CODE_P (*str))
-    while (bytes < len && ! CHAR_HEAD_P (str[bytes])) bytes++;
+  int bytes;
 
+  PARSE_MULTIBYTE_SEQ (str, len, bytes);
   return bytes;
 }
 
-/* Check if string STR of length LEN contains valid multi-byte form of
-   a character.  If valid, charset and position codes of the character
-   is set at *CHARSET, *C1, and *C2, and return 0.  If not valid,
+/* Check multibyte form at string STR of length LEN and set variables
+   pointed by CHARSET, C1, and C2 to charset and position codes of the
+   character at STR, and return 0.  If there's no multibyte character,
    return -1.  This should be used only in the macro SPLIT_STRING
    which checks range of STR in advance.  */
 
 int
 split_non_ascii_string (str, len, charset, c1, c2)
-     register const unsigned char *str;
-     register unsigned char *c1, *c2;
-     register int len, *charset;
+     const unsigned char *str;
+     unsigned char *c1, *c2;
+     int len, *charset;
 {
-  register unsigned int cs = *str++;
+  register int bytes, cs, code1, code2 = -1;
 
-  if (cs == LEADING_CODE_COMPOSITION)
-    {
-      int cmpchar_id = str_cmpchar_id (str - 1, len);
-
-      if (cmpchar_id < 0)
-       return -1;
-      *charset = cs, *c1 = cmpchar_id >> 7, *c2 = cmpchar_id & 0x7F;
-    }
-  else if ((cs < LEADING_CODE_PRIVATE_11 || (cs = *str++) >= 0xA0)
-          && CHARSET_DEFINED_P (cs))
-    {
-      *charset = cs;
-      if (*str < 0xA0)
-       return -1;
-      *c1 = (*str++) & 0x7F;
-      if (CHARSET_DIMENSION (cs) == 2)
-       {
-         if (*str < 0xA0)
-           return -1;
-         *c2 = (*str++) & 0x7F;
-       }
-    }
-  else
+  SPLIT_MULTIBYTE_SEQ (str, len, bytes, cs, code1, code2);
+  if (cs == CHARSET_ASCII)
     return -1;
-  return 0;
+  *charset = cs;
+  *c1 = code1;
+  *c2 = code2;
+}
+
+/* Return 1 iff character C has valid printable glyph.
+   Use the macro CHAR_PRINTABLE_P instead.  */
+int
+char_printable_p (c)
+     int c;
+{
+  int charset, c1, c2, chars;
+
+  if (SINGLE_BYTE_CHAR_P (c))
+    return 1;
+  if (c >= MIN_CHAR_COMPOSITION)
+    return (c < MAX_CHAR);
+  
+  SPLIT_NON_ASCII_CHAR (c, charset, c1, c2);
+  if (! CHARSET_DEFINED_P (charset))
+    return 0;
+  if (CHARSET_CHARS (charset) == 94
+      ? c1 <= 32 || c1 >= 127
+      : c1 < 32)
+    return 0;
+  if (CHARSET_DIMENSION (charset) == 2
+      && (CHARSET_CHARS (charset) == 94
+         ? c2 <= 32 || c2 >= 127
+         : c2 < 32))
+    return 0;
+  return 1;
 }
 
 /* Translate character C by translation table TABLE.  If C
@@ -311,8 +425,7 @@ translate_char (table, c, charset, c1, c2)
 
   if (c < 0) c = MAKE_CHAR (charset, c1, c2);
   if (!CHAR_TABLE_P (table)
-      || (ch = Faref (table, make_number (c)), !INTEGERP (ch))
-      || XINT (ch) < 0)
+      || (ch = Faref (table, make_number (c)), !NATNUMP (ch)))
     return c;
 
   SPLIT_CHAR (XFASTINT (ch), alt_charset, alt_c1, alt_c2);
@@ -340,20 +453,20 @@ int
 unibyte_char_to_multibyte (c)
      int c;
 {
-  if (c < 0400)
+  if (c < 0400 && c >= 0200)
     {
       int c_save = c;
 
       if (! NILP (Vnonascii_translation_table))
        {
          c = XINT (Faref (Vnonascii_translation_table, make_number (c)));
-         if (c >= 0400 && ! VALID_MULTIBYTE_CHAR_P (c))
+         if (c >= 0400 && ! char_valid_p (c, 0))
            c = c_save + DEFAULT_NONASCII_INSERT_OFFSET;
        }
       else if (c >= 0240 && nonascii_insert_offset > 0)
        {
          c += nonascii_insert_offset;
-         if (c < 0400 || ! VALID_MULTIBYTE_CHAR_P (c))
+         if (c < 0400 || ! char_valid_p (c, 0))
            c = c_save + DEFAULT_NONASCII_INSERT_OFFSET;
        }
       else if (c >= 0240)
@@ -718,17 +831,42 @@ CHARSET should be defined by `defined-charset' in advance.")
 
    If CMPCHARP is nonzero and some composite character is found,
    CHARSETS[128] is also set 1 and the returned number is incremented
-   by 1.  */
+   by 1.
+
+   If MULTIBYTE is zero, do not check multibyte characters, i.e. if
+   any ASCII codes (7-bit) are found, CHARSET[0] is set to 1, if any
+   8-bit codes are found CHARSET[1] is set to 1.  */
 
 int
-find_charset_in_str (str, len, charsets, table, cmpcharp)
+find_charset_in_str (str, len, charsets, table, cmpcharp, multibyte)
      unsigned char *str;
      int len, *charsets;
      Lisp_Object table;
      int cmpcharp;
+     int multibyte;
 {
   register int num = 0, c;
 
+  if (! multibyte)
+    {
+      unsigned char *endp = str + len;
+      int maskbits = 0;
+       
+      while (str < endp && maskbits != 3)
+       maskbits |=  (*str++ < 0x80 ? 1 : 2);
+      if (maskbits & 1)
+       {
+         charsets[0] = 1;
+         num++;
+       }
+      if (maskbits & 2)
+       {
+         charsets[1] = 1;
+         num++;
+       }
+      return num;
+    }
+
   if (! CHAR_TABLE_P (table))
     table = Qnil;
 
@@ -773,7 +911,7 @@ find_charset_in_str (str, len, charsets, table, cmpcharp)
              continue;
            }
 
-         charset = CHARSET_ASCII;
+         charset = 1;          /* This leads to `unknown' charset.  */
          bytes = 1;
        }
       else
@@ -805,23 +943,27 @@ DEFUN ("find-charset-region", Ffind_charset_region, Sfind_charset_region,
 BEG and END are buffer positions.\n\
 If the region contains any composite character,\n\
 `composition' is included in the returned list.\n\
-Optional arg TABLE if non-nil is a translation table to look up.")
+Optional arg TABLE if non-nil is a translation table to look up.\n\
+\n\
+If the region contains invalid multiybte characters,\n\
+`unknown' is included in the returned list.\n\
+\n\
+If the current buffer is unibyte, the returned list contains\n\
+`ascii' if any 7-bit characters are found,\n\
+and `unknown' if any 8-bit characters are found.")
   (beg, end, table)
      Lisp_Object beg, end, table;
 {
   int charsets[MAX_CHARSET + 1];
   int from, from_byte, to, stop, stop_byte, i;
   Lisp_Object val;
+  int undefined;
+  int multibyte = !NILP (current_buffer->enable_multibyte_characters);
 
   validate_region (&beg, &end);
   from = XFASTINT (beg);
   stop = to = XFASTINT (end);
 
-  if (NILP (current_buffer->enable_multibyte_characters))
-    return (from == to
-           ? Qnil
-           : Fcons (Qascii, Qnil));
-
   if (from < GPT && GPT < to)
     {
       stop = GPT;
@@ -836,7 +978,7 @@ Optional arg TABLE if non-nil is a translation table to look up.")
   while (1)
     {
       find_charset_in_str (BYTE_POS_ADDR (from_byte), stop_byte - from_byte,
-                          charsets, table, 1);
+                          charsets, table, 1, multibyte);
       if (stop < to)
        {
          from = stop, from_byte = stop_byte;
@@ -847,9 +989,17 @@ Optional arg TABLE if non-nil is a translation table to look up.")
     }
 
   val = Qnil;
-  for (i = MAX_CHARSET; i >= 0; i--)
+  undefined = 0;
+  for (i = (multibyte ? MAX_CHARSET : 1); i >= 0; i--)
     if (charsets[i])
-      val = Fcons (CHARSET_SYMBOL (i), val);
+      {
+       if (CHARSET_DEFINED_P (i) || i == CHARSET_COMPOSITION)
+         val = Fcons (CHARSET_SYMBOL (i), val);
+       else
+         undefined = 1;
+      }
+  if (undefined)
+    val = Fcons (Qunknown, val);
   return val;
 }
 
@@ -858,28 +1008,41 @@ DEFUN ("find-charset-string", Ffind_charset_string, Sfind_charset_string,
   "Return a list of charsets in STR.\n\
 If the string contains any composite characters,\n\
 `composition' is included in the returned list.\n\
-Optional arg TABLE if non-nil is a translation table to look up.")
+Optional arg TABLE if non-nil is a translation table to look up.\n\
+\n\
+If the region contains invalid multiybte characters,\n\
+`unknown' is included in the returned list.\n\
+\n\
+If STR is unibyte, the returned list contains\n\
+`ascii' if any 7-bit characters are found,\n\
+and `unknown' if any 8-bit characters are found.")
   (str, table)
      Lisp_Object str, table;
 {
   int charsets[MAX_CHARSET + 1];
   int i;
   Lisp_Object val;
+  int undefined;
+  int multibyte;
 
   CHECK_STRING (str, 0);
-
-  if (! STRING_MULTIBYTE (str))
-    return (XSTRING (str)->size == 0
-           ? Qnil
-           : Fcons (Qascii, Qnil));
+  multibyte = STRING_MULTIBYTE (str);
 
   bzero (charsets, (MAX_CHARSET + 1) * sizeof (int));
   find_charset_in_str (XSTRING (str)->data, STRING_BYTES (XSTRING (str)),
-                      charsets, table, 1);
+                      charsets, table, 1, multibyte);
   val = Qnil;
-  for (i = MAX_CHARSET; i >= 0; i--)
+  undefined = 0;
+  for (i = (multibyte ? MAX_CHARSET : 1); i >= 0; i--)
     if (charsets[i])
-      val = Fcons (CHARSET_SYMBOL (i), val);
+      {
+       if (CHARSET_DEFINED_P (i) || i == CHARSET_COMPOSITION)
+         val = Fcons (CHARSET_SYMBOL (i), val);
+       else
+         undefined = 1;
+      }
+  if (undefined)
+    val = Fcons (Qunknown, val);
   return val;
 }
 \f
@@ -888,32 +1051,56 @@ DEFUN ("make-char-internal", Fmake_char_internal, Smake_char_internal, 1, 3, 0,
   (charset, code1, code2)
      Lisp_Object charset, code1, code2;
 {
+  int charset_id, c1, c2;
+
   CHECK_NUMBER (charset, 0);
+  charset_id = XINT (charset);
+  if (!CHARSET_DEFINED_P (charset_id))
+    error ("Invalid charset ID: %d", XINT (charset));
 
   if (NILP (code1))
-    XSETFASTINT (code1, 0);
+    c1 = 0;
   else
-    CHECK_NUMBER (code1, 1);
+    {
+      CHECK_NUMBER (code1, 1);
+      c1 = XINT (code1);
+    }
   if (NILP (code2))
-    XSETFASTINT (code2, 0);
+    c2 = 0;
   else
-    CHECK_NUMBER (code2, 2);
-
-  if (!CHARSET_DEFINED_P (XINT (charset)))
-    error ("Invalid charset: %d", XINT (charset));
+    {
+      CHECK_NUMBER (code2, 2);
+      c2 = XINT (code2);
+    }
 
-  return make_number (MAKE_CHAR (XINT (charset), XINT (code1), XINT (code2)));
+  if (c1 < 0 || c1 > 0xFF || c2 < 0 || c2 > 0xFF)
+    error ("Invalid code points: %d %d", c1, c2);
+  c1 &= 0x7F;
+  c2 &= 0x7F;
+  if (c1 == 0
+      ? c2 != 0
+      : (c2 == 0
+        ? !CHAR_COMPONENTS_VALID_P (charset, c1, 0x20)
+        : !CHAR_COMPONENTS_VALID_P (charset, c1, c2)))
+    error ("Invalid code points: %d %d", c1, c2);
+
+  return make_number (MAKE_CHAR (charset_id, c1, c2));
 }
 
 DEFUN ("split-char", Fsplit_char, Ssplit_char, 1, 1, 0,
-  "Return list of charset and one or two position-codes of CHAR.")
+  "Return list of charset and one or two position-codes of CHAR.\n\
+If CHAR is invalid as a character code,\n\
+return a list of symbol `unknown' and CHAR.")
   (ch)
      Lisp_Object ch;
 {
   Lisp_Object val;
-  int charset, c1, c2;
+  int c, charset, c1, c2;
 
   CHECK_NUMBER (ch, 0);
+  c = XFASTINT (ch);
+  if (!CHAR_VALID_P (c, 1))
+    return Fcons (Qunknown, Fcons (ch, Qnil));
   SPLIT_CHAR (XFASTINT (ch), charset, c1, c2);
   return (c2 >= 0
          ? Fcons (CHARSET_SYMBOL (charset),
@@ -932,26 +1119,40 @@ DEFUN ("char-charset", Fchar_charset, Schar_charset, 1, 1, 0,
 }
 
 DEFUN ("charset-after", Fcharset_after, Scharset_after, 0, 1, 0,
-  "Return charset of a character in current buffer at position POS.\n\
-If POS is nil, it defauls to the current point.")
+  "Return charset of a character in the current buffer at position POS.\n\
+If POS is nil, it defauls to the current point.\n\
+If POS is out of range, the value is nil.")
   (pos)
      Lisp_Object pos;
 {
-  register int pos_byte, c, charset;
+  register int pos_byte, bytes, charset, c1, c2;
   register unsigned char *p;
 
   if (NILP (pos))
     pos_byte = PT_BYTE;
   else if (MARKERP (pos))
-    pos_byte = marker_byte_position (pos);
+    {
+      pos_byte = marker_byte_position (pos);
+      if (pos_byte < BEGV_BYTE || pos_byte >= ZV_BYTE)
+       return Qnil;
+    }
   else
     {
       CHECK_NUMBER (pos, 0);
+      if (XINT (pos) < BEGV || XINT (pos) >= ZV)
+       return Qnil;
       pos_byte = CHAR_TO_BYTE (XINT (pos));
     }
   p = BYTE_POS_ADDR (pos_byte);
-  c = STRING_CHAR (p, Z_BYTE - pos_byte);
-  charset = CHAR_CHARSET (c);
+  if (BASE_LEADING_CODE_P (*p))
+    {
+      SPLIT_MULTIBYTE_SEQ (p, Z_BYTE - pos_byte, bytes, charset, c1, c2);
+      if (charset < 0)
+       charset = 1;
+    }
+  else
+    charset = CHARSET_ASCII;
+
   return CHARSET_SYMBOL (charset);
 }
 
@@ -992,15 +1193,23 @@ char_valid_p (c, genericp)
   if (SINGLE_BYTE_CHAR_P (c))
     return 1;
   SPLIT_NON_ASCII_CHAR (c, charset, c1, c2);
-  if (!CHARSET_DEFINED_P (charset))
-    return 0;
-  return (c < MIN_CHAR_COMPOSITION
-         ? ((c & CHAR_FIELD1_MASK) /* i.e. dimension of C is two.  */
-            ? (genericp && c1 == 0 && c2 == 0
-               || c1 >= 32 && c2 >= 32)
-            : (genericp && c1 == 0
-               || c1 >= 32))
-         : c < MIN_CHAR_COMPOSITION + n_cmpchars);
+  if (charset == CHARSET_COMPOSITION)
+    return ((c >= MIN_CHAR_COMPOSITION
+            && c < MIN_CHAR_COMPOSITION + n_cmpchars)
+           || (genericp && c == GENERIC_COMPOSITION_CHAR));
+  if (genericp)
+    {
+      if (c1)
+       {
+         if (c2 <= 0) c2 = 0x20;
+       }
+      else
+       {
+         if (c2 <= 0) c1 = c2 = 0x20;
+       }
+    }
+  return (CHARSET_DEFINED_P (charset)
+         && CHAR_COMPONENTS_VALID_P (charset, c1, c2));
 }
 
 DEFUN ("char-valid-p", Fchar_valid_p, Schar_valid_p, 1, 2, 0,
@@ -1047,7 +1256,7 @@ The conversion is done based on `nonascii-translation-table' (which see)\n\
 
   CHECK_NUMBER (ch, 0);
   c = XINT (ch);
-  if (c < 0)
+  if (! CHAR_VALID_P (c, 0))
     error ("Invalid multibyte character: %d", c);
   c = multibyte_char_to_unibyte (c, Qnil);
   if (c < 0)
@@ -1075,6 +1284,9 @@ char_bytes (c)
 {
   int bytes;
 
+  if (SINGLE_BYTE_CHAR_P (c) || (c & ~GLYPH_MASK_CHAR))
+    return 1;
+
   if (COMPOSITE_CHAR_P (c))
     {
       unsigned int id = COMPOSITE_CHAR_ID (c);
@@ -1133,7 +1345,7 @@ The width is measured by how many columns it occupies on the screen.")
   else if (COMPOSITE_CHAR_P (c))
     {
       int id = COMPOSITE_CHAR_ID (XFASTINT (ch));
-      XSETFASTINT (val, (id < n_cmpchars ? cmpchar_table[id]->width : 0));
+      XSETFASTINT (val, (id < n_cmpchars ? cmpchar_table[id]->width : 1));
     }
   else
     {
@@ -1255,27 +1467,12 @@ chars_in_text (ptr, nbytes)
      unsigned char *ptr;
      int nbytes;
 {
-  unsigned char *endp, c;
-  int chars;
-
   /* current_buffer is null at early stages of Emacs initialization.  */
   if (current_buffer == 0
       || NILP (current_buffer->enable_multibyte_characters))
     return nbytes;
 
-  endp = ptr + nbytes;
-  chars = 0;
-
-  while (ptr < endp)
-    {
-      c = *ptr++;
-
-      if (BASE_LEADING_CODE_P (c))
-       while (ptr < endp && ! CHAR_HEAD_P (*ptr)) ptr++;
-      chars++;
-    }
-
-  return chars;
+  return multibyte_chars_in_text (ptr, nbytes);
 }
 
 /* Return the number of characters in the NBYTES bytes at PTR.
@@ -1287,18 +1484,25 @@ multibyte_chars_in_text (ptr, nbytes)
      unsigned char *ptr;
      int nbytes;
 {
-  unsigned char *endp, c;
-  int chars;
+  unsigned char *endp;
+  int chars, bytes;
 
   endp = ptr + nbytes;
   chars = 0;
 
   while (ptr < endp)
     {
-      c = *ptr++;
-
-      if (BASE_LEADING_CODE_P (c))
-       while (ptr < endp && ! CHAR_HEAD_P (*ptr)) ptr++;
+      if (BASE_LEADING_CODE_P (*ptr))
+       {
+         PARSE_MULTIBYTE_SEQ (ptr, nbytes, bytes);
+         ptr += bytes;
+         nbytes -= bytes;
+       }
+      else
+       {
+         ptr++;
+         nbytes--;
+       }
       chars++;
     }
 
@@ -1400,7 +1604,7 @@ str_cmpchar_id (str, len)
   int i;
   struct cmpchar_info *cmpcharp;
 
-  /* The second byte 0xFF means compostion rule is embedded.  */
+  /* The second byte 0xFF means COMPOSITION rule is embedded.  */
   embedded_rule = (str[1] == 0xFF);
 
   /* At first, get the actual length of the composite character.  */
@@ -1417,7 +1621,12 @@ str_cmpchar_id (str, len)
     p = str + 1;
     while (p < endp)
       {
-       if (embedded_rule) p++;
+       if (embedded_rule)
+         {
+           p++;
+           if (p >= endp)
+             return -1;
+         }
        /* No need of checking if *P is 0xA0 because
           BYTES_BY_CHAR_HEAD (0x80) surely returns 2.  */
        p += BYTES_BY_CHAR_HEAD (*p - 0x20);
@@ -1444,7 +1653,7 @@ str_cmpchar_id (str, len)
       }
 
   /* We have to register the composite character in cmpchar_table.  */
-  if (n_cmpchars > (CHAR_FIELD2_MASK | CHAR_FIELD3_MASK))
+  if (n_cmpchars >= (CHAR_FIELD2_MASK | CHAR_FIELD3_MASK))
     /* No, we have no more room for a new composite character.  */
     return -1;
 
@@ -1531,7 +1740,7 @@ str_cmpchar_id (str, len)
            /* Make `bufp' point normal multi-byte form temporally.  */
            *bufp -= 0x20;
            cmpcharp->glyph[i]
-             = FAST_MAKE_GLYPH (string_to_non_ascii_char (bufp, 4, 0, 0), 0);
+             = FAST_MAKE_GLYPH (string_to_non_ascii_char (bufp, 4, 0), 0);
            width = WIDTH_BY_CHAR_HEAD (*bufp);
            *bufp += 0x20;
            bufp += BYTES_BY_CHAR_HEAD (*bufp - 0x20);
@@ -1597,16 +1806,27 @@ str_cmpchar_id (str, len)
   return n_cmpchars++;
 }
 
-/* Return the Nth element of the composite character C.  */
+/* Return the Nth element of the composite character C.  If NOERROR is
+   nonzero, return 0 on error condition (C is an invalid composite
+   charcter, or N is out of range). */
 int
-cmpchar_component (c, n)
-     unsigned int c, n;
+cmpchar_component (c, n, noerror)
+     int c, n, noerror;
 {
   int id = COMPOSITE_CHAR_ID (c);
 
-  if (id >= n_cmpchars         /* C is not a valid composite character.  */
-      || n >= cmpchar_table[id]->glyph_len) /* No such component.  */
-    return -1;
+  if (id < 0 || id >= n_cmpchars)
+    {
+      /* C is not a valid composite character.  */
+      if (noerror) return 0;
+      error ("Invalid composite character: %d", c)  ;
+    }
+  if (n >= cmpchar_table[id]->glyph_len)
+    {
+      /* No such component.  */
+      if (noerror) return 0;
+      args_out_of_range (make_number (c), make_number (n));
+    }
   /* No face data is stored in glyph code.  */
   return ((int) (cmpchar_table[id]->glyph[n]));
 }
@@ -1622,30 +1842,28 @@ DEFUN ("cmpcharp", Fcmpcharp, Scmpcharp, 1, 1, 0,
 
 DEFUN ("composite-char-component", Fcmpchar_component, Scmpchar_component,
        2, 2, 0,
-  "Return the IDXth component character of composite character CHARACTER.")
-  (character, idx)
-     Lisp_Object character, idx;
+  "Return the Nth component character of composite character CHARACTER.")
+  (character, n)
+     Lisp_Object character, n;
 {
-  int c;
+  int id;
 
   CHECK_NUMBER (character, 0);
-  CHECK_NUMBER (idx, 1);
-
-  if ((c = cmpchar_component (XINT (character), XINT (idx))) < 0)
-    args_out_of_range (character, idx);
+  CHECK_NUMBER (n, 1);
 
-  return make_number (c);
+  return (make_number (cmpchar_component (XINT (character), XINT (n), 0)));
 }
 
 DEFUN ("composite-char-composition-rule", Fcmpchar_cmp_rule, Scmpchar_cmp_rule,
        2, 2, 0,
-  "Return the Nth composition rule embedded in composite character CHARACTER.\n\
+  "Return the Nth composition rule of composite character CHARACTER.\n\
 The returned rule is for composing the Nth component\n\
-on the (N-1)th component.  If N is 0, the returned value is always 255.")
+on the (N-1)th component.\n\
+If CHARACTER should be composed relatively or N is 0, return 255.")
   (character, n)
      Lisp_Object character, n;
 {
-  int id, i;
+  int id;
 
   CHECK_NUMBER (character, 0);
   CHECK_NUMBER (n, 1);
@@ -1653,11 +1871,12 @@ on the (N-1)th component.  If N is 0, the returned value is always 255.")
   id = COMPOSITE_CHAR_ID (XINT (character));
   if (id < 0 || id >= n_cmpchars)
     error ("Invalid composite character: %d", XINT (character));
-  i = XINT (n);
-  if (i > cmpchar_table[id]->glyph_len)
+  if (XINT (n) < 0 || XINT (n) >= cmpchar_table[id]->glyph_len)
     args_out_of_range (character, n);
 
-  return make_number (cmpchar_table[id]->cmp_rule[i]);
+  return make_number (cmpchar_table[id]->cmp_rule
+                     ? cmpchar_table[id]->cmp_rule[XINT (n)]
+                     : 255);
 }
 
 DEFUN ("composite-char-composition-rule-p", Fcmpchar_cmp_rule_p,
@@ -1709,7 +1928,7 @@ DEFUN ("compose-string", Fcompose_string, Scompose_string,
   i = 1;
   while (p < pend)
     {
-      if (*p < 0x20 || *p == 127) /* control code */
+      if (*p < 0x20) /* control code */
        error ("Invalid component character: %d", *p);
       else if (*p < 0x80)      /* ASCII */
        {
@@ -1726,8 +1945,12 @@ DEFUN ("compose-string", Fcompose_string, Scompose_string,
              LEADING_CODE_COMPOSITION, keep the remaining bytes
              unchanged.  */
          p++;
+         if (*p == 255)
+           error ("Can't compose a rule-based composition character");
          ptemp = p;
          while (! CHAR_HEAD_P (*p)) p++;
+         if (str_cmpchar_id (ptemp - 1, p - ptemp + 1) < 0)
+           error ("Can't compose an invalid composition character");
          if (i + (p - ptemp) >= MAX_LENGTH_OF_MULTI_BYTE_FORM)
            error ("Too long string to be composed: %s", XSTRING (str)->data);
          bcopy (ptemp, buf + i, p - ptemp);
@@ -1737,7 +1960,10 @@ DEFUN ("compose-string", Fcompose_string, Scompose_string,
        {
          /* Add 0x20 to the base leading-code, keep the remaining
              bytes unchanged.  */
-         len = BYTES_BY_CHAR_HEAD (*p);
+         int c = STRING_CHAR_AND_LENGTH (p, pend - p, len);
+
+         if (len <= 1 || ! CHAR_VALID_P (c, 0))
+           error ("Can't compose an invalid character");
          if (i + len >= MAX_LENGTH_OF_MULTI_BYTE_FORM)
            error ("Too long string to be composed: %s", XSTRING (str)->data);
          bcopy (p, buf + i, len);
@@ -1804,7 +2030,10 @@ init_charset_once ()
   Fput (Qcharset_table, Qchar_table_extra_slots, make_number (0));
   Vcharset_table = Fmake_char_table (Qcharset_table, Qnil);
 
-  Vcharset_symbol_table = Fmake_vector (make_number (MAX_CHARSET + 1), Qnil);
+  Qunknown = intern ("unknown");
+  staticpro (&Qunknown);
+  Vcharset_symbol_table = Fmake_vector (make_number (MAX_CHARSET + 1),
+                                       Qunknown);
 
   /* Setup tables.  */
   for (i = 0; i < 2; i++)