xref: /linux/drivers/tty/vt/ucs.c (revision 378ec25aec5a8444879f8696d580c94950a1f1df)
107bc3f44SNicolas Pitre // SPDX-License-Identifier: GPL-2.0
207bc3f44SNicolas Pitre /*
307bc3f44SNicolas Pitre  * ucs.c - Universal Character Set processing
407bc3f44SNicolas Pitre  */
507bc3f44SNicolas Pitre 
607bc3f44SNicolas Pitre #include <linux/array_size.h>
707bc3f44SNicolas Pitre #include <linux/bsearch.h>
807bc3f44SNicolas Pitre #include <linux/consolemap.h>
907bc3f44SNicolas Pitre #include <linux/minmax.h>
1007bc3f44SNicolas Pitre 
11d8f81c82SNicolas Pitre struct ucs_interval16 {
12d8f81c82SNicolas Pitre 	u16 first;
13d8f81c82SNicolas Pitre 	u16 last;
14d8f81c82SNicolas Pitre };
15d8f81c82SNicolas Pitre 
16d8f81c82SNicolas Pitre struct ucs_interval32 {
1707bc3f44SNicolas Pitre 	u32 first;
1807bc3f44SNicolas Pitre 	u32 last;
1907bc3f44SNicolas Pitre };
2007bc3f44SNicolas Pitre 
2154cda920SNicolas Pitre #include "ucs_width_table.h"
2207bc3f44SNicolas Pitre 
interval16_cmp(const void * key,const void * element)23d8f81c82SNicolas Pitre static int interval16_cmp(const void *key, const void *element)
2407bc3f44SNicolas Pitre {
25d8f81c82SNicolas Pitre 	u16 cp = *(u16 *)key;
26d8f81c82SNicolas Pitre 	const struct ucs_interval16 *entry = element;
2707bc3f44SNicolas Pitre 
2807bc3f44SNicolas Pitre 	if (cp < entry->first)
2907bc3f44SNicolas Pitre 		return -1;
3007bc3f44SNicolas Pitre 	if (cp > entry->last)
3107bc3f44SNicolas Pitre 		return 1;
3207bc3f44SNicolas Pitre 	return 0;
3307bc3f44SNicolas Pitre }
3407bc3f44SNicolas Pitre 
interval32_cmp(const void * key,const void * element)35d8f81c82SNicolas Pitre static int interval32_cmp(const void *key, const void *element)
36d8f81c82SNicolas Pitre {
37d8f81c82SNicolas Pitre 	u32 cp = *(u32 *)key;
38d8f81c82SNicolas Pitre 	const struct ucs_interval32 *entry = element;
39d8f81c82SNicolas Pitre 
40d8f81c82SNicolas Pitre 	if (cp < entry->first)
41d8f81c82SNicolas Pitre 		return -1;
42d8f81c82SNicolas Pitre 	if (cp > entry->last)
43d8f81c82SNicolas Pitre 		return 1;
44d8f81c82SNicolas Pitre 	return 0;
45d8f81c82SNicolas Pitre }
46d8f81c82SNicolas Pitre 
cp_in_range16(u16 cp,const struct ucs_interval16 * ranges,size_t size)47d8f81c82SNicolas Pitre static bool cp_in_range16(u16 cp, const struct ucs_interval16 *ranges, size_t size)
4854cda920SNicolas Pitre {
49a16014c0SNicolas Pitre 	if (cp < ranges[0].first || cp > ranges[size - 1].last)
5054cda920SNicolas Pitre 		return false;
5154cda920SNicolas Pitre 
5254cda920SNicolas Pitre 	return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
53d8f81c82SNicolas Pitre 				interval16_cmp) != NULL;
5454cda920SNicolas Pitre }
5554cda920SNicolas Pitre 
cp_in_range32(u32 cp,const struct ucs_interval32 * ranges,size_t size)56d8f81c82SNicolas Pitre static bool cp_in_range32(u32 cp, const struct ucs_interval32 *ranges, size_t size)
57d8f81c82SNicolas Pitre {
58a16014c0SNicolas Pitre 	if (cp < ranges[0].first || cp > ranges[size - 1].last)
59d8f81c82SNicolas Pitre 		return false;
60d8f81c82SNicolas Pitre 
61d8f81c82SNicolas Pitre 	return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
62d8f81c82SNicolas Pitre 				interval32_cmp) != NULL;
63d8f81c82SNicolas Pitre }
64d8f81c82SNicolas Pitre 
65d8f81c82SNicolas Pitre #define UCS_IS_BMP(cp)	((cp) <= 0xffff)
66d8f81c82SNicolas Pitre 
6754cda920SNicolas Pitre /**
6854cda920SNicolas Pitre  * ucs_is_zero_width() - Determine if a Unicode code point is zero-width.
6954cda920SNicolas Pitre  * @cp: Unicode code point (UCS-4)
7054cda920SNicolas Pitre  *
7154cda920SNicolas Pitre  * Return: true if the character is zero-width, false otherwise
7254cda920SNicolas Pitre  */
ucs_is_zero_width(u32 cp)7354cda920SNicolas Pitre bool ucs_is_zero_width(u32 cp)
7454cda920SNicolas Pitre {
75d8f81c82SNicolas Pitre 	if (UCS_IS_BMP(cp))
76d8f81c82SNicolas Pitre 		return cp_in_range16(cp, ucs_zero_width_bmp_ranges,
77d8f81c82SNicolas Pitre 				     ARRAY_SIZE(ucs_zero_width_bmp_ranges));
78d8f81c82SNicolas Pitre 	else
79d8f81c82SNicolas Pitre 		return cp_in_range32(cp, ucs_zero_width_non_bmp_ranges,
80d8f81c82SNicolas Pitre 				     ARRAY_SIZE(ucs_zero_width_non_bmp_ranges));
8154cda920SNicolas Pitre }
8254cda920SNicolas Pitre 
8307bc3f44SNicolas Pitre /**
8407bc3f44SNicolas Pitre  * ucs_is_double_width() - Determine if a Unicode code point is double-width.
8507bc3f44SNicolas Pitre  * @cp: Unicode code point (UCS-4)
8607bc3f44SNicolas Pitre  *
8707bc3f44SNicolas Pitre  * Return: true if the character is double-width, false otherwise
8807bc3f44SNicolas Pitre  */
ucs_is_double_width(u32 cp)8907bc3f44SNicolas Pitre bool ucs_is_double_width(u32 cp)
9007bc3f44SNicolas Pitre {
91d8f81c82SNicolas Pitre 	if (UCS_IS_BMP(cp))
92d8f81c82SNicolas Pitre 		return cp_in_range16(cp, ucs_double_width_bmp_ranges,
93d8f81c82SNicolas Pitre 				     ARRAY_SIZE(ucs_double_width_bmp_ranges));
94d8f81c82SNicolas Pitre 	else
95d8f81c82SNicolas Pitre 		return cp_in_range32(cp, ucs_double_width_non_bmp_ranges,
96d8f81c82SNicolas Pitre 				     ARRAY_SIZE(ucs_double_width_non_bmp_ranges));
9707bc3f44SNicolas Pitre }
98b5c57499SNicolas Pitre 
99b5c57499SNicolas Pitre /*
100b5c57499SNicolas Pitre  * Structure for base with combining mark pairs and resulting recompositions.
101b5c57499SNicolas Pitre  * Using u16 to save space since all values are within BMP range.
102b5c57499SNicolas Pitre  */
103b5c57499SNicolas Pitre struct ucs_recomposition {
104b5c57499SNicolas Pitre 	u16 base;	/* base character */
105b5c57499SNicolas Pitre 	u16 mark;	/* combining mark */
106b5c57499SNicolas Pitre 	u16 recomposed;	/* corresponding recomposed character */
107b5c57499SNicolas Pitre };
108b5c57499SNicolas Pitre 
109b5c57499SNicolas Pitre #include "ucs_recompose_table.h"
110b5c57499SNicolas Pitre 
111b5c57499SNicolas Pitre struct compare_key {
112b5c57499SNicolas Pitre 	u16 base;
113b5c57499SNicolas Pitre 	u16 mark;
114b5c57499SNicolas Pitre };
115b5c57499SNicolas Pitre 
recomposition_cmp(const void * key,const void * element)116b5c57499SNicolas Pitre static int recomposition_cmp(const void *key, const void *element)
117b5c57499SNicolas Pitre {
118b5c57499SNicolas Pitre 	const struct compare_key *search_key = key;
119b5c57499SNicolas Pitre 	const struct ucs_recomposition *entry = element;
120b5c57499SNicolas Pitre 
121b5c57499SNicolas Pitre 	/* Compare base character first */
122b5c57499SNicolas Pitre 	if (search_key->base < entry->base)
123b5c57499SNicolas Pitre 		return -1;
124b5c57499SNicolas Pitre 	if (search_key->base > entry->base)
125b5c57499SNicolas Pitre 		return 1;
126b5c57499SNicolas Pitre 
127b5c57499SNicolas Pitre 	/* Base characters match, now compare combining character */
128b5c57499SNicolas Pitre 	if (search_key->mark < entry->mark)
129b5c57499SNicolas Pitre 		return -1;
130b5c57499SNicolas Pitre 	if (search_key->mark > entry->mark)
131b5c57499SNicolas Pitre 		return 1;
132b5c57499SNicolas Pitre 
133b5c57499SNicolas Pitre 	/* Both match */
134b5c57499SNicolas Pitre 	return 0;
135b5c57499SNicolas Pitre }
136b5c57499SNicolas Pitre 
137b5c57499SNicolas Pitre /**
138b5c57499SNicolas Pitre  * ucs_recompose() - Attempt to recompose two Unicode characters into a single character.
139b5c57499SNicolas Pitre  * @base: Base Unicode code point (UCS-4)
140b5c57499SNicolas Pitre  * @mark: Combining mark Unicode code point (UCS-4)
141b5c57499SNicolas Pitre  *
142b5c57499SNicolas Pitre  * Return: Recomposed Unicode code point, or 0 if no recomposition is possible
143b5c57499SNicolas Pitre  */
ucs_recompose(u32 base,u32 mark)144b5c57499SNicolas Pitre u32 ucs_recompose(u32 base, u32 mark)
145b5c57499SNicolas Pitre {
146b5c57499SNicolas Pitre 	/* Check if characters are within the range of our table */
147a16014c0SNicolas Pitre 	if (base < UCS_RECOMPOSE_MIN_BASE || base > UCS_RECOMPOSE_MAX_BASE ||
148a16014c0SNicolas Pitre 	    mark < UCS_RECOMPOSE_MIN_MARK || mark > UCS_RECOMPOSE_MAX_MARK)
149b5c57499SNicolas Pitre 		return 0;
150b5c57499SNicolas Pitre 
151b5c57499SNicolas Pitre 	struct compare_key key = { base, mark };
152b5c57499SNicolas Pitre 	struct ucs_recomposition *result =
153b5c57499SNicolas Pitre 		__inline_bsearch(&key, ucs_recomposition_table,
154b5c57499SNicolas Pitre 				 ARRAY_SIZE(ucs_recomposition_table),
155b5c57499SNicolas Pitre 				 sizeof(*ucs_recomposition_table),
156b5c57499SNicolas Pitre 				 recomposition_cmp);
157b5c57499SNicolas Pitre 
158b5c57499SNicolas Pitre 	return result ? result->recomposed : 0;
159b5c57499SNicolas Pitre }
160fe26933cSNicolas Pitre 
161fe26933cSNicolas Pitre /*
162fe26933cSNicolas Pitre  * The fallback table structures implement a 2-level lookup.
163fe26933cSNicolas Pitre  */
164fe26933cSNicolas Pitre 
165fe26933cSNicolas Pitre struct ucs_page_desc {
166fe26933cSNicolas Pitre 	u8 page;	/* Page index (high byte of code points) */
167fe26933cSNicolas Pitre 	u8 count;	/* Number of entries in this page */
168fe26933cSNicolas Pitre 	u16 start;	/* Start index in entries array */
169fe26933cSNicolas Pitre };
170fe26933cSNicolas Pitre 
171fe26933cSNicolas Pitre struct ucs_page_entry {
172fe26933cSNicolas Pitre 	u8 offset;	/* Offset within page (0-255) */
173fe26933cSNicolas Pitre 	u8 fallback;	/* Fallback character or range start marker */
174fe26933cSNicolas Pitre };
175fe26933cSNicolas Pitre 
176fe26933cSNicolas Pitre #include "ucs_fallback_table.h"
177fe26933cSNicolas Pitre 
ucs_page_desc_cmp(const void * key,const void * element)178fe26933cSNicolas Pitre static int ucs_page_desc_cmp(const void *key, const void *element)
179fe26933cSNicolas Pitre {
180fe26933cSNicolas Pitre 	u8 page = *(u8 *)key;
181fe26933cSNicolas Pitre 	const struct ucs_page_desc *entry = element;
182fe26933cSNicolas Pitre 
183fe26933cSNicolas Pitre 	if (page < entry->page)
184fe26933cSNicolas Pitre 		return -1;
185fe26933cSNicolas Pitre 	if (page > entry->page)
186fe26933cSNicolas Pitre 		return 1;
187fe26933cSNicolas Pitre 	return 0;
188fe26933cSNicolas Pitre }
189fe26933cSNicolas Pitre 
ucs_page_entry_cmp(const void * key,const void * element)190fe26933cSNicolas Pitre static int ucs_page_entry_cmp(const void *key, const void *element)
191fe26933cSNicolas Pitre {
192fe26933cSNicolas Pitre 	u8 offset = *(u8 *)key;
193fe26933cSNicolas Pitre 	const struct ucs_page_entry *entry = element;
194fe26933cSNicolas Pitre 
195fe26933cSNicolas Pitre 	if (offset < entry->offset)
196fe26933cSNicolas Pitre 		return -1;
197fe26933cSNicolas Pitre 	if (entry->fallback == UCS_PAGE_ENTRY_RANGE_MARKER) {
198fe26933cSNicolas Pitre 		if (offset > entry[1].offset)
199fe26933cSNicolas Pitre 			return 1;
200fe26933cSNicolas Pitre 	} else {
201fe26933cSNicolas Pitre 		if (offset > entry->offset)
202fe26933cSNicolas Pitre 			return 1;
203fe26933cSNicolas Pitre 	}
204fe26933cSNicolas Pitre 	return 0;
205fe26933cSNicolas Pitre }
206fe26933cSNicolas Pitre 
207fe26933cSNicolas Pitre /**
208fe26933cSNicolas Pitre  * ucs_get_fallback() - Get a substitution for the provided Unicode character
209fe26933cSNicolas Pitre  * @base: Base Unicode code point (UCS-4)
210fe26933cSNicolas Pitre  *
211fe26933cSNicolas Pitre  * Get a simpler fallback character for the provided Unicode character.
212fe26933cSNicolas Pitre  * This is used for terminal display when corresponding glyph is unavailable.
213fe26933cSNicolas Pitre  * The substitution may not be as good as the actual glyph for the original
214fe26933cSNicolas Pitre  * character but still way more helpful than a squared question mark.
215fe26933cSNicolas Pitre  *
216fe26933cSNicolas Pitre  * Return: Fallback Unicode code point, or 0 if none is available
217fe26933cSNicolas Pitre  */
ucs_get_fallback(u32 cp)218fe26933cSNicolas Pitre u32 ucs_get_fallback(u32 cp)
219fe26933cSNicolas Pitre {
220fe26933cSNicolas Pitre 	const struct ucs_page_desc *page;
221fe26933cSNicolas Pitre 	const struct ucs_page_entry *entry;
222fe26933cSNicolas Pitre 	u8 page_idx = cp >> 8, offset = cp;
223fe26933cSNicolas Pitre 
224fe26933cSNicolas Pitre 	if (!UCS_IS_BMP(cp))
225fe26933cSNicolas Pitre 		return 0;
226fe26933cSNicolas Pitre 
227*63f0d28dSNicolas Pitre 	/*
228*63f0d28dSNicolas Pitre 	 * Full-width to ASCII mapping (covering all printable ASCII 33-126)
229*63f0d28dSNicolas Pitre 	 * 0xFF01 (!) to 0xFF5E (~) -> ASCII 33 (!) to 126 (~)
230*63f0d28dSNicolas Pitre 	 * We process them programmatically to reduce the table size.
231*63f0d28dSNicolas Pitre 	 */
232*63f0d28dSNicolas Pitre 	if (cp >= 0xFF01 && cp <= 0xFF5E)
233*63f0d28dSNicolas Pitre 		return cp - 0xFF01 + 33;
234*63f0d28dSNicolas Pitre 
235fe26933cSNicolas Pitre 	page = __inline_bsearch(&page_idx, ucs_fallback_pages,
236fe26933cSNicolas Pitre 				ARRAY_SIZE(ucs_fallback_pages),
237fe26933cSNicolas Pitre 				sizeof(*ucs_fallback_pages),
238fe26933cSNicolas Pitre 				ucs_page_desc_cmp);
239fe26933cSNicolas Pitre 	if (!page)
240fe26933cSNicolas Pitre 		return 0;
241fe26933cSNicolas Pitre 
242fe26933cSNicolas Pitre 	entry = __inline_bsearch(&offset, ucs_fallback_entries + page->start,
243fe26933cSNicolas Pitre 				 page->count, sizeof(*ucs_fallback_entries),
244fe26933cSNicolas Pitre 				 ucs_page_entry_cmp);
245fe26933cSNicolas Pitre 	if (!entry)
246fe26933cSNicolas Pitre 		return 0;
247fe26933cSNicolas Pitre 
248fe26933cSNicolas Pitre 	if (entry->fallback == UCS_PAGE_ENTRY_RANGE_MARKER)
249fe26933cSNicolas Pitre 		entry++;
250fe26933cSNicolas Pitre 	return entry->fallback;
251fe26933cSNicolas Pitre }
252