1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-23 07:19:15 +00:00
emacs/lisp/international/utf-8.el
2007-01-21 03:53:13 +00:00

1083 lines
39 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; utf-8.el --- UTF-8 decoding/encoding support -*- coding: iso-2022-7bit -*-
;; Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007
;; Free Software Foundation, Inc.
;; Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007
;; National Institute of Advanced Industrial Science and Technology (AIST)
;; Registration Number H14PRO021
;; Author: TAKAHASHI Naoto <ntakahas@m17n.org>
;; Maintainer: FSF
;; Keywords: multilingual, Unicode, UTF-8, i18n
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; The coding-system `mule-utf-8' basically supports encoding/decoding
;; of the following character sets to and from UTF-8:
;;
;; ascii
;; eight-bit-control
;; latin-iso8859-1
;; mule-unicode-0100-24ff
;; mule-unicode-2500-33ff
;; mule-unicode-e000-ffff
;;
;; On decoding, Unicode characters that do not fit into the above
;; character sets are handled as `eight-bit-control' or
;; `eight-bit-graphic' characters to retain the information about the
;; original byte sequence and text properties record the corresponding
;; unicode.
;;
;; Fixme: note that reading and writing invalid utf-8 may not be
;; idempotent -- to represent the bytes to fix that needs a new charset.
;;
;; Characters from other character sets can be encoded with mule-utf-8
;; by populating the translation table
;; `utf-translation-table-for-encode'. Hash tables
;; `utf-subst-table-for-decode' and `utf-subst-table-for-encode' are
;; used to support encoding and decoding of about a quarter of the CJK
;; space between U+3400 and U+DFFF.
;; UTF-8 is defined in RFC 3629. A sketch of the encoding is:
;; scalar | utf-8
;; value | 1st byte | 2nd byte | 3rd byte
;; --------------------+-----------+-----------+----------
;; 0000 0000 0xxx xxxx | 0xxx xxxx | |
;; 0000 0yyy yyxx xxxx | 110y yyyy | 10xx xxxx |
;; zzzz yyyy yyxx xxxx | 1110 zzzz | 10yy yyyy | 10xx xxxx
;;; Code:
(defvar ucs-mule-to-mule-unicode (make-char-table 'translation-table nil)
"Char table mapping characters to latin-iso8859-1 or mule-unicode-*.
If `unify-8859-on-encoding-mode' is non-nil, this table populates the
translation-table named `utf-translation-table-for-encode'.")
(define-translation-table 'utf-translation-table-for-encode)
;; Map Cyrillic and Greek to iso-8859 charsets, which take half the
;; space of mule-unicode. For Latin scripts this isn't very
;; important. Hebrew and Arabic might go here too when there's proper
;; support for them.
(defvar utf-fragmentation-table (make-char-table 'translation-table nil)
"Char-table normally mapping non-Latin mule-unicode-* chars to iso-8859-*.
If `utf-fragment-on-decoding' is non-nil, this table populates the
translation-table named `utf-translation-table-for-decode'")
(defvar utf-defragmentation-table (make-char-table 'translation-table nil)
"Char-table for reverse mapping of `utf-fragmentation-table'.
If `utf-fragment-on-decoding' is non-nil and
`unify-8859-on-encoding-mode' is nil, this table populates the
translation-table named `utf-translation-table-for-encode'")
(define-translation-table 'utf-translation-table-for-decode)
(defvar ucs-mule-cjk-to-unicode (make-hash-table :test 'eq)
"Hash table mapping Emacs CJK character sets to Unicode code points.
If `utf-translate-cjk-mode' is non-nil, this table populates the
translation-hash-table named `utf-subst-table-for-encode'.")
(define-translation-hash-table 'utf-subst-table-for-encode
ucs-mule-cjk-to-unicode)
(defvar ucs-unicode-to-mule-cjk (make-hash-table :test 'eq)
"Hash table mapping Unicode code points to Emacs CJK character sets.
If `utf-translate-cjk-mode' is non-nil, this table populates the
translation-hash-table named `utf-subst-table-for-decode'.")
(define-translation-hash-table 'utf-subst-table-for-decode
ucs-unicode-to-mule-cjk)
(mapc
(lambda (pair)
(aset utf-fragmentation-table (car pair) (cdr pair))
(aset utf-defragmentation-table (cdr pair) (car pair)))
'((?$,1&d(B . ?,F4(B) (?$,1&e(B . ?,F5(B) (?$,1&f(B . ?,F6(B) (?$,1&h(B . ?,F8(B) (?$,1&i(B . ?,F9(B)
(?$,1&j(B . ?,F:(B) (?$,1&l(B . ?,F<(B) (?$,1&n(B . ?,F>(B) (?$,1&o(B . ?,F?(B) (?$,1&p(B . ?,F@(B)
(?$,1&q(B . ?,FA(B) (?$,1&r(B . ?,FB(B) (?$,1&s(B . ?,FC(B) (?$,1&t(B . ?,FD(B) (?$,1&u(B . ?,FE(B)
(?$,1&v(B . ?,FF(B) (?$,1&w(B . ?,FG(B) (?$,1&x(B . ?,FH(B) (?$,1&y(B . ?,FI(B) (?$,1&z(B . ?,FJ(B)
(?$,1&{(B . ?,FK(B) (?$,1&|(B . ?,FL(B) (?$,1&}(B . ?,FM(B) (?$,1&~(B . ?,FN(B) (?$,1&(B . ?,FO(B)
(?$,1' (B . ?,FP(B) (?$,1'!(B . ?,FQ(B) (?$,1'#(B . ?,FS(B) (?$,1'$(B . ?,FT(B) (?$,1'%(B . ?,FU(B)
(?$,1'&(B . ?,FV(B) (?$,1''(B . ?,FW(B) (?$,1'((B . ?,FX(B) (?$,1')(B . ?,FY(B) (?$,1'*(B . ?,FZ(B)
(?$,1'+(B . ?,F[(B) (?$,1',(B . ?,F\(B) (?$,1'-(B . ?,F](B) (?$,1'.(B . ?,F^(B) (?$,1'/(B . ?,F_(B)
(?$,1'0(B . ?,F`(B) (?$,1'1(B . ?,Fa(B) (?$,1'2(B . ?,Fb(B) (?$,1'3(B . ?,Fc(B) (?$,1'4(B . ?,Fd(B)
(?$,1'5(B . ?,Fe(B) (?$,1'6(B . ?,Ff(B) (?$,1'7(B . ?,Fg(B) (?$,1'8(B . ?,Fh(B) (?$,1'9(B . ?,Fi(B)
(?$,1':(B . ?,Fj(B) (?$,1';(B . ?,Fk(B) (?$,1'<(B . ?,Fl(B) (?$,1'=(B . ?,Fm(B) (?$,1'>(B . ?,Fn(B)
(?$,1'?(B . ?,Fo(B) (?$,1'@(B . ?,Fp(B) (?$,1'A(B . ?,Fq(B) (?$,1'B(B . ?,Fr(B) (?$,1'C(B . ?,Fs(B)
(?$,1'D(B . ?,Ft(B) (?$,1'E(B . ?,Fu(B) (?$,1'F(B . ?,Fv(B) (?$,1'G(B . ?,Fw(B) (?$,1'H(B . ?,Fx(B)
(?$,1'I(B . ?,Fy(B) (?$,1'J(B . ?,Fz(B) (?$,1'K(B . ?,F{(B) (?$,1'L(B . ?,F|(B) (?$,1'M(B . ?,F}(B)
(?$,1'N(B . ?,F~(B)
(?$,1(!(B . ?,L!(B) (?$,1("(B . ?,L"(B) (?$,1(#(B . ?,L#(B) (?$,1($(B . ?,L$(B)
(?$,1(%(B . ?,L%(B) (?$,1(&(B . ?,L&(B) (?$,1('(B . ?,L'(B) (?$,1(((B . ?,L((B) (?$,1()(B . ?,L)(B)
(?$,1(*(B . ?,L*(B) (?$,1(+(B . ?,L+(B) (?$,1(,(B . ?,L,(B) (?$,1(.(B . ?,L.(B) (?$,1(/(B . ?,L/(B)
(?$,1(0(B . ?,L0(B) (?$,1(1(B . ?,L1(B) (?$,1(2(B . ?,L2(B) (?$,1(3(B . ?,L3(B) (?$,1(4(B . ?,L4(B)
(?$,1(5(B . ?,L5(B) (?$,1(6(B . ?,L6(B) (?$,1(7(B . ?,L7(B) (?$,1(8(B . ?,L8(B) (?$,1(9(B . ?,L9(B)
(?$,1(:(B . ?,L:(B) (?$,1(;(B . ?,L;(B) (?$,1(<(B . ?,L<(B) (?$,1(=(B . ?,L=(B) (?$,1(>(B . ?,L>(B)
(?$,1(?(B . ?,L?(B) (?$,1(@(B . ?,L@(B) (?$,1(A(B . ?,LA(B) (?$,1(B(B . ?,LB(B) (?$,1(C(B . ?,LC(B)
(?$,1(D(B . ?,LD(B) (?$,1(E(B . ?,LE(B) (?$,1(F(B . ?,LF(B) (?$,1(G(B . ?,LG(B) (?$,1(H(B . ?,LH(B)
(?$,1(I(B . ?,LI(B) (?$,1(J(B . ?,LJ(B) (?$,1(K(B . ?,LK(B) (?$,1(L(B . ?,LL(B) (?$,1(M(B . ?,LM(B)
(?$,1(N(B . ?,LN(B) (?$,1(O(B . ?,LO(B) (?$,1(P(B . ?,LP(B) (?$,1(Q(B . ?,LQ(B) (?$,1(R(B . ?,LR(B)
(?$,1(S(B . ?,LS(B) (?$,1(T(B . ?,LT(B) (?$,1(U(B . ?,LU(B) (?$,1(V(B . ?,LV(B) (?$,1(W(B . ?,LW(B)
(?$,1(X(B . ?,LX(B) (?$,1(Y(B . ?,LY(B) (?$,1(Z(B . ?,LZ(B) (?$,1([(B . ?,L[(B) (?$,1(\(B . ?,L\(B)
(?$,1(](B . ?,L](B) (?$,1(^(B . ?,L^(B) (?$,1(_(B . ?,L_(B) (?$,1(`(B . ?,L`(B) (?$,1(a(B . ?,La(B)
(?$,1(b(B . ?,Lb(B) (?$,1(c(B . ?,Lc(B) (?$,1(d(B . ?,Ld(B) (?$,1(e(B . ?,Le(B) (?$,1(f(B . ?,Lf(B)
(?$,1(g(B . ?,Lg(B) (?$,1(h(B . ?,Lh(B) (?$,1(i(B . ?,Li(B) (?$,1(j(B . ?,Lj(B) (?$,1(k(B . ?,Lk(B)
(?$,1(l(B . ?,Ll(B) (?$,1(m(B . ?,Lm(B) (?$,1(n(B . ?,Ln(B) (?$,1(o(B . ?,Lo(B) (?$,1(q(B . ?,Lq(B)
(?$,1(r(B . ?,Lr(B) (?$,1(s(B . ?,Ls(B) (?$,1(t(B . ?,Lt(B) (?$,1(u(B . ?,Lu(B) (?$,1(v(B . ?,Lv(B)
(?$,1(w(B . ?,Lw(B) (?$,1(x(B . ?,Lx(B) (?$,1(y(B . ?,Ly(B) (?$,1(z(B . ?,Lz(B) (?$,1({(B . ?,L{(B)
(?$,1(|(B . ?,L|(B) (?$,1(~(B . ?,L~(B) (?$,1((B . ?,L(B)))
(defcustom utf-fragment-on-decoding nil
"Whether or not to decode some chars in UTF-8/16 text into iso8859 charsets.
Setting this means that the relevant Cyrillic and Greek characters are
decoded into the iso8859 charsets rather than into
mule-unicode-0100-24ff. The iso8859 charsets take half as much space
in the buffer, but using them may affect how the buffer can be re-encoded
and may require a different input method to search for them, for instance.
See `unify-8859-on-decoding-mode' and `unify-8859-on-encoding-mode'
for mechanisms to make this largely transparent.
Setting this variable outside customize has no effect."
:set (lambda (s v)
(if v
(progn
(define-translation-table 'utf-translation-table-for-decode
utf-fragmentation-table)
;; Even if unify-8859-on-encoding-mode is off, make
;; mule-utf-* encode characters in
;; utf-fragmentation-table.
(unless (eq (get 'utf-translation-table-for-encode
'translation-table)
ucs-mule-to-mule-unicode)
(define-translation-table 'utf-translation-table-for-encode
utf-defragmentation-table)))
(define-translation-table 'utf-translation-table-for-decode)
;; When unify-8859-on-encoding-mode is off, be sure to make
;; mule-utf-* disabled for characters in
;; utf-fragmentation-table.
(unless (eq (get 'utf-translation-table-for-encode
'translation-table)
ucs-mule-to-mule-unicode)
(define-translation-table 'utf-translation-table-for-encode)))
(set-default s v))
:version "22.1"
:type 'boolean
:group 'mule)
(defconst utf-translate-cjk-charsets '(chinese-gb2312
chinese-big5-1 chinese-big5-2
japanese-jisx0208 japanese-jisx0212
katakana-jisx0201
korean-ksc5601)
"List of charsets supported by `utf-translate-cjk-mode'.")
(defvar utf-translate-cjk-lang-env nil
"Language environment in which tables for `utf-translate-cjk-mode' is loaded.
The value nil means that the tables are not yet loaded.")
(defvar utf-translate-cjk-unicode-range)
;; String generated from utf-translate-cjk-unicode-range. It is
;; suitable for an argument to skip-chars-forward.
(defvar utf-translate-cjk-unicode-range-string nil)
(defun utf-translate-cjk-set-unicode-range (range)
(setq utf-translate-cjk-unicode-range range)
(setq utf-translate-cjk-unicode-range-string
(let ((decode-char-no-trans
#'(lambda (x)
(cond ((< x #x100) (make-char 'latin-iso8859-1 x))
((< x #x2500)
(setq x (- x #x100))
(make-char 'mule-unicode-0100-24ff
(+ (/ x 96) 32) (+ (% x 96) 32)))
((< x #x3400)
(setq x (- x #x2500))
(make-char 'mule-unicode-2500-33ff
(+ (/ x 96) 32) (+ (% x 96) 32)))
(t
(setq x (- x #xe000))
(make-char 'mule-unicode-e000-ffff
(+ (/ x 96) 32) (+ (% x 96) 32))))))
ranges from to)
(dolist (elt range)
(setq from (max #xA0 (car elt)) to (min #xffff (cdr elt)))
(if (and (>= to #x3400) (< to #xE000))
(setq to #x33FF))
(cond ((< from #x100)
(if (>= to #xE000)
(setq ranges (cons (cons #xE000 to) ranges)
to #x33FF))
(if (>= to #x2500)
(setq ranges (cons (cons #x2500 to) ranges)
to #x24FF))
(if (>= to #x100)
(setq ranges (cons (cons #x100 to) ranges)
to #xFF)))
((< from #x2500)
(if (>= to #xE000)
(setq ranges (cons (cons #xE000 to) ranges)
to #x33FF))
(if (>= to #x2500)
(setq ranges (cons (cons #x2500 to) ranges)
to #x24FF)))
((< from #x3400)
(if (>= to #xE000)
(setq ranges (cons (cons #xE000 to) ranges)
to #x33FF))))
(if (<= from to)
(setq ranges (cons (cons from to) ranges))))
(mapconcat #'(lambda (x)
(format "%c-%c"
(funcall decode-char-no-trans (car x))
(funcall decode-char-no-trans (cdr x))))
ranges "")))
;; These forces loading and settting tables for
;; utf-translate-cjk-mode.
(setq utf-translate-cjk-lang-env nil
ucs-mule-cjk-to-unicode (make-hash-table :test 'eq)
ucs-unicode-to-mule-cjk (make-hash-table :test 'eq)))
(defcustom utf-translate-cjk-unicode-range '((#x2e80 . #xd7a3)
(#xff00 . #xffef))
"List of Unicode code ranges supported by `utf-translate-cjk-mode'.
Setting this variable directly does not take effect;
use either \\[customize] or the function
`utf-translate-cjk-set-unicode-range'."
:version "22.1"
:type '(repeat (cons integer integer))
:set (lambda (symbol value)
(utf-translate-cjk-set-unicode-range value))
:group 'mule)
;; Return non-nil if CODE-POINT is in `utf-translate-cjk-unicode-range'.
(defsubst utf-translate-cjk-substitutable-p (code-point)
(let ((tail utf-translate-cjk-unicode-range)
elt)
(while tail
(setq elt (car tail) tail (cdr tail))
(if (and (>= code-point (car elt)) (<= code-point (cdr elt)))
(setq tail nil)
(setq elt nil)))
elt))
(defun utf-translate-cjk-load-tables ()
"Load tables for `utf-translate-cjk-mode'."
;; Fixme: Allow the use of the CJK charsets to be
;; customized by reordering and possible omission.
(let ((redefined (< (hash-table-size ucs-mule-cjk-to-unicode) 43000)))
(if redefined
;; Redefine them with realistic initial sizes and a
;; smallish rehash size to avoid wasting significant
;; space after they're built.
(setq ucs-mule-cjk-to-unicode
(make-hash-table :test 'eq :size 43000 :rehash-size 1000)
ucs-unicode-to-mule-cjk
(make-hash-table :test 'eq :size 21500 :rehash-size 1000)))
;; Load the files explicitly, to avoid having to keep
;; around the large tables they contain (as well as the
;; ones which get built).
;; Here we bind coding-system-for-read to nil so that coding tags
;; in the files are respected even if the files are not yet
;; byte-compiled
(let ((coding-system-for-read nil)
;; We must avoid clobbering this variable, in case the load
;; files below use different coding systems.
(last-coding-system-used last-coding-system-used))
(cond ((string= "Korean" current-language-environment)
(load "subst-jis")
(load "subst-big5")
(load "subst-gb2312")
(load "subst-ksc"))
((string= "Chinese-BIG5" current-language-environment)
(load "subst-jis")
(load "subst-ksc")
(load "subst-gb2312")
(load "subst-big5"))
((string= "Chinese-GB" current-language-environment)
(load "subst-jis")
(load "subst-ksc")
(load "subst-big5")
(load "subst-gb2312"))
(t
(load "subst-ksc")
(load "subst-gb2312")
(load "subst-big5")
(load "subst-jis")))) ; jis covers as much as big5, gb2312
(when redefined
(define-translation-hash-table 'utf-subst-table-for-decode
ucs-unicode-to-mule-cjk)
(define-translation-hash-table 'utf-subst-table-for-encode
ucs-mule-cjk-to-unicode)
(set-char-table-extra-slot (get 'utf-translation-table-for-encode
'translation-table)
1 ucs-mule-cjk-to-unicode))
(setq utf-translate-cjk-lang-env current-language-environment)))
(defun utf-lookup-subst-table-for-decode (code-point)
(if (and utf-translate-cjk-mode
(not utf-translate-cjk-lang-env)
(utf-translate-cjk-substitutable-p code-point))
(utf-translate-cjk-load-tables))
(gethash code-point
(get 'utf-subst-table-for-decode 'translation-hash-table)))
(defun utf-lookup-subst-table-for-encode (char)
(if (and utf-translate-cjk-mode
(not utf-translate-cjk-lang-env)
(memq (char-charset char) utf-translate-cjk-charsets))
(utf-translate-cjk-load-tables))
(gethash char
(get 'utf-subst-table-for-encode 'translation-hash-table)))
(define-minor-mode utf-translate-cjk-mode
"Toggle whether UTF based coding systems de/encode CJK characters.
If ARG is an integer, enable if ARG is positive and disable if
zero or negative. This is a minor mode.
Enabling this allows the coding systems mule-utf-8,
mule-utf-16le and mule-utf-16be to encode characters in the charsets
`korean-ksc5601', `chinese-gb2312', `chinese-big5-1',
`chinese-big5-2', `japanese-jisx0208' and `japanese-jisx0212', and to
decode the corresponding unicodes into such characters.
Where the charsets overlap, the one preferred for decoding is chosen
according to the language environment in effect when this option is
turned on: ksc5601 for Korean, gb2312 for Chinese-GB, big5 for
Chinese-Big5 and jisx for other environments.
This mode is on by default. If you are not interested in CJK
characters and want to avoid some overhead on encoding/decoding
by the above coding systems, you can customize the user option
`utf-translate-cjk-mode' to nil."
:init-value t
:version "22.1"
:type 'boolean
:group 'mule
:global t
(if utf-translate-cjk-mode
(progn
(define-translation-hash-table 'utf-subst-table-for-decode
ucs-unicode-to-mule-cjk)
(define-translation-hash-table 'utf-subst-table-for-encode
ucs-mule-cjk-to-unicode)
(set-char-table-extra-slot (get 'utf-translation-table-for-encode
'translation-table)
1 ucs-mule-cjk-to-unicode))
(define-translation-hash-table 'utf-subst-table-for-decode
(make-hash-table :test 'eq))
(define-translation-hash-table 'utf-subst-table-for-encode
(make-hash-table :test 'eq))
(set-char-table-extra-slot (get 'utf-translation-table-for-encode
'translation-table)
1 nil))
;; Update safe-chars of mule-utf-* coding systems.
(dolist (elt (coding-system-list t))
(if (string-match "^mule-utf" (symbol-name elt))
(let ((safe-charsets (coding-system-get elt 'safe-charsets))
(safe-chars (coding-system-get elt 'safe-chars))
(need-update nil))
(dolist (charset utf-translate-cjk-charsets)
(unless (eq utf-translate-cjk-mode (memq charset safe-charsets))
(setq safe-charsets
(if utf-translate-cjk-mode
(cons charset safe-charsets)
(delq charset safe-charsets))
need-update t)
(aset safe-chars (make-char charset) utf-translate-cjk-mode)))
(when need-update
(coding-system-put elt 'safe-charsets safe-charsets)
(define-coding-system-internal elt))))))
(define-ccl-program ccl-mule-utf-untrans
;; R0 is an untranslatable Unicode code-point (U+3500..U+DFFF or
;; U+10000..U+10FFFF) or an invaid byte (#x00..#xFF). Write
;; eight-bit-control/graphic sequence (2 to 4 chars) representing
;; UTF-8 sequence of r0. Registers r4, r5, r6 are modified.
;;
;; This is a subrountine because we assume that this is called very
;; rarely (so we don't have to worry about the overhead of the
;; call).
`(0
((r5 = ,(charset-id 'eight-bit-control))
(r6 = ,(charset-id 'eight-bit-graphic))
(if (r0 < #x100)
((r4 = ((r0 >> 6) | #xC0))
(write-multibyte-character r6 r4))
((if (r0 < #x10000)
((r4 = ((r0 >> 12) | #xE0))
(write-multibyte-character r6 r4))
((r4 = ((r0 >> 18) | #xF0))
(write-multibyte-character r6 r4)
(r4 = (((r0 >> 12) & #x3F) | #x80))
(if (r4 < #xA0)
(write-multibyte-character r5 r4)
(write-multibyte-character r6 r4))))
(r4 = (((r0 >> 6) & #x3F) | #x80))
(if (r4 < #xA0)
(write-multibyte-character r5 r4)
(write-multibyte-character r6 r4))))
(r4 = ((r0 & #x3F) | #x80))
(if (r4 < #xA0)
(write-multibyte-character r5 r4)
(write-multibyte-character r6 r4)))))
(define-ccl-program ccl-decode-mule-utf-8
;;
;; charset | bytes in utf-8 | bytes in emacs
;; -----------------------+----------------+---------------
;; ascii | 1 | 1
;; -----------------------+----------------+---------------
;; eight-bit-control | 2 | 2
;; eight-bit-graphic | 2 | 1
;; latin-iso8859-1 | 2 | 2
;; -----------------------+----------------+---------------
;; mule-unicode-0100-24ff | 2 | 4
;; (< 0800) | |
;; -----------------------+----------------+---------------
;; mule-unicode-0100-24ff | 3 | 4
;; (>= 8000) | |
;; mule-unicode-2500-33ff | 3 | 4
;; mule-unicode-e000-ffff | 3 | 4
;; -----------------------+----------------+---------------
;; invalid byte | 1 | 2
;;
;; Thus magnification factor is two.
;;
`(2
((r6 = ,(charset-id 'latin-iso8859-1))
(read r0)
(loop
(if (r0 < #x80)
;; 1-byte encoding, i.e., ascii
(write-read-repeat r0))
(if (r0 < #xc2)
;; continuation byte (invalid here) or 1st byte of overlong
;; 2-byte sequence.
((call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
(read r0)
(repeat)))
;; Read the 2nd byte.
(read r1)
(if ((r1 & #b11000000) != #b10000000) ; Invalid 2nd byte
((call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
;; Handle it in the next loop.
(r0 = r1)
(repeat)))
(if (r0 < #xe0)
;; 2-byte encoding 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
((r1 &= #x3F)
(r1 |= ((r0 & #x1F) << 6))
;; Now r1 holds scalar value. We don't have to check
;; `overlong sequence' because r0 >= 0xC2.
(if (r1 >= 256)
;; mule-unicode-0100-24ff (< 0800)
((r0 = r1)
(lookup-integer utf-subst-table-for-decode r0 r1)
(if (r7 == 0)
((r0 = ,(charset-id 'mule-unicode-0100-24ff))
(r1 -= #x0100)
(r2 = (((r1 / 96) + 32) << 7))
(r1 %= 96)
(r1 += (r2 + 32))
(translate-character
utf-translation-table-for-decode r0 r1)))
(write-multibyte-character r0 r1)
(read r0)
(repeat))
(if (r1 >= 160)
;; latin-iso8859-1
((r0 = r1)
(lookup-integer utf-subst-table-for-decode r0 r1)
(if (r7 == 0)
((r1 -= 128)
(write-multibyte-character r6 r1))
((write-multibyte-character r0 r1)))
(read r0)
(repeat))
;; eight-bit-control
((r0 = ,(charset-id 'eight-bit-control))
(write-multibyte-character r0 r1)
(read r0)
(repeat))))))
;; Read the 3rd bytes.
(read r2)
(if ((r2 & #b11000000) != #b10000000) ; Invalid 3rd byte
((call ccl-mule-utf-untrans)
(r0 = r1)
(call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
;; Handle it in the next loop.
(r0 = r2)
(repeat)))
(if (r0 < #xF0)
;; 3byte encoding
;; zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
((r3 = ((r0 & #xF) << 12))
(r3 |= ((r1 & #x3F) << 6))
(r3 |= (r2 & #x3F))
(if (r3 < #x800) ; `overlong sequence'
((call ccl-mule-utf-untrans)
(r0 = r1)
(call ccl-mule-utf-untrans)
(r0 = r2)
(call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
(read r0)
(repeat)))
(if (r3 < #x2500)
;; mule-unicode-0100-24ff (>= 0800)
((r0 = r3)
(lookup-integer utf-subst-table-for-decode r0 r1)
(if (r7 == 0)
((r0 = ,(charset-id 'mule-unicode-0100-24ff))
(r3 -= #x0100)
(r3 //= 96)
(r1 = (r7 + 32))
(r1 += ((r3 + 32) << 7))
(translate-character
utf-translation-table-for-decode r0 r1)))
(write-multibyte-character r0 r1)
(read r0)
(repeat)))
(if (r3 < #x3400)
;; mule-unicode-2500-33ff
((r0 = r3) ; don't zap r3
(lookup-integer utf-subst-table-for-decode r0 r1)
(if (r7 == 0)
((r0 = ,(charset-id 'mule-unicode-2500-33ff))
(r3 -= #x2500)
(r3 //= 96)
(r1 = (r7 + 32))
(r1 += ((r3 + 32) << 7))))
(write-multibyte-character r0 r1)
(read r0)
(repeat)))
(if (r3 < #xE000)
;; Try to convert to CJK chars, else
;; keep them as eight-bit-{control|graphic}.
((r0 = r3)
(lookup-integer utf-subst-table-for-decode r3 r1)
(if r7
;; got a translation
((write-multibyte-character r3 r1)
(read r0)
(repeat))
((call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
(read r0)
(repeat)))))
;; mule-unicode-e000-ffff
;; Fixme: fffe and ffff are invalid.
(r0 = r3) ; don't zap r3
(lookup-integer utf-subst-table-for-decode r0 r1)
(if (r7 == 0)
((r0 = ,(charset-id 'mule-unicode-e000-ffff))
(r3 -= #xe000)
(r3 //= 96)
(r1 = (r7 + 32))
(r1 += ((r3 + 32) << 7))))
(write-multibyte-character r0 r1)
(read r0)
(repeat)))
;; Read the 4th bytes.
(read r3)
(if ((r3 & #b11000000) != #b10000000) ; Invalid 4th byte
((call ccl-mule-utf-untrans)
(r0 = r1)
(call ccl-mule-utf-untrans)
(r0 = r2)
(call ccl-mule-utf-untrans)
(r6 = ,(charset-id 'latin-iso8859-1))
;; Handle it in the next loop.
(r0 = r3)
(repeat)))
(if (r0 < #xF8)
;; 4-byte encoding:
;; wwwzzzzzzyyyyyyxxxxxx = 11110www 10zzzzzz 10yyyyyy 10xxxxxx
;; keep those bytes as eight-bit-{control|graphic}
;; Fixme: allow lookup in utf-subst-table-for-decode.
((r4 = ((r0 & #x7) << 18))
(r4 |= ((r1 & #x3F) << 12))
(r4 |= ((r2 & #x3F) << 6))
(r4 |= (r3 & #x3F))
(if (r4 < #x10000) ; `overlong sequence'
((call ccl-mule-utf-untrans)
(r0 = r1)
(call ccl-mule-utf-untrans)
(r0 = r2)
(call ccl-mule-utf-untrans)
(r0 = r3)
(call ccl-mule-utf-untrans))
((r0 = r4)
(call ccl-mule-utf-untrans))))
;; Unsupported sequence.
((call ccl-mule-utf-untrans)
(r0 = r1)
(call ccl-mule-utf-untrans)
(r0 = r2)
(call ccl-mule-utf-untrans)
(r0 = r3)
(call ccl-mule-utf-untrans)))
(r6 = ,(charset-id 'latin-iso8859-1))
(read r0)
(repeat)))
;; At EOF...
(if (r0 >= 0)
;; r0 >= #x80
((call ccl-mule-utf-untrans)
(if (r1 >= 0)
((r0 = r1)
(call ccl-mule-utf-untrans)
(if (r2 >= 0)
((r0 = r2)
(call ccl-mule-utf-untrans)
(if (r3 >= 0)
((r0 = r3)
(call ccl-mule-utf-untrans))))))))))
"CCL program to decode UTF-8.
Basic decoding is done into the charsets ascii, latin-iso8859-1 and
mule-unicode-*, but see also `utf-fragmentation-table' and
`ucs-mule-cjk-to-unicode'.
Encodings of un-representable Unicode characters are decoded asis into
eight-bit-control and eight-bit-graphic characters.")
(define-ccl-program ccl-mule-utf-8-encode-untrans
;; UTF-8 decoder generates an UTF-8 sequence represented by a
;; sequence eight-bit-control/graphic chars for an untranslatable
;; character and an invalid byte.
;;
;; This CCL parses that sequence (the first byte is already in r1),
;; writes out the original bytes of that sequence, and sets r5 to
;; -1.
;;
;; If the eight-bit-control/graphic sequence is shorter than what r1
;; suggests, it sets r5 and r6 to the last character read that
;; should be handled by the next loop of a caller.
;;
;; Note: For UTF-8 validation, we only check if a character is
;; eight-bit-control/graphic or not. It may result in incorrect
;; handling of random binary data, but such a data can't be encoded
;; by UTF-8 anyway. At least, UTF-8 decoders doesn't generate such
;; a sequence even if a source contains invalid byte-sequence.
`(0
(;; Read the 2nd byte.
(read-multibyte-character r5 r6)
(r0 = (r5 != ,(charset-id 'eight-bit-control)))
(if ((r5 != ,(charset-id 'eight-bit-graphic)) & r0)
((write r1) ; invalid UTF-8
(r1 = -1)
(end)))
(if (r1 <= #xC3)
;; 2-byte sequence for an originally invalid byte.
((r6 &= #x3F)
(r6 |= ((r1 & #x1F) << 6))
(write r6)
(r5 = -1)
(end)))
(write r1 r6)
(r2 = r1)
(r1 = -1)
;; Read the 3rd byte.
(read-multibyte-character r5 r6)
(r0 = (r5 != ,(charset-id 'eight-bit-control)))
(if ((r5 != ,(charset-id 'eight-bit-graphic)) & r0)
(end)) ; invalid UTF-8
(write r6)
(if (r2 < #xF0)
;; 3-byte sequence for an untranslated character.
((r5 = -1)
(end)))
;; Read the 4th byte.
(read-multibyte-character r5 r6)
(r0 = (r5 != ,(charset-id 'eight-bit-control)))
(if ((r5 != ,(charset-id 'eight-bit-graphic)) & r0)
(end)) ; invalid UTF-8
;; 4-byte sequence for an untranslated character.
(write r6)
(r5 = -1)
(end))
;; At EOF...
((r5 = -1)
(if (r1 >= 0)
(write r1)))))
(define-ccl-program ccl-encode-mule-utf-8
`(1
((r5 = -1)
(loop
(if (r5 < 0)
(read-multibyte-character r0 r1)
;; Pre-read character is in r5 (charset-ID) and r6 (code-point).
((r0 = r5)
(r1 = r6)
(r5 = -1)))
(translate-character utf-translation-table-for-encode r0 r1)
(if (r0 == ,(charset-id 'ascii))
(write-repeat r1))
(if (r0 == ,(charset-id 'latin-iso8859-1))
;; r1 scalar utf-8
;; 0000 0yyy yyxx xxxx 110y yyyy 10xx xxxx
;; 20 0000 0000 1010 0000 1100 0010 1010 0000
;; 7f 0000 0000 1111 1111 1100 0011 1011 1111
((write ((r1 >> 6) | #xc2))
(r1 &= #x3f)
(r1 |= #x80)
(write-repeat r1)))
(if (r0 == ,(charset-id 'mule-unicode-0100-24ff))
((r0 = ((((r1 & #x3f80) >> 7) - 32) * 96))
;; #x3f80 == (0011 1111 1000 0000)b
(r1 &= #x7f)
(r1 += (r0 + 224)) ; 240 == -32 + #x0100
;; now r1 holds scalar value
(if (r1 < #x0800)
;; 2byte encoding
((write ((r1 >> 6) | #xC0))
(r1 &= #x3F)
(r1 |= #x80)
(write-repeat r1))
;; 3byte encoding
((write ((r1 >> 12) | #xE0))
(write (((r1 & #x0FC0) >> 6) | #x80))
(r1 &= #x3F)
(r1 |= #x80)
(write-repeat r1)))))
(if (r0 == ,(charset-id 'mule-unicode-2500-33ff))
((r0 = ((((r1 & #x3f80) >> 7) - 32) * 96))
(r1 &= #x7f)
(r1 += (r0 + 9440)) ; 9440 == -32 + #x2500
;; now r1 holds scalar value
(write ((r1 >> 12) | #xE0))
(write (((r1 & #x0FC0) >> 6) | #x80))
(r1 &= #x3F)
(r1 |= #x80)
(write-repeat r1)))
(if (r0 == ,(charset-id 'mule-unicode-e000-ffff))
((r0 = ((((r1 & #x3f80) >> 7) - 32) * 96))
(r1 &= #x7f)
(r1 += (r0 + 57312)) ; 57312 == -32 + #xe000
;; now r1 holds scalar value
(write ((r1 >> 12) | #xE0))
(write (((r1 & #x0FC0) >> 6) | #x80))
(r1 &= #x3F)
(r1 |= #x80)
(write-repeat r1)))
(if (r0 == ,(charset-id 'eight-bit-control))
;; r1 scalar utf-8
;; 0000 0yyy yyxx xxxx 110y yyyy 10xx xxxx
;; 80 0000 0000 1000 0000 1100 0010 1000 0000
;; 9f 0000 0000 1001 1111 1100 0010 1001 1111
((write #xC2)
(write-repeat r1)))
(if (r0 == ,(charset-id 'eight-bit-graphic))
;; r1 scalar utf-8
;; 0000 0yyy yyxx xxxx 110y yyyy 10xx xxxx
;; a0 0000 0000 1010 0000 1100 0010 1010 0000
;; ff 0000 0000 1111 1111 1101 1111 1011 1111
((r0 = (r1 >= #xC0))
(r0 &= (r1 <= #xC3))
(r4 = (r1 >= #xE1))
(r4 &= (r1 <= #xF7))
(r0 |= r4)
(if r0
((call ccl-mule-utf-8-encode-untrans)
(repeat))
(write-repeat r1))))
(lookup-character utf-subst-table-for-encode r0 r1)
(if r7 ; lookup succeeded
(if (r0 < #x800)
;; 2byte encoding
((write ((r0 >> 6) | #xC0))
(r0 = ((r0 & #x3F) | #x80))
(write-repeat r0))
;; 3byte encoding
((write ((r0 >> 12) | #xE0))
(write (((r0 & #x0FC0) >> 6) | #x80))
(r0 = ((r0 & #x3F) | #x80))
(write-repeat r0))))
;; Unsupported character.
;; Output U+FFFD, which is `ef bf bd' in UTF-8.
(write #xef)
(write #xbf)
(write-repeat #xbd))))
"CCL program to encode into UTF-8.")
(define-ccl-program ccl-untranslated-to-ucs
`(0
(if (r1 == 0)
nil
(if (r0 <= #xC3) ; 2-byte encoding
((r0 = ((r0 & #x3) << 6))
(r0 |= (r1 & #x3F))
(r1 = 2))
(if (r2 == 0)
(r1 = 0)
(if (r0 < #xF0) ; 3-byte encoding, as above
((r0 = ((r0 & #xF) << 12))
(r0 |= ((r1 & #x3F) << 6))
(r0 |= (r2 & #x3F))
(r1 = 3))
(if (r3 == 0)
(r1 = 0)
((r0 = ((r0 & #x7) << 18))
(r0 |= ((r1 & #x3F) << 12))
(r0 |= ((r2 & #x3F) << 6))
(r0 |= (r3 & #x3F))
(r1 = 4))))))))
"Decode 2-, 3-, or 4-byte sequences in r0, r1, r2 [,r3] to unicodes in r0.
Set r1 to the byte length. r0 == 0 for invalid sequence.")
(defvar utf-8-ccl-regs (make-vector 8 0))
(defsubst utf-8-untranslated-to-ucs ()
"Return the UCS code for an untranslated sequence of raw bytes t point.
Only for 3- or 4-byte sequences."
(aset utf-8-ccl-regs 0 (or (char-after) 0))
(aset utf-8-ccl-regs 1 (or (char-after (1+ (point))) 0))
(aset utf-8-ccl-regs 2 (or (char-after (+ 2 (point))) 0))
(aset utf-8-ccl-regs 3 (or (char-after (+ 3 (point))) 0))
(ccl-execute 'ccl-untranslated-to-ucs utf-8-ccl-regs))
(defun utf-8-help-echo (window object position)
(format "Untranslated Unicode U+%04X"
(get-char-property position 'untranslated-utf-8 object)))
;; We compose the untranslatable sequences into a single character,
;; and move point to the next character.
;; This is infelicitous for editing, because there's currently no
;; mechanism for treating compositions as atomic, but is OK for
;; display. They are composed to U+FFFD with help-echo which
;; indicates the unicodes they represent. This function GCs too much.
;; If utf-translate-cjk-mode is non-nil, this function is called with
;; HASH-TABLE which translates CJK characters into some of CJK
;; charsets.
(defsubst utf-8-compose (hash-table)
"Put a suitable composition on an untranslatable sequence at point.
If HASH-TABLE is non-nil, try to translate CJK characters by it at first.
Move point to the end of the sequence."
(utf-8-untranslated-to-ucs)
(let ((l (aref utf-8-ccl-regs 1))
ch)
(if (> l 0)
(if (and hash-table
(setq ch (gethash (aref utf-8-ccl-regs 0) hash-table)))
(progn
(insert ch)
(delete-region (point) (min (point-max) (+ l (point)))))
(setq ch (aref utf-8-ccl-regs 0))
(put-text-property (point) (min (point-max) (+ l (point)))
'untranslated-utf-8 ch)
(put-text-property (point) (min (point-max) (+ l (point)))
'help-echo 'utf-8-help-echo)
(if (= l 2)
(put-text-property (point) (min (point-max) (+ l (point)))
'display (propertize (format "\\%03o" ch)
'face 'escape-glyph))
(compose-region (point) (+ l (point)) ?$,3u=(B))
(forward-char l))
(forward-char 1))))
(defcustom utf-8-compose-scripts nil
"*Non-nil means compose various scripts on decoding utf-8 text."
:group 'mule
:version "22.1"
:type 'boolean)
(defun utf-8-post-read-conversion (length)
"Compose untranslated utf-8 sequences into single characters.
If `utf-translate-cjk-mode' is non-nil, tries to translate CJK characters.
Also compose particular scripts if `utf-8-compose-scripts' is non-nil."
(save-excursion
(save-restriction
(narrow-to-region (point) (+ (point) length))
;; Can't do eval-when-compile to insert a multibyte constant
;; version of the string in the loop, since it's always loaded as
;; unibyte from a byte-compiled file.
(let ((range (string-as-multibyte "^\xc0-\xc3\xe1-\xf7"))
(buffer-multibyte enable-multibyte-characters)
hash-table ch)
(set-buffer-multibyte t)
(when utf-translate-cjk-mode
(unless utf-translate-cjk-lang-env
;; Check these characters in utf-translate-cjk-range.
;; We may have to translate them to CJK charsets.
(skip-chars-forward
(concat range utf-translate-cjk-unicode-range-string))
(unless (eobp)
(utf-translate-cjk-load-tables)
(setq range
(concat range utf-translate-cjk-unicode-range-string)))
(setq hash-table (get 'utf-subst-table-for-decode
'translation-hash-table))))
(while (and (skip-chars-forward range)
(not (eobp)))
(setq ch (following-char))
(if (< ch 256)
(utf-8-compose hash-table)
(if (and hash-table
(setq ch (gethash (encode-char ch 'ucs) hash-table)))
(progn
(insert ch)
(delete-char 1))
(forward-char 1))))
(or buffer-multibyte
(set-buffer-multibyte nil)))
(when (and utf-8-compose-scripts (> length 1))
;; These currently have definitions which cover the relevant
;; unicodes. We could avoid loading thai-util &c by checking
;; whether the region contains any characters with the appropriate
;; categories. There aren't yet Unicode-based rules for Tibetan.
(diacritic-compose-region (point-max) (point-min))
(thai-compose-region (point-max) (point-min))
(lao-compose-region (point-max) (point-min))
(devanagari-compose-region (point-max) (point-min))
(malayalam-compose-region (point-max) (point-min))
(tamil-compose-region (point-max) (point-min)))
(- (point-max) (point-min)))))
(defun utf-8-pre-write-conversion (beg end)
"Prepare for `utf-translate-cjk-mode' to encode text between BEG and END.
This is used as a post-read-conversion of utf-8 coding system."
(if (and utf-translate-cjk-mode
(not utf-translate-cjk-lang-env)
(save-excursion
(goto-char beg)
(re-search-forward "\\cc\\|\\cj\\|\\ch" end t)))
(utf-translate-cjk-load-tables))
nil)
(make-coding-system
'mule-utf-8 4 ?u
"UTF-8 encoding for Emacs-supported Unicode characters.
It supports Unicode characters of these ranges:
U+0000..U+33FF, U+E000..U+FFFF.
They correspond to these Emacs character sets:
ascii, latin-iso8859-1, mule-unicode-0100-24ff,
mule-unicode-2500-33ff, mule-unicode-e000-ffff
On decoding (e.g. reading a file), Unicode characters not in the above
ranges are decoded into sequences of eight-bit-control and
eight-bit-graphic characters to preserve their byte sequences. The
byte sequence is preserved on i/o for valid utf-8, but not necessarily
for invalid utf-8.
On encoding (e.g. writing a file), Emacs characters not belonging to
any of the character sets listed above are encoded into the UTF-8 byte
sequence representing U+FFFD (REPLACEMENT CHARACTER)."
'(ccl-decode-mule-utf-8 . ccl-encode-mule-utf-8)
`((safe-charsets
ascii
eight-bit-control
eight-bit-graphic
latin-iso8859-1
mule-unicode-0100-24ff
mule-unicode-2500-33ff
mule-unicode-e000-ffff
,@(if utf-translate-cjk-mode
utf-translate-cjk-charsets))
(mime-charset . utf-8)
(coding-category . coding-category-utf-8)
(valid-codes (0 . 255))
(pre-write-conversion . utf-8-pre-write-conversion)
(post-read-conversion . utf-8-post-read-conversion)
(translation-table-for-encode . utf-translation-table-for-encode)
(dependency unify-8859-on-encoding-mode
unify-8859-on-decoding-mode
utf-fragment-on-decoding
utf-translate-cjk-mode)))
(define-coding-system-alias 'utf-8 'mule-utf-8)
;; I think this needs special private charsets defined for the
;; untranslated sequences, if it's going to work well.
;;; (defun utf-8-compose-function (pos to pattern &optional string)
;;; (let* ((prop (get-char-property pos 'composition string))
;;; (l (and prop (- (cadr prop) (car prop)))))
;;; (cond ((and l (> l (- to pos)))
;;; (delete-region pos to))
;;; ((and (> (char-after pos) 224)
;;; (< (char-after pos) 256)
;;; (save-restriction
;;; (narrow-to-region pos to)
;;; (utf-8-compose)))
;;; t))))
;;; (dotimes (i 96)
;;; (aset composition-function-table
;;; (+ 128 i)
;;; `((,(string-as-multibyte "[\200-\237\240-\377]")
;;; . utf-8-compose-function))))
;;; arch-tag: b08735b7-753b-4ae6-b754-0f3efe4515c5
;;; utf-8.el ends here