mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-29 07:58:28 +00:00
ea986a64b2
7acae22f42
Fix auto-filling in Texinfo mode4bda962734
; * admin/git-bisect-start: Update failing commitsdcf8c01102
Merge branch 'scratch/long-lines-cleanup' into 'emacs-29'1e3a66df45
Add an assertion in, and a commentary for, 'get_nearby_bo...f0f08eeb05
Fix the return type of 'labeled_restrictions_get_bound'acf4763417
Fix mouse highlight with some fonts in Cairo builds32b42b333c
; * etc/NEWS: Fix wording in last change.09d6070e56
; Improve and update documentation of built-in package up...ba2c76fa2b
Ensure that package menu respects 'package-install-upgrad...6fa9332e7c
Ensure that EXTRA-DATA are always written when generating...60d5a015d1
Update to Transient v0.4.0b8bcd42cab
Revert "Don't have nntp-report signal an error"ef1f4068f6
; * lisp/wid-edit.el (widget-specify-insert): Fix debug s...09bf476836
Make c-emacs-features use the proper binding of parse-sex...c9e2a5ec26
; * lisp/obsolete/autoload.el (make-directory-autoloads):...346f4ac3bf
; Fix example in ELisp manual91fff05ae3
; Fix wording in Emacs manual2438fa2e6c
; Fix minor documentation issue ion replace.el93005cd9dc
with-display-message: Workaround for bug#632536924c81a6d
; Don't use literal non-ASCII characters in Texinfof1675df3d0
Fido-mode: never shadow 'external' completion style56d2949d44
; * lisp/leim/quail/persian.el: Fix a typo in last commit.d94ea9efca
Avoid crashes in --without-all build trying to scale non-...387ddc0ccc
Improve instructions for dealing with Emacs crashese6b4784a37
Improved transliterations + improved bidi insertion suppo...c1363a04bb
Fix crash when creating a child frame in NS (bug#63107)7d6855c9ab
Fix outgoing mime type regression (Bug#62815)e920dd2b6f
define-minor-mode: sanitize mode function messages910a7b30df
Fix beginning/end-of-defun with tree-sittere205f68717
Fix indent for enums in csharp-modedfde902f3b
; Expand 'package-vc-install' documentation7133784303
Teach c-ts-mode about the 'restrict' keyword15e06260ae
* lisp/x-dnd.el (x-dnd-after-move-frame): Skip dead frame...a081b6625b
; Updated Elispref-Manual: `nil' cannot be defun'ed97b818a4fb
Fix doc strings of 'mark-sexp' and 'mark-word'6f910ad932
; * etc/EGLOT-NEWS: Fix misspellings.9b775ddc05
; * etc/EGLOT-NEWS: Fix wording of last change.79a886ba36
(package-upgrade): Don't remove the package from 'package...c0ab4e9ca9
Eglot: re-rename eglot-upgrade to eglot-upgrade-eglotb4e90070f9
Fix arguments of xml.c functions as displayed in Help buf...b1bda8228e
More fixes for NetBSD/vaxa2d4cd06f4
Improve VHDL mode highlighting2f3a514b6d
Clarify documentation wrt floating point division by zero...94e984e670
Make loaddefs-generate slightly more tolerantaba41d2c4b
; Minor doc cleanups in go-ts-mode.elb42ccb2e5c
; Minor grammar fix in treesit manual.ab44c8a6f9
Fix order of rcirc-connect arguments8eb6e33691
Fix rcirc messages printing in the wrong place2901a3443c
Prevent unnecessary modifications of 'package-vc-selected...eaad302bd6
Rename eglot-update to eglot-upgradeeaf25b9c6a
go-ts-mode: Use iota query only if supported (Bug#63086)cc090294d7
(rng-complete-tag): Add the (ignored) argument to the :co...21ec6c1d5c
Update to Transient v0.3.7-219-g3ded15b8d5aa8df4a
Fix inserting selection data into Mozilla programs57562c3fd0
Recognize defstruct slot names in various eieio functionsb93eb68cc3
Use 'calendar-buffer' instead of fixed stringe338a8ac41
Handle point not at EOB in minibuffer-choose-completionfceaf230b0
Note that Emacs pauses when handling sentinel errors46392c1623
Fix vertical-motion when tab-line is displayed in a window0e52beeace
Update to Org 9.6.5-3-g2993f4dd21003878
Prevent generating empty autoload files2bcf11d0ef
* lisp/org/org-macs.el (org--inhibit-version-check): Fix ...ca43435816
Fix redisplay of mode line after its format changes from nil610a7657e0
Fix c-ts-mode--emacs-c-range-query7f94558b77
Improve documentation of warnings5a3f0e2c55
; Doc fix in c-ts-mode.el21361d0563
Fix FOR_EACH_TAIL fontification (bug#62951)d0df3404fd
; * etc/EGLOT-NEWS: chsharp-le -> csharp-lsc229e83c3c
; * etc/EGLOT-NEWS (https): Elglot -> Eglot.b4f2f49978
Fix documentation of libxml-parse-* functions5dd784961d
; * src/treesit.c (syms_of_treesit): Fix error messages.ddfa0d8da9
; Remove some leftover text212e30f678
; Fix byte-compilation warnings in c-ts-mode.el1f2214dabd
Skip over whitespace in annotation-top-cont check (bug#63...7e136c51f6
Update zh-CN tutorial translationd3ca0b3aa2
; * lisp/progmodes/c-ts-mode.el: Fix comments and doc str...c6f15c2486
; Fix last change.b9e06330f7
; * etc/NEWS: Followup to bug#62720.b33d25f596
; Minor improvements in doc strings of package-upgrade co...c3a61870b9
Fix eglot.texia40f181623
Fix two crashes upon startup44ebd9cbd5
Eglot: explain how to update Eglot in manual (bug#62720)941ef044f2
Eglot: fix edge case when deleting inlay hint overlaysa365984d9e
package-upgrade[-all]: Expand docstrings to note the curr...f965f35b33
Rename all functions called package-*-update-* to package...31b58161bb
Fix FOR_EACH_TAIL in c-ts-mode (bug#62951)0cf6e0998b
* Makefile.in (distclean): Remove the 'native-lisp' direc...933705d61e
Improve greek-ibycus4 input methodc46e93b1f5
Explain ERC 5.5 regressions in new version 5.5.0.29.1af43f0a295
* doc/misc/erc.texi: Elaborate on upgrading via ELPA.10948948c1
Improve outline-default-state docstringb5ace2eed8
Document problems with /bin/sh on Solaris 107b2ad8f199
; Add missing <<inserted by help-with-tutorial>> line to ...524e161a53
Followup to addition of TUTORIAL.fa76f50df153
Add Farsi/Persian translation of the tutorial8eacfaea6d
Add Mongolian language environmentsfe8efbb8f7
Document the 'end-session' event on MS-Windowsd80f959bed
Update to Org 9.6.4-9-g8eb20998c6cfcbe4
Don't support versioned grammar libraries on MS-Windows8f71c1546d
Accept versioned tree-sitter language grammar files99add09d5e
tab-bar-new-tab: inhibit side-window checks087e818194
* etc/NEWS: Fix outline level. (Bug#63042)d7f38558c4
; Improve font selection for Traditional Mongolian965c5e0231
Fix rendering of Traditional Mongolian script9a0f10b5f8
Fix line-number-at-pos when POSITION is out of narrowing4e0f4292aa
; * etc/tutorials/TUTORIAL: Fix punctuation.dec2ac0c65
Fix exiting Emacs after saving a tutorial44145bf07e
Add indentation style setting for c-ts-mode in .dir-local...e7db6c59cc
; * .dir-locals.el (c-ts-mode): Add settings.d041f01b02
; Minor fix in Emacs Lisp Intro manual3899acbb33
; * src/fringe.c: Fix description of large circle. (Bug#...2b10e1827d
sql: add missing postgresql types9ac1259278
Fix display of menu-bar bindings of commands in *Help* bu...ecdd3a9efa
Improve Completion Example section in the Emacs manual626e1ac62b
Improve 'message-server-alist' docstring327986936c
Add index entry for fallback modes1c4783c330
; * etc/NEWS: Copyedits and grammar fixes.3d6f755331
xref-search-program-alist: Fix searching larger file list...1b8b2cf61b
Fix typo and inaccuracy in the ELisp Reference manualdf17682ebf
; Support 'dart-ts-mode' in Eglote0dc60e078
; Fix typos in gdb-mi.el60560cc7ad
Fix description of lexical environment's internals1456adf424
; Eglot: fix a typo in a customization type2f59595f5f
; * etc/NEWS: Grammar fixes.596b780ab7
Update to Org 9.6.4-2-g0f6ae7a0b04a2247
Documentation copyedits for 'package-install-upgrade-buil...580d8278c5
Allow upgrading built-in packages with 'package-install'329304c23f
; * src/term.c (init_tty): Fix last change. (Bug#62877)200dbf7d30
Minor changes in c-ts-mode.el's support of DEFUNs9686b015a0
Fix strike-through attribute support on TTY frames39035fbfc5
Avoid crashes in 'describe-keymap' due to shadowingb7023da662
Make image-map bindings available on image linksd9e96c029b
* CONTRIBUTE: Fix a typo3f71a2a0cf
; * lisp/progmodes/c-ts-mode.el (treesit-node-next-siblin...adf9c956c2
Add to Eglot support for additional language-servers.b3603b84bd
Partial support for DEFUN in c-ts-mode (bug#62825)14e809ddff
Fix style and unwinding code in treesit.c759cdf1e51
Catch signals produced by PRED in tree-sitter search func...864a4dc236
Fix compilation of w32.c with old MinGW system headersa22eb9ae0f
ruby-add-log-current-method: Reduce the use of 'nreverse'17d803d0a7
Fix detection of WebP images by their signature43290391ce
; Eglot: make version parseable by version-to-list6e6e8b5c97
Add more documentation for the keys of `package-vc-select...7972b76c2c
; vc-checkout: Wrap var lookup in 'bound-and-true-p'e9fef1d70f
vc-checkout: Try to use the vc-dir's backend first372e024acc
; Fix wallpaper-tests on XFCE7055fd8e43
Improve documentation related to 'ispell-complete-word'61fd017abd
* configure.ac: Add -lbsd on Haiku.05971c4d9a
Add menu to 'c-ts-mode' and 'c++-ts-mode'954e2d96a9
Update manual about `sort`c62afb10cf
Fix wallpaper-tests on MS-Windowsf2d212c696
Fix a couple of eglot-tests338b3718b6
Fix visiting RPM filesb4afee0319
Fix ff-quiet-mode doc2445100d7d
; Improve documentation of 'match-buffers'd4d0da96f0
; Update make-tarball.txt for Emacs 29.9b0bf694da
; Fix ldefs-boot.el.0cb86a348c
; Update ChangeLog.4.5e039d5a6e
* lisp/ldefs-boot.el: Regenerate.671abd0cc4
Merge branch 'emacs-29' of git.sv.gnu.org:/srv/git/emacs ...4bc678ec9f
Bump Emacs version to 29.0.90db8f207e52
Fix some cases of incomplete code's indentation [c/c++-ts...589959fb09
project-search: Pipe the list of files through 'file-regu...2b91567bf6
Update ChangeLog and AUTHORS for Emacs 29d6af1f1498
; doc/lispref/windows.texi: Fix @pxref paren.57490fff6e
; Backport: Eglot: fix misplaced parenthesis in last comm...2a62273f3b
Backport: Eglot: no more tests based on Pylsp (bug#62694)5ef7ff0573
; Start a new ChangeLog.4 file.11126c6d30
Fix 'C-h k' for "Paste from Kill Menu" in context menus74ddfe811f
; * doc/misc/calc.texi (Rewrites Tutorial): Fix a typo (b...08cda286c3
Improve the documentation of the XDS support14d1c00e80
Allow reindentation of images inserted by 'mm-inline-image'b63a9eda01
Fix "C-h k" and "C-h c" with Paste from Kill Menub36c21e27d
Change cursor color on NS port when it matches the face b...96714c106b
Improve documentation of image-related commands6a2863ca01
Fix handling of sliced images5be79fd05a
; * etc/NEWS: Announce 'cyrillic-mongolian' IM.ca1a0fda98
; Fix last change.ce63462dbd
Add cyrillic-mongolian input method5880179270
; Minor addition to the Emacs FAQ88847dee12
Jsonrpc: don't bind inhibit-read-only to t so earlycb8c87a423
Allow active region when IM is used305246d972
Add emoji-zoom-reset470d269ec1
Make emoji-zoom-{increase,decrease} set text properties c...63d4a86f8d
Fix transforming sliced images5e1953a8f8
; * etc/NEWS: Minor copyedits of entry for 'keymap-*' fun...6b9f9df945
; Improve documentation of 'declare-function'81d1f46d0f
; Avoid compiler warning in eglot.el.38cdfcb212
; Fix description of new 'keymap-*' functions257090b872
Adapt EMBA scripts.90c07d3fdd
Another terminology fix in ELisp reference manuala832bc7090
Correct terminology in Elisp Reference Manualdb308233cb
Comment out GNUSTEP jobs on EMBA (again)8c1b102243
; * lisp/image.el (put-image): Doc fix.eda88c63ad
; * doc/emacs/trouble.texi (Checklist): Minor grammar fix.728bc09cf3
Fix regexp string escaping mistake in vhdl-mode.el (bug#6...479626dbac
Update to Org 9.6.3-2-gf2949d5a1c9aace7
; Add a bit more docstring to tsx-ts-mode (bug#62429)86cf9fd932
Eglot: don't watch directories that don't exist82d0b6c64e
; * lisp/subr.el (use-dialog-box-p): Fix last change.3619663f98
Preserve peer information for web page in eww-readablecb8d6ab648
* lisp/subr.el (use-dialog-box-p): Fix conditions for GUI...fb2c440920
; * lisp/progmodes/c-ts-mode.el (c++-ts-mode): Add some n...c0b9530862
Another final fix to last changes0cc8d6826a
Three final fixes to last changes89e337c3fc
; Make sure 'eshell-command' tests don't prompt the user097c5ee8f5
Two further fixes to last changesb39c3cd112
; * etc/NEWS: Fix typos.dce08cf05c
Improve and fix last changes89ac5ba11c
Fix ModelSim error regexp in vhdl-mode24ed9c7ae7
; * doc/emacs/trouble.texi (Checklist): Minor copyedits (...d1d39a0f09
Document enhancements in handling of echo-area messages46209b2453
; Fix last change21a4ee209c
Fix new Eshell tests on MS-Windowse2ebf3995d
; Auto-commit of loaddefs files.6419d78fa6
Fix using background commands in 'eshell-command'3bdbb66efb
; CONTRIBUTE: Minor stylistic changes.d0eb12e8d3
Fix typo in section 14.1 of Emacs Manualb2fbec37f3
; * etc/EGLOT-NEWS: Clarify scope of topmost section131ec049db
Eglot: unbreak eglot-extend-to-xref on w320622e1f29f
Eglot: ensure server shutdown turns off eglot-inlay-hints...59f66ea302
; * lisp/emacs-lisp/package-vc.el: Remove completed item ...d23dc3dd7e
; * lisp/emacs-lisp/package-vc.el (package-vc): Fix manua...4508a024e8
; Clarify documentation of 'cursor' text propertyd2e82817a3
Add two typescript-ts-mode faces (bug#62429)10918fc9d2
Fix scrolling window when point moves up9b32bc134c
Improve documentation of 'defcustom's :set keywordab4273056e
Comp fix calls to redefined primtives with op-bytecode (b...c98929c7e1
; Fix last changea14c3f62a6
; Fix last change09fece5722
Fix duplicate defcustom in eww.el2093e010dc
Fix cursor motion in character-only terminalse45bd10a3d
Fix indentation regression in 'C-h l'46fd10a760
* doc/misc/tramp.texi (Remote shell setup): Clarify use o...974e4f3333
Make get_medium_narrowing_begv/zv staticafc2c6c13c
Improve accuracy of cursor motion commands in long lines7e26a5c774
Remove labeled restrictions before calling Fwiden85ed1c9ca6
Code cleanup for long line optimizations # Conflicts: # etc/NEWS
2294 lines
72 KiB
C
2294 lines
72 KiB
C
/* Composite sequence support.
|
||
Copyright (C) 2001-2023 Free Software Foundation, Inc.
|
||
Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
|
||
National Institute of Advanced Industrial Science and Technology (AIST)
|
||
Registration Number H14PRO021
|
||
Copyright (C) 2003, 2006
|
||
National Institute of Advanced Industrial Science and Technology (AIST)
|
||
Registration Number H13PRO009
|
||
|
||
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 3 of the License, 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. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
#include <config.h>
|
||
|
||
#include <stdlib.h> /* for qsort */
|
||
|
||
#include "lisp.h"
|
||
#include "character.h"
|
||
#include "composite.h"
|
||
#include "buffer.h"
|
||
#include "coding.h"
|
||
#include "intervals.h"
|
||
#include "frame.h"
|
||
#include "dispextern.h"
|
||
#include "termhooks.h"
|
||
|
||
|
||
/* Emacs uses special text property `composition' to support character
|
||
composition. A sequence of characters that have the same (i.e. eq)
|
||
`composition' property value is treated as a single composite
|
||
sequence (we call it just `composition' here after). Characters in
|
||
a composition are all composed somehow on the screen.
|
||
|
||
The property value has this form when the composition is made:
|
||
((LENGTH . COMPONENTS) . MODIFICATION-FUNC)
|
||
then turns to this form:
|
||
(COMPOSITION-ID . (LENGTH COMPONENTS-VEC . MODIFICATION-FUNC))
|
||
when the composition is registered in composition_hash_table and
|
||
composition_table. These rather peculiar structures were designed
|
||
to make it easy to distinguish them quickly (we can do that by
|
||
checking only the first element) and to extract LENGTH (from the
|
||
former form) and COMPOSITION-ID (from the latter form).
|
||
|
||
We register a composition when it is displayed, or when the width
|
||
is required (for instance, to calculate columns).
|
||
|
||
LENGTH -- Length of the composition. This information is used to
|
||
check the validity of the composition.
|
||
|
||
COMPONENTS -- Character, string, vector, list, or nil.
|
||
|
||
If it is nil, characters in the text are composed relatively
|
||
according to their metrics in font glyphs.
|
||
|
||
If it is a character or a string, the character or characters
|
||
in the string are composed relatively.
|
||
|
||
If it is a vector or list of integers, the element is a
|
||
character or an encoded composition rule. The characters are
|
||
composed according to the rules. (2N)th elements are
|
||
characters to be composed and (2N+1)th elements are
|
||
composition rules to tell how to compose (2N+2)th element with
|
||
the previously composed 2N glyphs.
|
||
|
||
COMPONENTS-VEC -- Vector of integers. In a relative composition,
|
||
the elements are the characters to be composed. In a rule-base
|
||
composition, the elements are characters or encoded
|
||
composition rules.
|
||
|
||
MODIFICATION-FUNC -- If non-nil, it is a function to call when the
|
||
composition gets invalid after a modification in a buffer. If
|
||
it is nil, a function in `composition-function-table' of the
|
||
first character in the sequence is called.
|
||
|
||
COMPOSITION-ID --Identification number of the composition. It is
|
||
used as an index to composition_table for the composition.
|
||
|
||
When Emacs has to display a composition or has to know its
|
||
displaying width, the function get_composition_id is called. It
|
||
returns COMPOSITION-ID so that the caller can access the
|
||
information about the composition through composition_table. If a
|
||
COMPOSITION-ID has not yet been assigned to the composition,
|
||
get_composition_id checks the validity of `composition' property,
|
||
and, if valid, assigns a new ID, registers the information in
|
||
composition_hash_table and composition_table, and changes the form
|
||
of the property value. If the property is invalid,
|
||
get_composition_id returns -1 without changing the property value.
|
||
|
||
We use two tables to keep the information about composition;
|
||
composition_hash_table and composition_table.
|
||
|
||
The former is a hash table whose keys are COMPONENTS-VECs and
|
||
values are the corresponding COMPOSITION-IDs. This hash table is
|
||
weak, but as each key (COMPONENTS-VEC) is also kept as a value of the
|
||
`composition' property, it won't be collected as garbage until all
|
||
bits of text that have the same COMPONENTS-VEC are deleted.
|
||
|
||
The latter is a table of pointers to `struct composition' indexed
|
||
by COMPOSITION-ID. This structure keeps the other information (see
|
||
composite.h).
|
||
|
||
In general, a text property holds information about individual
|
||
characters. But, a `composition' property holds information about
|
||
a sequence of characters (in this sense, it is like the `intangible'
|
||
property). That means that we should not share the property value
|
||
in adjacent compositions -- we can't distinguish them if they have the
|
||
same property. So, after any changes, we call
|
||
`update_compositions' and change a property of one of adjacent
|
||
compositions to a copy of it. This function also runs a proper
|
||
composition modification function to make a composition that gets
|
||
invalid by the change valid again.
|
||
|
||
As the value of the `composition' property holds information about a
|
||
specific range of text, the value gets invalid if we change the
|
||
text in the range. We treat the `composition' property as always
|
||
rear-nonsticky (currently by setting default-text-properties to
|
||
(rear-nonsticky (composition))) and we never make properties of
|
||
adjacent compositions identical. Thus, any such changes make the
|
||
range just shorter. So, we can check the validity of the `composition'
|
||
property by comparing LENGTH information with the actual length of
|
||
the composition.
|
||
|
||
*/
|
||
|
||
|
||
/* Table of pointers to the structure `composition' indexed by
|
||
COMPOSITION-ID. This structure is for storing information about
|
||
each composition except for COMPONENTS-VEC. */
|
||
struct composition **composition_table;
|
||
|
||
/* The current size of `composition_table'. */
|
||
static ptrdiff_t composition_table_size;
|
||
|
||
/* Number of compositions currently made. */
|
||
ptrdiff_t n_compositions;
|
||
|
||
/* Hash table for compositions. The key is COMPONENTS-VEC of
|
||
`composition' property. The value is the corresponding
|
||
COMPOSITION-ID. */
|
||
Lisp_Object composition_hash_table;
|
||
|
||
/* Maximum number of characters to look back for
|
||
auto-compositions. */
|
||
#define MAX_AUTO_COMPOSITION_LOOKBACK 3
|
||
|
||
/* Return COMPOSITION-ID of a composition at buffer position
|
||
CHARPOS/BYTEPOS and length NCHARS. The `composition' property of
|
||
the sequence is PROP. STRING, if non-nil, is a string that
|
||
contains the composition instead of the current buffer.
|
||
|
||
If the composition is invalid, return -1. */
|
||
|
||
ptrdiff_t
|
||
get_composition_id (ptrdiff_t charpos, ptrdiff_t bytepos, ptrdiff_t nchars,
|
||
Lisp_Object prop, Lisp_Object string)
|
||
{
|
||
Lisp_Object id, length, components, key, *key_contents, hash_code;
|
||
ptrdiff_t glyph_len;
|
||
struct Lisp_Hash_Table *hash_table = XHASH_TABLE (composition_hash_table);
|
||
ptrdiff_t hash_index;
|
||
enum composition_method method;
|
||
struct composition *cmp;
|
||
int ch;
|
||
|
||
/* Maximum length of a string of glyphs. XftGlyphExtents limits
|
||
this to INT_MAX, and Emacs limits it further. Divide INT_MAX - 1
|
||
by 2 because gui_produce_glyphs computes glyph_len * 2 + 1. Divide
|
||
the size by MAX_MULTIBYTE_LENGTH because encode_terminal_code
|
||
multiplies glyph_len by MAX_MULTIBYTE_LENGTH. */
|
||
enum {
|
||
GLYPH_LEN_MAX = min ((INT_MAX - 1) / 2,
|
||
min (PTRDIFF_MAX, SIZE_MAX) / MAX_MULTIBYTE_LENGTH)
|
||
};
|
||
|
||
/* PROP should be
|
||
Form-A: ((LENGTH . COMPONENTS) . MODIFICATION-FUNC)
|
||
or
|
||
Form-B: (COMPOSITION-ID . (LENGTH COMPONENTS-VEC . MODIFICATION-FUNC))
|
||
*/
|
||
if (nchars == 0 || !CONSP (prop))
|
||
goto invalid_composition;
|
||
|
||
id = XCAR (prop);
|
||
if (FIXNUMP (id))
|
||
{
|
||
/* PROP should be Form-B. */
|
||
if (XFIXNUM (id) < 0 || XFIXNUM (id) >= n_compositions)
|
||
goto invalid_composition;
|
||
return XFIXNUM (id);
|
||
}
|
||
|
||
/* PROP should be Form-A.
|
||
Thus, ID should be (LENGTH . COMPONENTS). */
|
||
if (!CONSP (id))
|
||
goto invalid_composition;
|
||
length = XCAR (id);
|
||
if (!FIXNUMP (length) || XFIXNUM (length) != nchars)
|
||
goto invalid_composition;
|
||
|
||
components = XCDR (id);
|
||
|
||
/* Check if the same composition has already been registered or not
|
||
by consulting composition_hash_table. The key for this table is
|
||
COMPONENTS (converted to a vector COMPONENTS-VEC) or, if it is
|
||
nil, vector of characters in the composition range. */
|
||
if (FIXNUMP (components))
|
||
key = make_vector (1, components);
|
||
else if (STRINGP (components) || CONSP (components))
|
||
key = Fvconcat (1, &components);
|
||
else if (VECTORP (components))
|
||
key = components;
|
||
else if (NILP (components))
|
||
{
|
||
key = make_uninit_vector (nchars);
|
||
if (STRINGP (string))
|
||
for (ptrdiff_t i = 0; i < nchars; i++)
|
||
{
|
||
ch = fetch_string_char_advance (string, &charpos, &bytepos);
|
||
ASET (key, i, make_fixnum (ch));
|
||
}
|
||
else
|
||
for (ptrdiff_t i = 0; i < nchars; i++)
|
||
{
|
||
ch = fetch_char_advance (&charpos, &bytepos);
|
||
ASET (key, i, make_fixnum (ch));
|
||
}
|
||
}
|
||
else
|
||
goto invalid_composition;
|
||
|
||
hash_index = hash_lookup (hash_table, key, &hash_code);
|
||
if (hash_index >= 0)
|
||
{
|
||
/* We have already registered the same composition. Change PROP
|
||
from Form-A above to Form-B while replacing COMPONENTS with
|
||
COMPONENTS-VEC stored in the hash table. We can directly
|
||
modify the cons cell of PROP because it is not shared. */
|
||
key = HASH_KEY (hash_table, hash_index);
|
||
id = HASH_VALUE (hash_table, hash_index);
|
||
XSETCAR (prop, id);
|
||
XSETCDR (prop, Fcons (make_fixnum (nchars), Fcons (key, XCDR (prop))));
|
||
return XFIXNUM (id);
|
||
}
|
||
|
||
/* This composition is a new one. We must register it. */
|
||
|
||
/* Check if we have sufficient memory to store this information. */
|
||
if (composition_table_size <= n_compositions)
|
||
composition_table = xpalloc (composition_table, &composition_table_size,
|
||
1, -1, sizeof *composition_table);
|
||
|
||
key_contents = XVECTOR (key)->contents;
|
||
|
||
/* Check if the contents of COMPONENTS are valid if COMPONENTS is a
|
||
vector or a list. It should be a sequence of:
|
||
char1 rule1 char2 rule2 char3 ... ruleN charN+1 */
|
||
|
||
if (VECTORP (components)
|
||
&& ASIZE (components) >= 2
|
||
&& VECTORP (AREF (components, 0)))
|
||
{
|
||
/* COMPONENTS is a glyph-string. */
|
||
ptrdiff_t len = ASIZE (key);
|
||
|
||
for (ptrdiff_t i = 1; i < len; i++)
|
||
if (! VECTORP (AREF (key, i)))
|
||
goto invalid_composition;
|
||
}
|
||
else if (VECTORP (components) || CONSP (components))
|
||
{
|
||
ptrdiff_t len = ASIZE (key);
|
||
|
||
/* The number of elements should be odd. */
|
||
if ((len % 2) == 0)
|
||
goto invalid_composition;
|
||
/* All elements should be integers (character or encoded
|
||
composition rule). */
|
||
for (ptrdiff_t i = 0; i < len; i++)
|
||
{
|
||
if (!FIXNUMP (key_contents[i]))
|
||
goto invalid_composition;
|
||
}
|
||
}
|
||
|
||
/* Change PROP from Form-A above to Form-B. We can directly modify
|
||
the cons cell of PROP because it is not shared. */
|
||
XSETFASTINT (id, n_compositions);
|
||
XSETCAR (prop, id);
|
||
XSETCDR (prop, Fcons (make_fixnum (nchars), Fcons (key, XCDR (prop))));
|
||
|
||
/* Register the composition in composition_hash_table. */
|
||
hash_index = hash_put (hash_table, key, id, hash_code);
|
||
|
||
method = (NILP (components)
|
||
? COMPOSITION_RELATIVE
|
||
: ((FIXNUMP (components) || STRINGP (components))
|
||
? COMPOSITION_WITH_ALTCHARS
|
||
: COMPOSITION_WITH_RULE_ALTCHARS));
|
||
|
||
glyph_len = (method == COMPOSITION_WITH_RULE_ALTCHARS
|
||
? (ASIZE (key) + 1) / 2
|
||
: ASIZE (key));
|
||
|
||
if (GLYPH_LEN_MAX < glyph_len)
|
||
memory_full (SIZE_MAX);
|
||
|
||
/* Register the composition in composition_table. */
|
||
cmp = xmalloc (sizeof *cmp);
|
||
|
||
cmp->method = method;
|
||
cmp->hash_index = hash_index;
|
||
cmp->glyph_len = glyph_len;
|
||
cmp->offsets = xnmalloc (glyph_len, 2 * sizeof *cmp->offsets);
|
||
cmp->font = NULL;
|
||
|
||
if (cmp->method != COMPOSITION_WITH_RULE_ALTCHARS)
|
||
{
|
||
/* Relative composition. */
|
||
cmp->width = 0;
|
||
for (ptrdiff_t i = 0; i < glyph_len; i++)
|
||
{
|
||
int this_width;
|
||
ch = XFIXNUM (key_contents[i]);
|
||
/* TAB in a composition means display glyphs with padding
|
||
space on the left or right. */
|
||
this_width = (ch == '\t' ? 1 : CHARACTER_WIDTH (ch));
|
||
if (cmp->width < this_width)
|
||
cmp->width = this_width;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Rule-base composition. */
|
||
double leftmost = 0.0, rightmost;
|
||
|
||
ch = XFIXNUM (key_contents[0]);
|
||
rightmost = ch != '\t' ? CHARACTER_WIDTH (ch) : 1;
|
||
|
||
for (ptrdiff_t i = 1; i < glyph_len; i += 2)
|
||
{
|
||
int rule, gref, nref;
|
||
int this_width;
|
||
double this_left;
|
||
|
||
rule = XFIXNUM (key_contents[i]);
|
||
ch = XFIXNUM (key_contents[i + 1]);
|
||
this_width = ch != '\t' ? CHARACTER_WIDTH (ch) : 1;
|
||
|
||
/* A composition rule is specified by an integer value
|
||
that encodes global and new reference points (GREF and
|
||
NREF). GREF and NREF are specified by numbers as
|
||
below:
|
||
0---1---2 -- ascent
|
||
| |
|
||
| |
|
||
| |
|
||
9--10--11 -- center
|
||
| |
|
||
---3---4---5--- baseline
|
||
| |
|
||
6---7---8 -- descent
|
||
*/
|
||
COMPOSITION_DECODE_REFS (rule, gref, nref);
|
||
this_left = (leftmost
|
||
+ (gref % 3) * (rightmost - leftmost) / 2.0
|
||
- (nref % 3) * this_width / 2.0);
|
||
|
||
if (this_left < leftmost)
|
||
leftmost = this_left;
|
||
if (this_left + this_width > rightmost)
|
||
rightmost = this_left + this_width;
|
||
}
|
||
|
||
cmp->width = rightmost - leftmost;
|
||
if (cmp->width < (rightmost - leftmost))
|
||
/* To get a ceiling integer value. */
|
||
cmp->width++;
|
||
}
|
||
|
||
composition_table[n_compositions] = cmp;
|
||
|
||
return n_compositions++;
|
||
|
||
invalid_composition:
|
||
/* Would it be better to remove this `composition' property? */
|
||
return -1;
|
||
}
|
||
|
||
|
||
/* Find a static composition at or nearest to position POS of OBJECT
|
||
(buffer or string).
|
||
|
||
OBJECT defaults to the current buffer. If there's a composition at
|
||
POS, set *START and *END to the start and end of the sequence,
|
||
*PROP to the `composition' property, and return 1.
|
||
|
||
If there's no composition at POS and LIMIT is negative, return 0.
|
||
|
||
Otherwise, search for a composition forward (LIMIT > POS) or
|
||
backward (LIMIT < POS). In this case, LIMIT bounds the search.
|
||
|
||
If a composition is found, set *START, *END, and *PROP as above,
|
||
and return 1, else return 0.
|
||
|
||
This doesn't check the validity of composition. */
|
||
|
||
bool
|
||
find_composition (ptrdiff_t pos, ptrdiff_t limit,
|
||
ptrdiff_t *start, ptrdiff_t *end,
|
||
Lisp_Object *prop, Lisp_Object object)
|
||
{
|
||
Lisp_Object val;
|
||
|
||
if (get_property_and_range (pos, Qcomposition, prop, start, end, object))
|
||
return 1;
|
||
|
||
if (limit < 0 || limit == pos)
|
||
return 0;
|
||
|
||
if (limit > pos) /* search forward */
|
||
{
|
||
val = Fnext_single_property_change (make_fixnum (pos), Qcomposition,
|
||
object, make_fixnum (limit));
|
||
pos = XFIXNUM (val);
|
||
if (pos == limit)
|
||
return 0;
|
||
}
|
||
else /* search backward */
|
||
{
|
||
if (get_property_and_range (pos - 1, Qcomposition, prop, start, end,
|
||
object))
|
||
return 1;
|
||
val = Fprevious_single_property_change (make_fixnum (pos), Qcomposition,
|
||
object, make_fixnum (limit));
|
||
pos = XFIXNUM (val);
|
||
if (pos == limit)
|
||
return 0;
|
||
pos--;
|
||
}
|
||
get_property_and_range (pos, Qcomposition, prop, start, end, object);
|
||
return 1;
|
||
}
|
||
|
||
/* Run a proper function to adjust the composition sitting between
|
||
FROM and TO with property PROP. */
|
||
|
||
static void
|
||
run_composition_function (ptrdiff_t from, ptrdiff_t to, Lisp_Object prop)
|
||
{
|
||
Lisp_Object func;
|
||
ptrdiff_t start, end;
|
||
|
||
func = COMPOSITION_MODIFICATION_FUNC (prop);
|
||
/* If an invalid composition precedes or follows, try to make them
|
||
valid too. */
|
||
if (from > BEGV
|
||
&& find_composition (from - 1, -1, &start, &end, &prop, Qnil)
|
||
&& !composition_valid_p (start, end, prop))
|
||
from = start;
|
||
if (to < ZV
|
||
&& find_composition (to, -1, &start, &end, &prop, Qnil)
|
||
&& !composition_valid_p (start, end, prop))
|
||
to = end;
|
||
if (!NILP (Ffboundp (func)))
|
||
call2 (func, make_fixnum (from), make_fixnum (to));
|
||
}
|
||
|
||
/* Make invalid compositions adjacent to or inside FROM and TO valid.
|
||
CHECK_MASK is bitwise `or' of mask bits defined by macros
|
||
CHECK_XXX (see the comment in composite.h).
|
||
|
||
It also resets the text-property `auto-composed' to a proper region
|
||
so that automatic character composition works correctly later while
|
||
displaying the region.
|
||
|
||
This function is called when a buffer text is changed. If the
|
||
change is deletion, FROM == TO. Otherwise, FROM < TO. */
|
||
|
||
void
|
||
update_compositions (ptrdiff_t from, ptrdiff_t to, int check_mask)
|
||
{
|
||
Lisp_Object prop;
|
||
ptrdiff_t start, end;
|
||
/* The beginning and end of the region to set the property
|
||
`auto-composed' to nil. */
|
||
ptrdiff_t min_pos = from, max_pos = to;
|
||
|
||
if (inhibit_modification_hooks)
|
||
return;
|
||
|
||
/* If FROM and TO are not in a valid range, do nothing. */
|
||
if (! (BEGV <= from && from <= to && to <= ZV))
|
||
return;
|
||
|
||
if (check_mask & CHECK_HEAD)
|
||
{
|
||
/* FROM should be at composition boundary. But, insertion or
|
||
deletion will make two compositions adjacent and
|
||
indistinguishable when they have same (eq) property. To
|
||
avoid it, in such a case, we change the property of the
|
||
latter to the copy of it. */
|
||
if (from > BEGV
|
||
&& find_composition (from - 1, -1, &start, &end, &prop, Qnil)
|
||
&& composition_valid_p (start, end, prop))
|
||
{
|
||
min_pos = start;
|
||
if (end > to)
|
||
max_pos = end;
|
||
if (from < end)
|
||
Fput_text_property (make_fixnum (from), make_fixnum (end),
|
||
Qcomposition,
|
||
Fcons (XCAR (prop), XCDR (prop)), Qnil);
|
||
run_composition_function (start, end, prop);
|
||
from = end;
|
||
}
|
||
else if (from < ZV
|
||
&& find_composition (from, -1, &start, &from, &prop, Qnil)
|
||
&& composition_valid_p (start, from, prop))
|
||
{
|
||
if (from > to)
|
||
max_pos = from;
|
||
run_composition_function (start, from, prop);
|
||
}
|
||
}
|
||
|
||
if (check_mask & CHECK_INSIDE)
|
||
{
|
||
/* In this case, we are sure that (check & CHECK_TAIL) is also
|
||
nonzero. Thus, here we should check only compositions before
|
||
(to - 1). */
|
||
while (from < to - 1
|
||
&& find_composition (from, to, &start, &from, &prop, Qnil)
|
||
&& composition_valid_p (start, from, prop)
|
||
&& from < to - 1)
|
||
run_composition_function (start, from, prop);
|
||
}
|
||
|
||
if (check_mask & CHECK_TAIL)
|
||
{
|
||
if (from < to
|
||
&& find_composition (to - 1, -1, &start, &end, &prop, Qnil)
|
||
&& composition_valid_p (start, end, prop))
|
||
{
|
||
/* TO should be also at composition boundary. But,
|
||
insertion or deletion will make two compositions adjacent
|
||
and indistinguishable when they have same (eq) property.
|
||
To avoid it, in such a case, we change the property of
|
||
the former to the copy of it. */
|
||
if (to < end)
|
||
{
|
||
Fput_text_property (make_fixnum (start), make_fixnum (to),
|
||
Qcomposition,
|
||
Fcons (XCAR (prop), XCDR (prop)), Qnil);
|
||
max_pos = end;
|
||
}
|
||
run_composition_function (start, end, prop);
|
||
}
|
||
else if (to < ZV
|
||
&& find_composition (to, -1, &start, &end, &prop, Qnil)
|
||
&& composition_valid_p (start, end, prop))
|
||
{
|
||
run_composition_function (start, end, prop);
|
||
max_pos = end;
|
||
}
|
||
}
|
||
if (min_pos < max_pos)
|
||
{
|
||
specpdl_ref count = SPECPDL_INDEX ();
|
||
|
||
specbind (Qinhibit_read_only, Qt);
|
||
specbind (Qinhibit_modification_hooks, Qt);
|
||
specbind (Qinhibit_point_motion_hooks, Qt);
|
||
Fremove_list_of_text_properties (make_fixnum (min_pos),
|
||
make_fixnum (max_pos),
|
||
list1 (Qauto_composed), Qnil);
|
||
unbind_to (count, Qnil);
|
||
}
|
||
}
|
||
|
||
|
||
/* Modify composition property values in LIST destructively. LIST is
|
||
a list as returned from text_property_list. Change values to the
|
||
top-level copies of them so that none of them are `eq'. */
|
||
|
||
void
|
||
make_composition_value_copy (Lisp_Object list)
|
||
{
|
||
Lisp_Object plist, val;
|
||
|
||
for (; CONSP (list); list = XCDR (list))
|
||
{
|
||
plist = XCAR (XCDR (XCDR (XCAR (list))));
|
||
while (CONSP (plist) && CONSP (XCDR (plist)))
|
||
{
|
||
if (EQ (XCAR (plist), Qcomposition)
|
||
&& (val = XCAR (XCDR (plist)), CONSP (val)))
|
||
XSETCAR (XCDR (plist), Fcons (XCAR (val), XCDR (val)));
|
||
plist = XCDR (XCDR (plist));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Make text in the region between START and END a composition that
|
||
has COMPONENTS and MODIFICATION-FUNC.
|
||
|
||
If STRING is non-nil, then operate on characters contained between
|
||
indices START and END in STRING. */
|
||
|
||
void
|
||
compose_text (ptrdiff_t start, ptrdiff_t end, Lisp_Object components,
|
||
Lisp_Object modification_func, Lisp_Object string)
|
||
{
|
||
Lisp_Object prop;
|
||
|
||
prop = Fcons (Fcons (make_fixnum (end - start), components),
|
||
modification_func);
|
||
Fput_text_property (make_fixnum (start), make_fixnum (end),
|
||
Qcomposition, prop, string);
|
||
}
|
||
|
||
/* Lisp glyph-string handlers. */
|
||
|
||
/* Hash table for automatic composition. The key is a header of a
|
||
lgstring (Lispy glyph-string), and the value is a body of a
|
||
lgstring. */
|
||
|
||
static Lisp_Object gstring_hash_table;
|
||
|
||
Lisp_Object
|
||
composition_gstring_lookup_cache (Lisp_Object header)
|
||
{
|
||
struct Lisp_Hash_Table *h = XHASH_TABLE (gstring_hash_table);
|
||
ptrdiff_t i = hash_lookup (h, header, NULL);
|
||
|
||
return (i >= 0 ? HASH_VALUE (h, i) : Qnil);
|
||
}
|
||
|
||
Lisp_Object
|
||
composition_gstring_put_cache (Lisp_Object gstring, ptrdiff_t len)
|
||
{
|
||
struct Lisp_Hash_Table *h = XHASH_TABLE (gstring_hash_table);
|
||
Lisp_Object header = LGSTRING_HEADER (gstring);
|
||
Lisp_Object hash = h->test.hashfn (header, h);
|
||
if (len < 0)
|
||
{
|
||
ptrdiff_t glyph_len = LGSTRING_GLYPH_LEN (gstring);
|
||
for (len = 0; len < glyph_len; len++)
|
||
if (NILP (LGSTRING_GLYPH (gstring, len)))
|
||
break;
|
||
}
|
||
|
||
Lisp_Object copy = make_nil_vector (len + 2);
|
||
LGSTRING_SET_HEADER (copy, Fcopy_sequence (header));
|
||
for (ptrdiff_t i = 0; i < len; i++)
|
||
LGSTRING_SET_GLYPH (copy, i, Fcopy_sequence (LGSTRING_GLYPH (gstring, i)));
|
||
ptrdiff_t id = hash_put (h, LGSTRING_HEADER (copy), copy, hash);
|
||
LGSTRING_SET_ID (copy, make_fixnum (id));
|
||
return copy;
|
||
}
|
||
|
||
Lisp_Object
|
||
composition_gstring_from_id (ptrdiff_t id)
|
||
{
|
||
struct Lisp_Hash_Table *h = XHASH_TABLE (gstring_hash_table);
|
||
|
||
return HASH_VALUE (h, id);
|
||
}
|
||
|
||
/* Remove from the composition hash table every lgstring that
|
||
references the given FONT_OBJECT. */
|
||
void
|
||
composition_gstring_cache_clear_font (Lisp_Object font_object)
|
||
{
|
||
struct Lisp_Hash_Table *h = XHASH_TABLE (gstring_hash_table);
|
||
|
||
for (ptrdiff_t i = 0; i < HASH_TABLE_SIZE (h); ++i)
|
||
{
|
||
Lisp_Object k = HASH_KEY (h, i);
|
||
|
||
if (!BASE_EQ (k, Qunbound))
|
||
{
|
||
Lisp_Object gstring = HASH_VALUE (h, i);
|
||
|
||
if (EQ (LGSTRING_FONT (gstring), font_object))
|
||
hash_remove_from_table (h, k);
|
||
}
|
||
}
|
||
}
|
||
|
||
DEFUN ("clear-composition-cache", Fclear_composition_cache,
|
||
Sclear_composition_cache, 0, 0, 0,
|
||
doc: /* Internal use only.
|
||
Clear composition cache. */)
|
||
(void)
|
||
{
|
||
gstring_hash_table = CALLN (Fmake_hash_table, QCtest, Qequal,
|
||
QCsize, make_fixnum (311));
|
||
/* Fixme: We call Fclear_face_cache to force complete re-building of
|
||
display glyphs. But, it may be better to call this function from
|
||
Fclear_face_cache instead. */
|
||
return Fclear_face_cache (Qt);
|
||
}
|
||
|
||
bool
|
||
composition_gstring_p (Lisp_Object gstring)
|
||
{
|
||
Lisp_Object header;
|
||
ptrdiff_t i;
|
||
|
||
if (! VECTORP (gstring) || ASIZE (gstring) < 2)
|
||
return 0;
|
||
header = LGSTRING_HEADER (gstring);
|
||
if (! VECTORP (header) || ASIZE (header) < 2)
|
||
return 0;
|
||
if (! NILP (LGSTRING_FONT (gstring))
|
||
&& (! FONT_OBJECT_P (LGSTRING_FONT (gstring))
|
||
&& ! CODING_SYSTEM_P (LGSTRING_FONT (gstring))))
|
||
return 0;
|
||
for (i = 1; i < ASIZE (LGSTRING_HEADER (gstring)); i++)
|
||
if (! FIXNATP (AREF (LGSTRING_HEADER (gstring), i)))
|
||
return 0;
|
||
if (! NILP (LGSTRING_ID (gstring)) && ! FIXNATP (LGSTRING_ID (gstring)))
|
||
return 0;
|
||
for (i = 0; i < LGSTRING_GLYPH_LEN (gstring); i++)
|
||
{
|
||
Lisp_Object glyph = LGSTRING_GLYPH (gstring, i);
|
||
if (NILP (glyph))
|
||
break;
|
||
if (! VECTORP (glyph) || ASIZE (glyph) != LGLYPH_SIZE)
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
int
|
||
composition_gstring_width (Lisp_Object gstring, ptrdiff_t from, ptrdiff_t to,
|
||
struct font_metrics *metrics)
|
||
{
|
||
Lisp_Object *glyph;
|
||
int width = 0;
|
||
|
||
if (metrics)
|
||
{
|
||
Lisp_Object font_object = LGSTRING_FONT (gstring);
|
||
|
||
if (FONT_OBJECT_P (font_object))
|
||
{
|
||
struct font *font = XFONT_OBJECT (font_object);
|
||
int font_ascent, font_descent;
|
||
|
||
get_font_ascent_descent (font, &font_ascent, &font_descent);
|
||
metrics->ascent = font_ascent;
|
||
metrics->descent = font_descent;
|
||
}
|
||
else
|
||
{
|
||
metrics->ascent = 1;
|
||
metrics->descent = 0;
|
||
}
|
||
metrics->width = metrics->lbearing = metrics->rbearing = 0;
|
||
}
|
||
for (glyph = lgstring_glyph_addr (gstring, from); from < to; from++, glyph++)
|
||
{
|
||
int x;
|
||
|
||
if (NILP (LGLYPH_ADJUSTMENT (*glyph)))
|
||
width += LGLYPH_WIDTH (*glyph);
|
||
else
|
||
width += LGLYPH_WADJUST (*glyph);
|
||
if (metrics)
|
||
{
|
||
x = metrics->width + LGLYPH_LBEARING (*glyph) + LGLYPH_XOFF (*glyph);
|
||
if (metrics->lbearing > x)
|
||
metrics->lbearing = x;
|
||
x = metrics->width + LGLYPH_RBEARING (*glyph) + LGLYPH_XOFF (*glyph);
|
||
if (metrics->rbearing < x)
|
||
metrics->rbearing = x;
|
||
metrics->width = width;
|
||
x = LGLYPH_ASCENT (*glyph) - LGLYPH_YOFF (*glyph);
|
||
if (metrics->ascent < x)
|
||
metrics->ascent = x;
|
||
x = LGLYPH_DESCENT (*glyph) + LGLYPH_YOFF (*glyph);
|
||
if (metrics->descent < x)
|
||
metrics->descent = x;
|
||
}
|
||
}
|
||
return width;
|
||
}
|
||
|
||
/* Adjust the width of each grapheme cluster of GSTRING because
|
||
zero-width grapheme clusters are not displayed. If the width is
|
||
zero, then the width of the last glyph in the cluster is
|
||
incremented. */
|
||
|
||
void
|
||
composition_gstring_adjust_zero_width (Lisp_Object gstring)
|
||
{
|
||
ptrdiff_t from = 0;
|
||
int width = 0;
|
||
|
||
for (ptrdiff_t i = 0; ; i++)
|
||
{
|
||
Lisp_Object glyph;
|
||
|
||
if (i < LGSTRING_GLYPH_LEN (gstring))
|
||
glyph = LGSTRING_GLYPH (gstring, i);
|
||
else
|
||
glyph = Qnil;
|
||
|
||
if (NILP (glyph) || from != LGLYPH_FROM (glyph))
|
||
{
|
||
eassert (i > 0);
|
||
Lisp_Object last = LGSTRING_GLYPH (gstring, i - 1);
|
||
|
||
if (width == 0)
|
||
{
|
||
if (NILP (LGLYPH_ADJUSTMENT (last)))
|
||
LGLYPH_SET_ADJUSTMENT (last,
|
||
CALLN (Fvector,
|
||
make_fixnum (0), make_fixnum (0),
|
||
make_fixnum (LGLYPH_WIDTH (last)
|
||
+ 1)));
|
||
else
|
||
ASET (LGLYPH_ADJUSTMENT (last), 2,
|
||
make_fixnum (LGLYPH_WADJUST (last) + 1));
|
||
}
|
||
if (NILP (glyph))
|
||
break;
|
||
from = LGLYPH_FROM (glyph);
|
||
width = 0;
|
||
}
|
||
width += (NILP (LGLYPH_ADJUSTMENT (glyph))
|
||
? LGLYPH_WIDTH (glyph) : LGLYPH_WADJUST (glyph));
|
||
}
|
||
}
|
||
|
||
|
||
static Lisp_Object gstring_work;
|
||
static Lisp_Object gstring_work_headers;
|
||
|
||
static Lisp_Object
|
||
fill_gstring_header (ptrdiff_t from, ptrdiff_t from_byte,
|
||
ptrdiff_t to, Lisp_Object font_object, Lisp_Object string)
|
||
{
|
||
ptrdiff_t len = to - from;
|
||
if (len == 0)
|
||
error ("Attempt to shape zero-length text");
|
||
eassume (0 < len);
|
||
Lisp_Object header = (len <= 8
|
||
? AREF (gstring_work_headers, len - 1)
|
||
: make_uninit_vector (len + 1));
|
||
|
||
ASET (header, 0, font_object);
|
||
for (ptrdiff_t i = 0; i < len; i++)
|
||
{
|
||
int c
|
||
= (NILP (string)
|
||
? fetch_char_advance_no_check (&from, &from_byte)
|
||
: fetch_string_char_advance_no_check (string, &from, &from_byte));
|
||
ASET (header, i + 1, make_fixnum (c));
|
||
}
|
||
return header;
|
||
}
|
||
|
||
static void
|
||
fill_gstring_body (Lisp_Object gstring)
|
||
{
|
||
Lisp_Object font_object = LGSTRING_FONT (gstring);
|
||
Lisp_Object header = AREF (gstring, 0);
|
||
ptrdiff_t len = LGSTRING_CHAR_LEN (gstring);
|
||
ptrdiff_t i;
|
||
struct font *font = NULL;
|
||
unsigned int code;
|
||
|
||
if (FONT_OBJECT_P (font_object))
|
||
font = XFONT_OBJECT (font_object);
|
||
|
||
for (i = 0; i < len; i++)
|
||
{
|
||
Lisp_Object g = LGSTRING_GLYPH (gstring, i);
|
||
int c = XFIXNAT (AREF (header, i + 1));
|
||
|
||
if (NILP (g))
|
||
{
|
||
g = LGLYPH_NEW ();
|
||
LGSTRING_SET_GLYPH (gstring, i, g);
|
||
}
|
||
LGLYPH_SET_FROM (g, i);
|
||
LGLYPH_SET_TO (g, i);
|
||
LGLYPH_SET_CHAR (g, c);
|
||
|
||
if (font != NULL)
|
||
code = font->driver->encode_char (font, LGLYPH_CHAR (g));
|
||
else
|
||
code = FONT_INVALID_CODE;
|
||
if (code != FONT_INVALID_CODE)
|
||
{
|
||
font_fill_lglyph_metrics (g, font, code);
|
||
}
|
||
else
|
||
{
|
||
int width = XFIXNAT (CHAR_TABLE_REF (Vchar_width_table, c));
|
||
|
||
LGLYPH_SET_CODE (g, c);
|
||
LGLYPH_SET_LBEARING (g, 0);
|
||
LGLYPH_SET_RBEARING (g, width);
|
||
LGLYPH_SET_WIDTH (g, width);
|
||
LGLYPH_SET_ASCENT (g, 1);
|
||
LGLYPH_SET_DESCENT (g, 0);
|
||
}
|
||
LGLYPH_SET_ADJUSTMENT (g, Qnil);
|
||
}
|
||
len = LGSTRING_GLYPH_LEN (gstring);
|
||
for (; i < len; i++)
|
||
LGSTRING_SET_GLYPH (gstring, i, Qnil);
|
||
}
|
||
|
||
|
||
/* Try to compose the characters at CHARPOS according to composition
|
||
rule RULE ([PATTERN PREV-CHARS FUNC]). LIMIT limits the characters
|
||
to compose. STRING, if not nil, is a target string. WIN is a
|
||
window where the characters are being displayed. CH is the
|
||
character that triggered the composition check. If characters are
|
||
successfully composed, return the composition as a glyph-string
|
||
object. Otherwise return nil. */
|
||
|
||
static Lisp_Object
|
||
autocmp_chars (Lisp_Object rule, ptrdiff_t charpos, ptrdiff_t bytepos,
|
||
ptrdiff_t limit, struct window *win, struct face *face,
|
||
Lisp_Object string, Lisp_Object direction, int ch)
|
||
{
|
||
specpdl_ref count = SPECPDL_INDEX ();
|
||
Lisp_Object pos = make_fixnum (charpos);
|
||
ptrdiff_t to;
|
||
ptrdiff_t pt = PT, pt_byte = PT_BYTE;
|
||
Lisp_Object re, font_object, lgstring;
|
||
ptrdiff_t len;
|
||
|
||
record_unwind_save_match_data ();
|
||
re = AREF (rule, 0);
|
||
if (NILP (re))
|
||
len = 1;
|
||
else if (! STRINGP (re))
|
||
return unbind_to (count, Qnil);
|
||
else if ((len = fast_looking_at (re, charpos, bytepos, limit, -1, string))
|
||
> 0)
|
||
{
|
||
if (NILP (string))
|
||
len = BYTE_TO_CHAR (bytepos + len) - charpos;
|
||
else
|
||
len = string_byte_to_char (string, bytepos + len) - charpos;
|
||
}
|
||
if (len <= 0)
|
||
return unbind_to (count, Qnil);
|
||
to = limit = charpos + len;
|
||
font_object = win->frame;
|
||
#ifdef HAVE_WINDOW_SYSTEM
|
||
struct frame *f = XFRAME (font_object);
|
||
if (FRAME_WINDOW_P (f))
|
||
{
|
||
font_object = font_range (charpos, bytepos, &to, win, face, string, ch);
|
||
if (! FONT_OBJECT_P (font_object)
|
||
|| (! NILP (re)
|
||
&& to < limit
|
||
&& (fast_looking_at (re, charpos, bytepos, to, -1, string) <= 0)))
|
||
return unbind_to (count, Qnil);
|
||
}
|
||
#endif
|
||
lgstring = Fcomposition_get_gstring (pos, make_fixnum (to), font_object,
|
||
string);
|
||
if (NILP (LGSTRING_ID (lgstring)))
|
||
{
|
||
/* Save point as marker before calling out to lisp. */
|
||
if (NILP (string))
|
||
record_unwind_protect (restore_point_unwind,
|
||
build_marker (current_buffer, pt, pt_byte));
|
||
lgstring = safe_call (7, Vauto_composition_function, AREF (rule, 2),
|
||
pos, make_fixnum (to), font_object, string,
|
||
direction);
|
||
}
|
||
return unbind_to (count, lgstring);
|
||
}
|
||
|
||
/* 1 iff the character C is composable. Characters of general
|
||
category Z? or C? are not composable except for ZWNJ and ZWJ,
|
||
and characters of category Zs. */
|
||
|
||
static bool
|
||
char_composable_p (int c)
|
||
{
|
||
Lisp_Object val;
|
||
return (c >= ' '
|
||
&& (c == ZERO_WIDTH_NON_JOINER || c == ZERO_WIDTH_JOINER
|
||
/* Per Unicode TR51, these tag characters can be part of
|
||
Emoji sequences. */
|
||
|| (TAG_SPACE <= c && c <= CANCEL_TAG)
|
||
/* unicode-category-table may not be available during
|
||
dumping. */
|
||
|| (CHAR_TABLE_P (Vunicode_category_table)
|
||
&& (val = CHAR_TABLE_REF (Vunicode_category_table, c),
|
||
(FIXNUMP (val)
|
||
&& (XFIXNUM (val) <= UNICODE_CATEGORY_Zs))))));
|
||
}
|
||
|
||
static inline bool
|
||
inhibit_auto_composition (void)
|
||
{
|
||
if (NILP (Vauto_composition_mode))
|
||
return true;
|
||
|
||
if (STRINGP (Vauto_composition_mode))
|
||
{
|
||
char *name = tty_type_name (Qnil);
|
||
|
||
if (name && ! strcmp (SSDATA (Vauto_composition_mode), name))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Update cmp_it->stop_pos to the next position after CHARPOS (and
|
||
BYTEPOS) where character composition may happen. If BYTEPOS is
|
||
negative, compute it. ENDPOS is a limit of searching. If it is
|
||
less than CHARPOS, search backward to ENDPOS+1 assuming that
|
||
set_iterator_to_next works in reverse order. In this case, if a
|
||
composition closest to CHARPOS is found, set cmp_it->stop_pos to
|
||
the last character of the composition. STRING, if non-nil, is
|
||
the string (as opposed to a buffer) whose characters should be
|
||
tested for being composable. INCLUDE_STATIC non-zero means
|
||
consider both static and automatic compositions; if zero, look
|
||
only for potential automatic compositions.
|
||
|
||
If no composition is found, set cmp_it->ch to -2. If a static
|
||
composition is found, set cmp_it->ch to -1. Otherwise, set
|
||
cmp_it->ch to the character that triggers the automatic
|
||
composition. */
|
||
|
||
void
|
||
composition_compute_stop_pos (struct composition_it *cmp_it, ptrdiff_t charpos,
|
||
ptrdiff_t bytepos, ptrdiff_t endpos,
|
||
Lisp_Object string, bool include_static)
|
||
{
|
||
ptrdiff_t start, end;
|
||
int c;
|
||
Lisp_Object prop, val;
|
||
/* This is from forward_to_next_line_start in xdisp.c. */
|
||
const int MAX_NEWLINE_DISTANCE = 500;
|
||
|
||
if (charpos < endpos)
|
||
{
|
||
if (endpos > charpos + MAX_NEWLINE_DISTANCE)
|
||
endpos = charpos + MAX_NEWLINE_DISTANCE;
|
||
}
|
||
else if (endpos < charpos)
|
||
{
|
||
/* We search backward for a position to check composition. */
|
||
if (endpos < 0)
|
||
{
|
||
/* But we don't know where to stop the searching. */
|
||
endpos = NILP (string) ? BEGV - 1 : -1;
|
||
/* Usually we don't reach ENDPOS because we stop searching
|
||
at an uncomposable character (NL, LRE, etc). In buffers
|
||
with long lines, however, NL might be far away, so
|
||
pretend that the buffer is smaller. */
|
||
if (current_buffer->long_line_optimizations_p)
|
||
endpos = get_small_narrowing_begv (cmp_it->parent_it->w, charpos);
|
||
}
|
||
}
|
||
cmp_it->id = -1;
|
||
cmp_it->ch = -2;
|
||
cmp_it->reversed_p = 0;
|
||
cmp_it->stop_pos = endpos;
|
||
if (charpos == endpos)
|
||
return;
|
||
/* Look for static compositions. */
|
||
/* FIXME: Bidi is not yet handled well in static composition. */
|
||
if (include_static
|
||
&& charpos < endpos
|
||
&& find_composition (charpos, endpos, &start, &end, &prop, string)
|
||
&& start >= charpos
|
||
&& composition_valid_p (start, end, prop))
|
||
{
|
||
cmp_it->stop_pos = endpos = start;
|
||
cmp_it->ch = -1;
|
||
}
|
||
if ((NILP (string)
|
||
&& NILP (BVAR (current_buffer, enable_multibyte_characters)))
|
||
|| (STRINGP (string) && !STRING_MULTIBYTE (string))
|
||
|| inhibit_auto_composition ())
|
||
return;
|
||
if (bytepos < 0)
|
||
{
|
||
if (NILP (string))
|
||
bytepos = CHAR_TO_BYTE (charpos);
|
||
else
|
||
bytepos = string_char_to_byte (string, charpos);
|
||
}
|
||
|
||
/* Look for automatic compositions. */
|
||
start = charpos;
|
||
if (charpos < endpos)
|
||
{
|
||
/* Forward search. */
|
||
while (charpos < endpos)
|
||
{
|
||
c = (STRINGP (string)
|
||
? fetch_string_char_advance (string, &charpos, &bytepos)
|
||
: fetch_char_advance (&charpos, &bytepos));
|
||
if (c == '\n')
|
||
{
|
||
cmp_it->ch = -2;
|
||
break;
|
||
}
|
||
val = CHAR_TABLE_REF (Vcomposition_function_table, c);
|
||
if (! NILP (val))
|
||
{
|
||
for (EMACS_INT ridx = 0; CONSP (val); val = XCDR (val), ridx++)
|
||
{
|
||
Lisp_Object elt = XCAR (val);
|
||
if (VECTORP (elt) && ASIZE (elt) == 3
|
||
&& FIXNATP (AREF (elt, 1))
|
||
&& charpos - 1 - XFIXNAT (AREF (elt, 1)) >= start)
|
||
{
|
||
cmp_it->rule_idx = ridx;
|
||
cmp_it->lookback = XFIXNAT (AREF (elt, 1));
|
||
cmp_it->stop_pos = charpos - 1 - cmp_it->lookback;
|
||
cmp_it->ch = c;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (charpos == endpos
|
||
&& !(STRINGP (string) && endpos == SCHARS (string)))
|
||
{
|
||
/* We couldn't find a composition point before ENDPOS. But,
|
||
some character after ENDPOS may be composed with
|
||
characters before ENDPOS. So, we should stop at the safe
|
||
point. */
|
||
charpos = endpos - MAX_AUTO_COMPOSITION_LOOKBACK;
|
||
if (charpos < start)
|
||
charpos = start;
|
||
}
|
||
}
|
||
else if (charpos > endpos)
|
||
{
|
||
/* Search backward for a pattern that may be composed and the
|
||
position of (possibly) the last character of the match is
|
||
closest to (but not after) START. The reason for the last
|
||
character is that set_iterator_to_next works in reverse order,
|
||
and thus we must stop at the last character for composition
|
||
check. */
|
||
unsigned char *p;
|
||
int len;
|
||
/* Limit byte position used in fast_looking_at. This is the
|
||
byte position of the character after START. */
|
||
ptrdiff_t limit;
|
||
|
||
if (NILP (string))
|
||
p = BYTE_POS_ADDR (bytepos);
|
||
else
|
||
p = SDATA (string) + bytepos;
|
||
c = string_char_and_length (p, &len);
|
||
limit = bytepos + len;
|
||
while (char_composable_p (c))
|
||
{
|
||
val = CHAR_TABLE_REF (Vcomposition_function_table, c);
|
||
for (EMACS_INT ridx = 0; CONSP (val); val = XCDR (val), ridx++)
|
||
{
|
||
Lisp_Object elt = XCAR (val);
|
||
if (VECTORP (elt) && ASIZE (elt) == 3
|
||
&& FIXNATP (AREF (elt, 1))
|
||
&& charpos - XFIXNAT (AREF (elt, 1)) > endpos)
|
||
{
|
||
ptrdiff_t back = XFIXNAT (AREF (elt, 1));
|
||
ptrdiff_t cpos = charpos - back, bpos;
|
||
|
||
if (back == 0)
|
||
bpos = bytepos;
|
||
else
|
||
bpos = (NILP (string) ? CHAR_TO_BYTE (cpos)
|
||
: string_char_to_byte (string, cpos));
|
||
ptrdiff_t blen
|
||
= (STRINGP (AREF (elt, 0))
|
||
? fast_looking_at (AREF (elt, 0), cpos, bpos,
|
||
start + 1, limit, string)
|
||
: 1);
|
||
if (blen > 0)
|
||
{
|
||
/* Make CPOS point to the last character of
|
||
match. Note that BLEN is byte-length. */
|
||
if (blen > 1)
|
||
{
|
||
bpos += blen;
|
||
if (NILP (string))
|
||
cpos = BYTE_TO_CHAR (bpos) - 1;
|
||
else
|
||
cpos = string_byte_to_char (string, bpos) - 1;
|
||
}
|
||
back = cpos - (charpos - back);
|
||
if (cmp_it->stop_pos < cpos
|
||
|| (cmp_it->stop_pos == cpos
|
||
&& cmp_it->lookback < back))
|
||
{
|
||
cmp_it->rule_idx = ridx;
|
||
cmp_it->stop_pos = cpos;
|
||
cmp_it->ch = c;
|
||
cmp_it->lookback = back;
|
||
cmp_it->nchars = back + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (charpos - 1 == endpos)
|
||
break;
|
||
if (STRINGP (string))
|
||
{
|
||
p--, bytepos--;
|
||
while (! CHAR_HEAD_P (*p))
|
||
p--, bytepos--;
|
||
charpos--;
|
||
}
|
||
else
|
||
{
|
||
dec_both (&charpos, &bytepos);
|
||
p = BYTE_POS_ADDR (bytepos);
|
||
}
|
||
c = STRING_CHAR (p);
|
||
}
|
||
if (cmp_it->ch >= 0)
|
||
/* We found a position to check. */
|
||
return;
|
||
/* Skip all uncomposable characters. */
|
||
if (NILP (string))
|
||
{
|
||
while (charpos - 1 > endpos && ! char_composable_p (c))
|
||
{
|
||
dec_both (&charpos, &bytepos);
|
||
c = FETCH_MULTIBYTE_CHAR (bytepos);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
while (charpos - 1 > endpos && ! char_composable_p (c))
|
||
{
|
||
p--;
|
||
while (! CHAR_HEAD_P (*p))
|
||
p--;
|
||
charpos--;
|
||
c = STRING_CHAR (p);
|
||
}
|
||
}
|
||
}
|
||
cmp_it->stop_pos = charpos;
|
||
}
|
||
|
||
/* Check if the character at CHARPOS (and BYTEPOS) is composed
|
||
(possibly with the following characters) on window W. ENDPOS limits
|
||
characters to be composed. FACE, if non-NULL, is a base face of
|
||
the character. If STRING is not nil, it is a string containing the
|
||
character to check, and CHARPOS and BYTEPOS are indices in the
|
||
string. In that case, FACE must not be NULL. BIDI_LEVEL is the bidi
|
||
embedding level of the current paragraph, and is used to calculate the
|
||
direction argument to pass to the font shaper; value of -1 means the
|
||
caller doesn't know the embedding level (used by callers which didn't
|
||
invoke the display routines that perform bidi-display-reordering).
|
||
|
||
If the character is composed, setup members of CMP_IT (id, nglyphs,
|
||
from, to, reversed_p), and return true. Otherwise, update
|
||
CMP_IT->stop_pos, and return false. */
|
||
|
||
bool
|
||
composition_reseat_it (struct composition_it *cmp_it, ptrdiff_t charpos,
|
||
ptrdiff_t bytepos, ptrdiff_t endpos, struct window *w,
|
||
signed char bidi_level, struct face *face, Lisp_Object string)
|
||
{
|
||
if (cmp_it->ch == -2)
|
||
{
|
||
composition_compute_stop_pos (cmp_it, charpos, bytepos, endpos, string,
|
||
true);
|
||
if (cmp_it->ch == -2 || cmp_it->stop_pos != charpos)
|
||
/* The current position is not composed. */
|
||
return 0;
|
||
}
|
||
|
||
if (endpos < 0)
|
||
endpos = NILP (string) ? BEGV : 0;
|
||
|
||
if (cmp_it->ch < 0)
|
||
{
|
||
/* We are looking at a static composition. */
|
||
ptrdiff_t start, end;
|
||
Lisp_Object prop;
|
||
|
||
find_composition (charpos, -1, &start, &end, &prop, string);
|
||
cmp_it->id = get_composition_id (charpos, bytepos, end - start,
|
||
prop, string);
|
||
if (cmp_it->id < 0)
|
||
goto no_composition;
|
||
cmp_it->nchars = end - start;
|
||
cmp_it->nglyphs = composition_table[cmp_it->id]->glyph_len;
|
||
}
|
||
else if (w)
|
||
{
|
||
Lisp_Object lgstring = Qnil;
|
||
Lisp_Object val, elt, direction = Qnil;
|
||
|
||
val = CHAR_TABLE_REF (Vcomposition_function_table, cmp_it->ch);
|
||
for (EMACS_INT i = 0; i < cmp_it->rule_idx; i++, val = XCDR (val))
|
||
continue;
|
||
if (charpos < endpos)
|
||
{
|
||
if (bidi_level < 0)
|
||
direction = Qnil;
|
||
else if ((bidi_level & 1) == 0)
|
||
direction = QL2R;
|
||
else
|
||
direction = QR2L;
|
||
for (; CONSP (val); val = XCDR (val))
|
||
{
|
||
elt = XCAR (val);
|
||
if (! VECTORP (elt) || ASIZE (elt) != 3
|
||
|| ! FIXNUMP (AREF (elt, 1)))
|
||
continue;
|
||
if (XFIXNAT (AREF (elt, 1)) != cmp_it->lookback)
|
||
goto no_composition;
|
||
lgstring = autocmp_chars (elt, charpos, bytepos, endpos,
|
||
w, face, string, direction, cmp_it->ch);
|
||
if (composition_gstring_p (lgstring))
|
||
break;
|
||
lgstring = Qnil;
|
||
/* Composition failed perhaps because the font doesn't
|
||
support sufficient range of characters. Try the
|
||
other composition rules if any. */
|
||
}
|
||
cmp_it->reversed_p = 0;
|
||
}
|
||
else
|
||
{
|
||
ptrdiff_t cpos = charpos, bpos = bytepos;
|
||
|
||
cmp_it->reversed_p = 1;
|
||
elt = XCAR (val);
|
||
if (cmp_it->lookback > 0)
|
||
{
|
||
cpos = charpos - cmp_it->lookback;
|
||
/* Reject the composition if it starts before ENDPOS,
|
||
which here can only happen if
|
||
composition-break-at-point is non-nil and point is
|
||
inside the composition. */
|
||
if (cpos < endpos)
|
||
{
|
||
eassert (composition_break_at_point);
|
||
eassert (endpos == PT);
|
||
goto no_composition;
|
||
}
|
||
if (STRINGP (string))
|
||
bpos = string_char_to_byte (string, cpos);
|
||
else
|
||
bpos = CHAR_TO_BYTE (cpos);
|
||
}
|
||
/* The bidi_level < 0 case below strictly speaking should
|
||
never happen, since we get here when bidi scan direction
|
||
is backward in the buffer, which can only happen if the
|
||
display routines were called to perform the bidi
|
||
reordering. But it doesn't harm to test for that, and
|
||
avoid someone raising their brows and thinking it's a
|
||
subtle bug... */
|
||
if (bidi_level < 0)
|
||
direction = Qnil;
|
||
else if ((bidi_level & 1) == 0)
|
||
direction = QL2R;
|
||
else
|
||
direction = QR2L;
|
||
lgstring = autocmp_chars (elt, cpos, bpos, charpos + 1, w, face,
|
||
string, direction, cmp_it->ch);
|
||
if (! composition_gstring_p (lgstring)
|
||
|| cpos + LGSTRING_CHAR_LEN (lgstring) - 1 != charpos)
|
||
/* Composition failed or didn't cover the current
|
||
character. */
|
||
goto no_composition;
|
||
}
|
||
if (NILP (lgstring))
|
||
goto no_composition;
|
||
if (NILP (LGSTRING_ID (lgstring)))
|
||
lgstring = composition_gstring_put_cache (lgstring, -1);
|
||
cmp_it->id = XFIXNUM (LGSTRING_ID (lgstring));
|
||
int i;
|
||
for (i = 0; i < LGSTRING_GLYPH_LEN (lgstring); i++)
|
||
if (NILP (LGSTRING_GLYPH (lgstring, i)))
|
||
break;
|
||
cmp_it->nglyphs = i;
|
||
cmp_it->from = 0;
|
||
cmp_it->to = i;
|
||
}
|
||
else
|
||
goto no_composition;
|
||
return 1;
|
||
|
||
no_composition:
|
||
if (charpos == endpos)
|
||
return 0;
|
||
if (charpos < endpos)
|
||
{
|
||
charpos++;
|
||
if (NILP (string))
|
||
bytepos += next_char_len (bytepos);
|
||
else
|
||
bytepos += BYTES_BY_CHAR_HEAD (*(SDATA (string) + bytepos));
|
||
}
|
||
else
|
||
{
|
||
charpos--;
|
||
/* BYTEPOS is calculated in composition_compute_stop_pos */
|
||
bytepos = -1;
|
||
}
|
||
if (cmp_it->reversed_p)
|
||
endpos = -1;
|
||
composition_compute_stop_pos (cmp_it, charpos, bytepos, endpos, string, true);
|
||
return 0;
|
||
}
|
||
|
||
/* Update charpos, nchars, nbytes, and width of the current grapheme
|
||
cluster.
|
||
|
||
If the composition is static or automatic in L2R context, the
|
||
cluster is identified by CMP_IT->from, and CHARPOS is the position
|
||
of the first character of the cluster. In this case, update
|
||
CMP_IT->to too.
|
||
|
||
If the composition is automatic in R2L context, the cluster is
|
||
identified by CMP_IT->to, and CHARPOS is the position of the last
|
||
character of the cluster. In this case, update CMP_IT->from too.
|
||
|
||
The return value is the character code of the first character of
|
||
the cluster, or -1 if the composition is somehow broken. */
|
||
|
||
int
|
||
composition_update_it (struct composition_it *cmp_it, ptrdiff_t charpos, ptrdiff_t bytepos, Lisp_Object string)
|
||
{
|
||
int i;
|
||
int c UNINIT;
|
||
|
||
if (cmp_it->ch < 0)
|
||
{
|
||
/* static composition */
|
||
struct composition *cmp = composition_table[cmp_it->id];
|
||
|
||
cmp_it->charpos = charpos;
|
||
cmp_it->to = cmp_it->nglyphs;
|
||
if (cmp_it->nglyphs == 0)
|
||
c = -1;
|
||
else
|
||
{
|
||
for (i = 0; i < cmp->glyph_len; i++)
|
||
/* TAB in a composition means display glyphs with padding
|
||
space on the left or right. */
|
||
if ((c = COMPOSITION_GLYPH (cmp, i)) != '\t')
|
||
break;
|
||
if (c == '\t')
|
||
c = ' ';
|
||
}
|
||
cmp_it->width = cmp->width;
|
||
charpos += cmp_it->nchars;
|
||
if (STRINGP (string))
|
||
cmp_it->nbytes = string_char_to_byte (string, charpos) - bytepos;
|
||
else
|
||
cmp_it->nbytes = CHAR_TO_BYTE (charpos) - bytepos;
|
||
}
|
||
else
|
||
{
|
||
/* Automatic composition. */
|
||
Lisp_Object gstring = composition_gstring_from_id (cmp_it->id);
|
||
Lisp_Object glyph;
|
||
ptrdiff_t from;
|
||
|
||
if (cmp_it->nglyphs == 0)
|
||
{
|
||
cmp_it->nchars = LGSTRING_CHAR_LEN (gstring);
|
||
cmp_it->width = 0;
|
||
cmp_it->from = cmp_it->to = 0;
|
||
return -1;
|
||
}
|
||
if (! cmp_it->reversed_p)
|
||
{
|
||
glyph = LGSTRING_GLYPH (gstring, cmp_it->from);
|
||
from = LGLYPH_FROM (glyph);
|
||
for (cmp_it->to = cmp_it->from + 1; cmp_it->to < cmp_it->nglyphs;
|
||
cmp_it->to++)
|
||
{
|
||
glyph = LGSTRING_GLYPH (gstring, cmp_it->to);
|
||
if (LGLYPH_FROM (glyph) != from)
|
||
break;
|
||
}
|
||
cmp_it->charpos = charpos;
|
||
}
|
||
else
|
||
{
|
||
glyph = LGSTRING_GLYPH (gstring, cmp_it->to - 1);
|
||
from = LGLYPH_FROM (glyph);
|
||
cmp_it->charpos = charpos - (LGLYPH_TO (glyph) - from);
|
||
for (cmp_it->from = cmp_it->to - 1; cmp_it->from > 0;
|
||
cmp_it->from--)
|
||
{
|
||
glyph = LGSTRING_GLYPH (gstring, cmp_it->from - 1);
|
||
if (LGLYPH_FROM (glyph) != from)
|
||
break;
|
||
}
|
||
}
|
||
glyph = LGSTRING_GLYPH (gstring, cmp_it->from);
|
||
cmp_it->nchars = LGLYPH_TO (glyph) + 1 - from;
|
||
cmp_it->nbytes = 0;
|
||
cmp_it->width = 0;
|
||
for (i = cmp_it->nchars - 1; i >= 0; i--)
|
||
{
|
||
c = XFIXNUM (LGSTRING_CHAR (gstring, from + i));
|
||
cmp_it->nbytes += CHAR_BYTES (c);
|
||
cmp_it->width += CHARACTER_WIDTH (c);
|
||
}
|
||
}
|
||
return c;
|
||
}
|
||
|
||
|
||
struct position_record
|
||
{
|
||
ptrdiff_t pos, pos_byte;
|
||
unsigned char *p;
|
||
};
|
||
|
||
/* Update the members of POSITION to the next character boundary. */
|
||
#define FORWARD_CHAR(POSITION, STOP) \
|
||
do { \
|
||
(POSITION).pos++; \
|
||
if ((POSITION).pos == (STOP)) \
|
||
{ \
|
||
(POSITION).p = GAP_END_ADDR; \
|
||
(POSITION).pos_byte = GPT_BYTE; \
|
||
} \
|
||
else \
|
||
{ \
|
||
(POSITION).pos_byte += BYTES_BY_CHAR_HEAD (*((POSITION).p)); \
|
||
(POSITION).p += BYTES_BY_CHAR_HEAD (*((POSITION).p)); \
|
||
} \
|
||
} while (0)
|
||
|
||
/* Update the members of POSITION to the previous character boundary. */
|
||
#define BACKWARD_CHAR(POSITION, STOP) \
|
||
do { \
|
||
if ((POSITION).pos == (STOP)) \
|
||
(POSITION).p = GPT_ADDR; \
|
||
do { \
|
||
(POSITION).pos_byte--; \
|
||
(POSITION).p--; \
|
||
} while (! CHAR_HEAD_P (*((POSITION).p))); \
|
||
(POSITION).pos--; \
|
||
} while (0)
|
||
|
||
/* Similar to find_composition, but find an automatic composition instead.
|
||
|
||
This function looks for automatic composition at or near position
|
||
POS of STRING object, either a buffer or a Lisp string. If STRING
|
||
is nil, it defaults to the current buffer. It must be assured that
|
||
POS is not within a static composition. Also, the current buffer
|
||
must be displayed in some window, otherwise the function will
|
||
return FALSE.
|
||
|
||
If LIMIT is negative, and there's no composition that includes POS
|
||
(i.e. starts at or before POS and ends at or after POS), return
|
||
FALSE. In this case, the function is allowed to look from POS as
|
||
far back as BACKLIM, and as far forward as POS+1 plus
|
||
MAX_AUTO_COMPOSITION_LOOKBACK, the maximum number of look-back for
|
||
automatic compositions (3) -- this is a limitation imposed by
|
||
composition rules in composition-function-table, which see. If
|
||
BACKLIM is negative, it stands for the beginning of STRING object:
|
||
BEGV for a buffer or position zero for a string.
|
||
|
||
If LIMIT is positive, search for a composition forward (LIMIT >
|
||
POS) or backward (LIMIT < POS). In this case, LIMIT bounds the
|
||
search for the first character of a composed sequence.
|
||
(LIMIT == POS is the same as LIMIT < 0.) If LIMIT > POS, the
|
||
function can find a composition that starts after POS.
|
||
|
||
BACKLIM limits how far back is the function allowed to look in
|
||
STRING object while trying to find a position where it is safe to
|
||
start searching forward for compositions. Such a safe place is
|
||
generally the position after a character that can never be
|
||
composed.
|
||
|
||
If BACKLIM is negative, that means the first character position of
|
||
STRING object; this is useful when calling the function for the
|
||
first time for a given buffer or string, since it is possible that
|
||
a composition begins before POS. However, if POS is very far from
|
||
the beginning of STRING object, a negative value of BACKLIM could
|
||
make the function slow. For that reason, when STRING is a buffer
|
||
or nil, we restrict the search back to the first newline before
|
||
POS. Also, in this case the function may return START and END that
|
||
do not include POS, something that is not necessarily wanted, and
|
||
needs to be explicitly checked by the caller.
|
||
|
||
When calling the function in a loop for the same buffer/string, the
|
||
caller should generally set BACKLIM equal to POS, to avoid costly
|
||
repeated searches backward. This is because if the previous
|
||
positions were already checked for compositions, there should be no
|
||
reason to re-check them.
|
||
|
||
If BACKLIM is positive, it must be less or equal to LIMIT.
|
||
|
||
If an automatic composition satisfying the above conditions is
|
||
found, set *GSTRING to the Lispy glyph-string representing the
|
||
composition, set *START and *END to the start and end of the
|
||
composed sequence, and return TRUE. Otherwise, set *GSTRING to
|
||
nil, and return FALSE. */
|
||
|
||
bool
|
||
find_automatic_composition (ptrdiff_t pos, ptrdiff_t limit, ptrdiff_t backlim,
|
||
ptrdiff_t *start, ptrdiff_t *end,
|
||
Lisp_Object *gstring, Lisp_Object string)
|
||
{
|
||
ptrdiff_t head, tail, stop;
|
||
/* Forward limit position of checking a composition taking a
|
||
looking-back count into account. */
|
||
ptrdiff_t fore_check_limit;
|
||
struct position_record cur, prev;
|
||
int c;
|
||
Lisp_Object window;
|
||
struct window *w;
|
||
bool need_adjustment = 0;
|
||
|
||
window = Fget_buffer_window (Fcurrent_buffer (), Qnil);
|
||
if (NILP (window))
|
||
return 0;
|
||
w = XWINDOW (window);
|
||
|
||
cur.pos = pos;
|
||
if (NILP (string))
|
||
{
|
||
if (backlim < 0)
|
||
{
|
||
/* This assumes a newline can never be composed. */
|
||
head = find_newline (pos, -1, 0, -1, -1, NULL, NULL, false);
|
||
}
|
||
else
|
||
head = backlim;
|
||
if (current_buffer->long_line_optimizations_p)
|
||
{
|
||
/* In buffers with very long lines, this function becomes very
|
||
slow. Pretend that the buffer is narrowed to make it fast. */
|
||
ptrdiff_t begv = get_small_narrowing_begv (w, window_point (w));
|
||
if (pos > begv)
|
||
head = begv;
|
||
}
|
||
tail = ZV;
|
||
stop = GPT;
|
||
cur.pos_byte = CHAR_TO_BYTE (cur.pos);
|
||
cur.p = BYTE_POS_ADDR (cur.pos_byte);
|
||
}
|
||
else
|
||
{
|
||
head = backlim < 0 ? 0 : backlim, tail = SCHARS (string), stop = -1;
|
||
cur.pos_byte = string_char_to_byte (string, cur.pos);
|
||
cur.p = SDATA (string) + cur.pos_byte;
|
||
}
|
||
if (limit < 0)
|
||
/* Finding a composition covering the character after POS is the
|
||
same as setting LIMIT to POS. */
|
||
limit = pos;
|
||
|
||
eassert (backlim < 0 || backlim <= limit);
|
||
|
||
if (limit <= pos)
|
||
fore_check_limit = min (tail, pos + 1 + MAX_AUTO_COMPOSITION_LOOKBACK);
|
||
else
|
||
fore_check_limit = min (tail, limit + MAX_AUTO_COMPOSITION_LOOKBACK);
|
||
|
||
/* Provided that we have these possible compositions now:
|
||
|
||
POS: 1 2 3 4 5 6 7 8 9
|
||
|-A-|
|
||
|-B-|-C-|--D--|
|
||
|
||
Here, it is known that characters after positions 1 and 9 can
|
||
never be composed (i.e. ! char_composable_p (CH)), and
|
||
composition A is an invalid one because it's partially covered by
|
||
the valid composition C. And to know whether a composition is
|
||
valid or not, the only way is to start searching forward from a
|
||
position that can not be a tail part of composition (it's 2 in
|
||
the above case).
|
||
|
||
Now we have these cases (1 through 4):
|
||
|
||
-- character after POS is ... --
|
||
not composable composable
|
||
LIMIT <= POS (1) (3)
|
||
POS < LIMIT (2) (4)
|
||
|
||
Among them, in case (2), we simply search forward from POS.
|
||
|
||
In the other cases, we at first rewind back to the position where
|
||
the previous character is not composable or the beginning of
|
||
buffer (string), then search compositions forward. In case (1)
|
||
and (3) we repeat this process until a composition is found. */
|
||
|
||
while (1)
|
||
{
|
||
c = STRING_CHAR (cur.p);
|
||
if (! char_composable_p (c))
|
||
{
|
||
if (limit <= pos) /* case (1) */
|
||
{
|
||
do {
|
||
if (cur.pos <= limit)
|
||
return 0;
|
||
BACKWARD_CHAR (cur, stop);
|
||
c = STRING_CHAR (cur.p);
|
||
} while (! char_composable_p (c));
|
||
fore_check_limit = cur.pos + 1;
|
||
}
|
||
else /* case (2) */
|
||
/* No need of rewinding back. */
|
||
goto search_forward;
|
||
}
|
||
|
||
/* Rewind back to the position where we can safely search
|
||
forward for compositions. It is assured that the character
|
||
at cur.pos is composable. */
|
||
while (head < cur.pos)
|
||
{
|
||
prev = cur;
|
||
BACKWARD_CHAR (cur, stop);
|
||
c = STRING_CHAR (cur.p);
|
||
if (! char_composable_p (c))
|
||
{
|
||
cur = prev;
|
||
break;
|
||
}
|
||
}
|
||
|
||
search_forward:
|
||
/* Now search forward. */
|
||
*gstring = Qnil;
|
||
prev = cur; /* remember the start of searching position. */
|
||
while (cur.pos < fore_check_limit)
|
||
{
|
||
Lisp_Object val;
|
||
|
||
c = STRING_CHAR (cur.p);
|
||
for (val = CHAR_TABLE_REF (Vcomposition_function_table, c);
|
||
CONSP (val); val = XCDR (val))
|
||
{
|
||
Lisp_Object elt = XCAR (val);
|
||
|
||
if (VECTORP (elt) && ASIZE (elt) == 3 && FIXNATP (AREF (elt, 1)))
|
||
{
|
||
EMACS_INT check_pos = cur.pos - XFIXNAT (AREF (elt, 1));
|
||
struct position_record check;
|
||
|
||
if (check_pos < head
|
||
|| (limit <= pos ? pos < check_pos
|
||
: limit <= check_pos))
|
||
continue;
|
||
for (check = cur; check_pos < check.pos; )
|
||
BACKWARD_CHAR (check, stop);
|
||
*gstring = autocmp_chars (elt, check.pos, check.pos_byte,
|
||
tail, w, NULL, string, Qnil, c);
|
||
need_adjustment = 1;
|
||
if (NILP (*gstring))
|
||
{
|
||
/* As we have called Lisp, there's a possibility
|
||
that buffer/string is relocated. */
|
||
if (NILP (string))
|
||
cur.p = BYTE_POS_ADDR (cur.pos_byte);
|
||
else
|
||
cur.p = SDATA (string) + cur.pos_byte;
|
||
}
|
||
else
|
||
{
|
||
/* We found a candidate of a target composition. */
|
||
*start = check.pos;
|
||
*end = check.pos + LGSTRING_CHAR_LEN (*gstring);
|
||
if (pos < limit
|
||
? pos < *end
|
||
: *start <= pos && pos < *end)
|
||
/* This is the target composition. */
|
||
return 1;
|
||
cur.pos = *end;
|
||
if (NILP (string))
|
||
{
|
||
cur.pos_byte = CHAR_TO_BYTE (cur.pos);
|
||
cur.p = BYTE_POS_ADDR (cur.pos_byte);
|
||
}
|
||
else
|
||
{
|
||
cur.pos_byte = string_char_to_byte (string, cur.pos);
|
||
cur.p = SDATA (string) + cur.pos_byte;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (! CONSP (val))
|
||
/* We found no composition here. */
|
||
FORWARD_CHAR (cur, stop);
|
||
}
|
||
|
||
if (pos < limit) /* case (2) and (4)*/
|
||
return 0;
|
||
if (! NILP (*gstring))
|
||
return 1;
|
||
if (prev.pos == head)
|
||
return 0;
|
||
cur = prev;
|
||
if (need_adjustment)
|
||
{
|
||
if (NILP (string))
|
||
cur.p = BYTE_POS_ADDR (cur.pos_byte);
|
||
else
|
||
cur.p = SDATA (string) + cur.pos_byte;
|
||
}
|
||
BACKWARD_CHAR (cur, stop);
|
||
}
|
||
}
|
||
|
||
/* Return the adjusted point provided that point is moved from LAST_PT
|
||
to NEW_PT. */
|
||
|
||
ptrdiff_t
|
||
composition_adjust_point (ptrdiff_t last_pt, ptrdiff_t new_pt)
|
||
{
|
||
ptrdiff_t i, beg, end;
|
||
Lisp_Object val;
|
||
|
||
if (new_pt == BEGV || new_pt == ZV)
|
||
return new_pt;
|
||
|
||
/* At first check the static composition. */
|
||
if (get_property_and_range (new_pt, Qcomposition, &val, &beg, &end, Qnil)
|
||
&& composition_valid_p (beg, end, val))
|
||
{
|
||
if (beg < new_pt /* && end > new_pt <- It's always the case. */
|
||
&& (last_pt <= beg || last_pt >= end))
|
||
return (new_pt < last_pt ? beg : end);
|
||
return new_pt;
|
||
}
|
||
|
||
if (NILP (BVAR (current_buffer, enable_multibyte_characters))
|
||
|| inhibit_auto_composition ())
|
||
return new_pt;
|
||
|
||
/* Next check the automatic composition. */
|
||
if (! find_automatic_composition (new_pt, (ptrdiff_t) -1, (ptrdiff_t) -1,
|
||
&beg, &end, &val, Qnil)
|
||
|| beg == new_pt)
|
||
return new_pt;
|
||
for (i = 0; i < LGSTRING_GLYPH_LEN (val); i++)
|
||
{
|
||
Lisp_Object glyph = LGSTRING_GLYPH (val, i);
|
||
|
||
if (NILP (glyph))
|
||
break;
|
||
if (beg + LGLYPH_FROM (glyph) == new_pt)
|
||
return new_pt;
|
||
if (beg + LGLYPH_TO (glyph) >= new_pt)
|
||
return (new_pt < last_pt
|
||
? beg + LGLYPH_FROM (glyph)
|
||
: beg + LGLYPH_TO (glyph) + 1);
|
||
}
|
||
return new_pt;
|
||
}
|
||
|
||
DEFUN ("composition-get-gstring", Fcomposition_get_gstring,
|
||
Scomposition_get_gstring, 4, 4, 0,
|
||
doc: /* Return a glyph-string for characters between FROM and TO.
|
||
If the glyph string is for graphic display, FONT-OBJECT must be
|
||
a font-object to use for those characters.
|
||
Otherwise (for terminal display), FONT-OBJECT must be a terminal ID, a
|
||
frame, or nil for the selected frame's terminal device.
|
||
|
||
If the optional 4th argument STRING is not nil, it is a string
|
||
containing the target characters between indices FROM and TO,
|
||
which are treated as in `substring'. Otherwise FROM and TO are
|
||
character positions in current buffer; they can be in either order,
|
||
and can be integers or markers.
|
||
|
||
A glyph-string is a vector containing information about how to display
|
||
a specific character sequence. The format is:
|
||
[HEADER ID GLYPH ...]
|
||
|
||
HEADER is a vector of this form:
|
||
[FONT-OBJECT CHAR ...]
|
||
where
|
||
FONT-OBJECT is a font-object for all glyphs in the glyph-string,
|
||
or the terminal coding system of the specified terminal.
|
||
CHARs are characters to be composed by GLYPHs.
|
||
|
||
ID is an identification number of the glyph-string. It may be nil if
|
||
not yet shaped.
|
||
|
||
GLYPH is a vector whose elements have this form:
|
||
[ FROM-IDX TO-IDX C CODE WIDTH LBEARING RBEARING ASCENT DESCENT
|
||
[ [X-OFF Y-OFF WADJUST] | nil] ]
|
||
where
|
||
FROM-IDX and TO-IDX are used internally and should not be touched.
|
||
C is the character of the glyph.
|
||
CODE is the glyph-code of C in FONT-OBJECT.
|
||
WIDTH thru DESCENT are the metrics (in pixels) of the glyph.
|
||
X-OFF and Y-OFF are offsets to the base position for the glyph.
|
||
WADJUST is the adjustment to the normal width of the glyph.
|
||
|
||
If GLYPH is nil, the remaining elements of the glyph-string vector
|
||
should be ignored. */)
|
||
(Lisp_Object from, Lisp_Object to, Lisp_Object font_object, Lisp_Object string)
|
||
{
|
||
Lisp_Object gstring, header;
|
||
ptrdiff_t frompos, frombyte, topos;
|
||
|
||
if (! FONT_OBJECT_P (font_object))
|
||
{
|
||
struct coding_system *coding;
|
||
struct terminal *terminal = decode_live_terminal (font_object);
|
||
|
||
coding = ((TERMINAL_TERMINAL_CODING (terminal)->common_flags
|
||
& CODING_REQUIRE_ENCODING_MASK)
|
||
? TERMINAL_TERMINAL_CODING (terminal) : &safe_terminal_coding);
|
||
font_object = CODING_ID_NAME (coding->id);
|
||
}
|
||
|
||
if (NILP (string))
|
||
{
|
||
if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
|
||
error ("Attempt to shape unibyte text");
|
||
validate_region (&from, &to);
|
||
frompos = XFIXNAT (from);
|
||
topos = XFIXNAT (to);
|
||
frombyte = CHAR_TO_BYTE (frompos);
|
||
}
|
||
else
|
||
{
|
||
CHECK_STRING (string);
|
||
ptrdiff_t chars = SCHARS (string);
|
||
validate_subarray (string, from, to, chars, &frompos, &topos);
|
||
if (! STRING_MULTIBYTE (string))
|
||
{
|
||
ptrdiff_t i;
|
||
|
||
for (i = SBYTES (string) - 1; i >= 0; i--)
|
||
if (!ASCII_CHAR_P (SREF (string, i)))
|
||
error ("Attempt to shape unibyte text");
|
||
/* STRING is a pure-ASCII string, so we can convert it (or,
|
||
rather, its copy) to multibyte and use that thereafter. */
|
||
/* FIXME: Not clear why we need to do that: AFAICT the rest of
|
||
the code should work on an ASCII-only unibyte string just
|
||
as well (bug#56347). */
|
||
string = make_multibyte_string (SSDATA (string), chars, chars);
|
||
}
|
||
frombyte = string_char_to_byte (string, frompos);
|
||
}
|
||
|
||
header = fill_gstring_header (frompos, frombyte,
|
||
topos, font_object, string);
|
||
gstring = composition_gstring_lookup_cache (header);
|
||
if (! NILP (gstring))
|
||
return gstring;
|
||
|
||
if (LGSTRING_GLYPH_LEN (gstring_work) < topos - frompos)
|
||
gstring_work = make_nil_vector (topos - frompos + 2);
|
||
LGSTRING_SET_HEADER (gstring_work, header);
|
||
LGSTRING_SET_ID (gstring_work, Qnil);
|
||
fill_gstring_body (gstring_work);
|
||
return gstring_work;
|
||
}
|
||
|
||
|
||
/* Emacs Lisp APIs. */
|
||
|
||
DEFUN ("compose-region-internal", Fcompose_region_internal,
|
||
Scompose_region_internal, 2, 4, 0,
|
||
doc: /* Internal use only.
|
||
|
||
Compose text in the region between START and END.
|
||
Optional 3rd and 4th arguments are COMPONENTS and MODIFICATION-FUNC
|
||
for the composition. See `compose-region' for more details. */)
|
||
(Lisp_Object start, Lisp_Object end, Lisp_Object components, Lisp_Object modification_func)
|
||
{
|
||
validate_region (&start, &end);
|
||
if (!NILP (components)
|
||
&& !FIXNUMP (components)
|
||
&& !CONSP (components)
|
||
&& !STRINGP (components))
|
||
CHECK_VECTOR (components);
|
||
|
||
compose_text (XFIXNUM (start), XFIXNUM (end), components, modification_func, Qnil);
|
||
return Qnil;
|
||
}
|
||
|
||
DEFUN ("compose-string-internal", Fcompose_string_internal,
|
||
Scompose_string_internal, 3, 5, 0,
|
||
doc: /* Internal use only.
|
||
|
||
Compose text between indices START and END of STRING, where
|
||
START and END are treated as in `substring'. Optional 4th
|
||
and 5th arguments are COMPONENTS and MODIFICATION-FUNC
|
||
for the composition. See `compose-string' for more details. */)
|
||
(Lisp_Object string, Lisp_Object start, Lisp_Object end,
|
||
Lisp_Object components, Lisp_Object modification_func)
|
||
{
|
||
ptrdiff_t from, to;
|
||
|
||
CHECK_STRING (string);
|
||
validate_subarray (string, start, end, SCHARS (string), &from, &to);
|
||
compose_text (from, to, components, modification_func, string);
|
||
return string;
|
||
}
|
||
|
||
DEFUN ("find-composition-internal", Ffind_composition_internal,
|
||
Sfind_composition_internal, 4, 4, 0,
|
||
doc: /* Internal use only.
|
||
|
||
Return information about composition at or nearest to position POS.
|
||
See `find-composition' for more details. */)
|
||
(Lisp_Object pos, Lisp_Object limit, Lisp_Object string, Lisp_Object detail_p)
|
||
{
|
||
Lisp_Object prop, tail, gstring;
|
||
ptrdiff_t start, end, from, to;
|
||
int id;
|
||
|
||
EMACS_INT fixed_pos = fix_position (pos);
|
||
if (!NILP (limit))
|
||
to = clip_to_bounds (PTRDIFF_MIN, fix_position (limit), ZV);
|
||
else
|
||
to = -1;
|
||
|
||
if (!NILP (string))
|
||
{
|
||
CHECK_STRING (string);
|
||
if (! (0 <= fixed_pos && fixed_pos <= SCHARS (string)))
|
||
args_out_of_range (string, pos);
|
||
}
|
||
else
|
||
{
|
||
if (! (BEGV <= fixed_pos && fixed_pos <= ZV))
|
||
args_out_of_range (Fcurrent_buffer (), pos);
|
||
}
|
||
from = fixed_pos;
|
||
|
||
if (!find_composition (from, to, &start, &end, &prop, string))
|
||
{
|
||
if (((NILP (string)
|
||
&& !NILP (BVAR (current_buffer, enable_multibyte_characters)))
|
||
|| (!NILP (string) && STRING_MULTIBYTE (string)))
|
||
&& ! inhibit_auto_composition ()
|
||
&& find_automatic_composition (from, to, (ptrdiff_t) -1,
|
||
&start, &end, &gstring, string))
|
||
return list3 (make_fixnum (start), make_fixnum (end), gstring);
|
||
return Qnil;
|
||
}
|
||
if (! (start <= fixed_pos && fixed_pos < end))
|
||
{
|
||
ptrdiff_t s, e;
|
||
|
||
if (find_automatic_composition (from, to, (ptrdiff_t) -1,
|
||
&s, &e, &gstring, string)
|
||
&& (e <= fixed_pos ? e > end : s < start))
|
||
return list3 (make_fixnum (s), make_fixnum (e), gstring);
|
||
}
|
||
if (!composition_valid_p (start, end, prop))
|
||
return list3 (make_fixnum (start), make_fixnum (end), Qnil);
|
||
if (NILP (detail_p))
|
||
return list3 (make_fixnum (start), make_fixnum (end), Qt);
|
||
|
||
if (composition_registered_p (prop))
|
||
id = COMPOSITION_ID (prop);
|
||
else
|
||
{
|
||
ptrdiff_t start_byte = (NILP (string)
|
||
? CHAR_TO_BYTE (start)
|
||
: string_char_to_byte (string, start));
|
||
id = get_composition_id (start, start_byte, end - start, prop, string);
|
||
}
|
||
|
||
if (id >= 0)
|
||
{
|
||
Lisp_Object components, relative_p, mod_func;
|
||
enum composition_method method = composition_method (prop);
|
||
int width = composition_table[id]->width;
|
||
|
||
components = Fcopy_sequence (COMPOSITION_COMPONENTS (prop));
|
||
relative_p = (method == COMPOSITION_WITH_RULE_ALTCHARS
|
||
? Qnil : Qt);
|
||
mod_func = COMPOSITION_MODIFICATION_FUNC (prop);
|
||
tail = list4 (components, relative_p, mod_func, make_fixnum (width));
|
||
}
|
||
else
|
||
tail = Qnil;
|
||
|
||
return Fcons (make_fixnum (start), Fcons (make_fixnum (end), tail));
|
||
}
|
||
|
||
static int
|
||
compare_composition_rules (const void *r1, const void *r2)
|
||
{
|
||
Lisp_Object vec1 = *(Lisp_Object *)r1, vec2 = *(Lisp_Object *)r2;
|
||
|
||
return XFIXNAT (AREF (vec2, 1)) - XFIXNAT (AREF (vec1, 1));
|
||
}
|
||
|
||
DEFUN ("composition-sort-rules", Fcomposition_sort_rules,
|
||
Scomposition_sort_rules, 1, 1, 0,
|
||
doc: /* Sort composition RULES by their LOOKBACK parameter.
|
||
|
||
If RULES include just one rule, return RULES.
|
||
Otherwise, return a new list of rules where all the rules are
|
||
arranged in decreasing order of the LOOKBACK parameter of the
|
||
rules (the second element of the rule's vector). This is required
|
||
when combining composition rules from different sources, because
|
||
of the way buffer text is examined for matching one of the rules. */)
|
||
(Lisp_Object rules)
|
||
{
|
||
ptrdiff_t nrules;
|
||
USE_SAFE_ALLOCA;
|
||
|
||
CHECK_LIST (rules);
|
||
nrules = list_length (rules);
|
||
if (nrules > 1)
|
||
{
|
||
ptrdiff_t i;
|
||
Lisp_Object *sortvec;
|
||
|
||
SAFE_NALLOCA (sortvec, 1, nrules);
|
||
for (i = 0; i < nrules; i++)
|
||
{
|
||
Lisp_Object elt = XCAR (rules);
|
||
if (VECTORP (elt) && ASIZE (elt) == 3 && FIXNATP (AREF (elt, 1)))
|
||
sortvec[i] = elt;
|
||
else
|
||
error ("Invalid composition rule in RULES argument");
|
||
rules = XCDR (rules);
|
||
}
|
||
qsort (sortvec, nrules, sizeof (Lisp_Object), compare_composition_rules);
|
||
rules = Flist (nrules, sortvec);
|
||
SAFE_FREE ();
|
||
}
|
||
|
||
return rules;
|
||
}
|
||
|
||
|
||
void
|
||
syms_of_composite (void)
|
||
{
|
||
int i;
|
||
|
||
DEFSYM (Qcomposition, "composition");
|
||
|
||
/* Make a hash table for static composition. */
|
||
/* We used to make the hash table weak so that unreferenced
|
||
compositions can be garbage-collected. But, usually once
|
||
created compositions are repeatedly used in an Emacs session,
|
||
and thus it's not worth to save memory in such a way. So, we
|
||
make the table not weak. */
|
||
Lisp_Object args[] = {QCtest, Qequal, QCsize, make_fixnum (311)};
|
||
composition_hash_table = CALLMANY (Fmake_hash_table, args);
|
||
staticpro (&composition_hash_table);
|
||
|
||
/* Make a hash table for glyph-string. */
|
||
gstring_hash_table = CALLMANY (Fmake_hash_table, args);
|
||
staticpro (&gstring_hash_table);
|
||
|
||
staticpro (&gstring_work_headers);
|
||
gstring_work_headers = make_nil_vector (8);
|
||
for (i = 0; i < 8; i++)
|
||
ASET (gstring_work_headers, i, make_nil_vector (i + 2));
|
||
staticpro (&gstring_work);
|
||
gstring_work = make_nil_vector (10);
|
||
|
||
/* Text property `composition' should be nonsticky by default. */
|
||
Vtext_property_default_nonsticky
|
||
= Fcons (Fcons (Qcomposition, Qt), Vtext_property_default_nonsticky);
|
||
|
||
DEFVAR_LISP ("compose-chars-after-function", Vcompose_chars_after_function,
|
||
doc: /* Function to adjust composition of buffer text.
|
||
|
||
This function is called with three arguments: FROM, TO, and OBJECT.
|
||
FROM and TO specify the range of text whose composition should be
|
||
adjusted. OBJECT, if non-nil, is a string that contains the text.
|
||
|
||
This function is called after a text with `composition' property is
|
||
inserted or deleted to keep `composition' property of buffer text
|
||
valid.
|
||
|
||
The default value is the function `compose-chars-after'. */);
|
||
Vcompose_chars_after_function = intern_c_string ("compose-chars-after");
|
||
|
||
DEFSYM (Qauto_composed, "auto-composed");
|
||
|
||
DEFVAR_LISP ("auto-composition-mode", Vauto_composition_mode,
|
||
doc: /* Non-nil if Auto-Composition mode is enabled.
|
||
Use the command `auto-composition-mode' to change this variable.
|
||
|
||
If this variable is a string, `auto-composition-mode' will be disabled in
|
||
buffers displayed on a terminal whose type, as reported by `tty-type',
|
||
compares equal to that string. */);
|
||
Vauto_composition_mode = Qt;
|
||
|
||
DEFVAR_LISP ("auto-composition-function", Vauto_composition_function,
|
||
doc: /* Function to call to compose characters automatically.
|
||
This function is called from the display engine with 6 arguments:
|
||
FUNC, FROM, TO, FONT-OBJECT, STRING, and DIRECTION.
|
||
|
||
FUNC is the function to compose characters. On text-mode display,
|
||
FUNC is ignored and `compose-gstring-for-terminal' is used instead.
|
||
|
||
If STRING is nil, the function must compose characters in the region
|
||
between FROM and TO in the current buffer.
|
||
|
||
Otherwise, STRING is a string, and FROM and TO are indices into the
|
||
string. In this case, the function must compose characters in the
|
||
string.
|
||
|
||
FONT-OBJECT is the font to use, or nil if characters are to be
|
||
composed on a text-mode display.
|
||
|
||
DIRECTION is the bidi directionality of the text to shape. It could
|
||
be L2R or R2L, or nil if unknown. */);
|
||
Vauto_composition_function = Qnil;
|
||
|
||
DEFVAR_LISP ("composition-function-table", Vcomposition_function_table,
|
||
doc: /* Char-table of functions for automatic character composition.
|
||
For each character that has to be composed automatically with
|
||
preceding and/or following characters, this char-table contains
|
||
a function to call to compose that character.
|
||
|
||
The element at index C in the table, if non-nil, is a list of
|
||
composition rules of the form ([PATTERN PREV-CHARS FUNC] ...);
|
||
the rules must be specified in the descending order of PREV-CHARS
|
||
values.
|
||
|
||
PATTERN is a regular expression which C and the surrounding
|
||
characters must match.
|
||
|
||
PREV-CHARS is a non-negative integer (less than 4) specifying how many
|
||
characters before C to check the matching with PATTERN. If it is 0,
|
||
PATTERN must match C and the following characters. If it is 1,
|
||
PATTERN must match a character before C and the following characters.
|
||
|
||
If PREV-CHARS is 0, PATTERN can be nil, which means that the
|
||
single character C should be composed.
|
||
|
||
FUNC is a function to return a glyph-string representing a
|
||
composition of the characters that match PATTERN. It is
|
||
called with one argument GSTRING.
|
||
|
||
GSTRING is a template of a glyph-string to return. It is already
|
||
filled with a proper header for the characters to compose, and
|
||
glyphs corresponding to those characters one by one. The
|
||
function must return a new glyph-string with the same header as
|
||
GSTRING, or modify GSTRING itself and return it.
|
||
|
||
See also the documentation of `auto-composition-mode'. */);
|
||
Vcomposition_function_table = Fmake_char_table (Qnil, Qnil);
|
||
|
||
DEFVAR_LISP ("auto-composition-emoji-eligible-codepoints", Vauto_composition_emoji_eligible_codepoints,
|
||
doc: /* List of codepoints for which auto-composition will check for an emoji font.
|
||
|
||
These are codepoints which have Emoji_Presentation = No, and thus by
|
||
default are not displayed as emoji. In certain circumstances, such as
|
||
when followed by U+FE0F (VS-16) the emoji font should be used for
|
||
them anyway.
|
||
|
||
This list is auto-generated, you should not need to modify it. */);
|
||
Vauto_composition_emoji_eligible_codepoints = Qnil;
|
||
|
||
defsubr (&Scompose_region_internal);
|
||
defsubr (&Scompose_string_internal);
|
||
defsubr (&Sfind_composition_internal);
|
||
defsubr (&Scomposition_get_gstring);
|
||
defsubr (&Sclear_composition_cache);
|
||
defsubr (&Scomposition_sort_rules);
|
||
}
|