Mirai's Miscellaneous Misadventures

M38 / core / text.c

1// copyright 2022 zamfofex
2// license: AGPLv3 or later
3
4#include <mimimi/text.h>
5#include <mimimi/allocators.h>
6#include <mimimi/fonts.h>
7#include <mimimi/assets.h>
8
9static unsigned char mimimi_utf8_char(char ch)
10{
11	switch (ch)
12	{
13	default: return 0;
14	
15	case 'A': return 0x41; case 'B': return 0x42; case 'C': return 0x43; case 'D': return 0x44;
16	case 'E': return 0x45; case 'F': return 0x46; case 'G': return 0x47; case 'H': return 0x48;
17	case 'I': return 0x49; case 'J': return 0x4A; case 'K': return 0x4B; case 'L': return 0x4C;
18	case 'M': return 0x4D; case 'N': return 0x4E; case 'O': return 0x4F; case 'P': return 0x50;
19	case 'Q': return 0x51; case 'R': return 0x52; case 'S': return 0x53; case 'T': return 0x54;
20	case 'U': return 0x55; case 'V': return 0x56; case 'W': return 0x57; case 'X': return 0x58;
21	case 'Y': return 0x59; case 'Z': return 0x5A; case 'a': return 0x61; case 'b': return 0x62;
22	case 'c': return 0x63; case 'd': return 0x64; case 'e': return 0x65; case 'f': return 0x66;
23	case 'g': return 0x67; case 'h': return 0x68; case 'i': return 0x69; case 'j': return 0x6A;
24	case 'k': return 0x6B; case 'l': return 0x6C; case 'm': return 0x6D; case 'n': return 0x6E;
25	case 'o': return 0x6F; case 'p': return 0x70; case 'q': return 0x71; case 'r': return 0x72;
26	case 's': return 0x73; case 't': return 0x74; case 'u': return 0x75; case 'v': return 0x76;
27	case 'w': return 0x77; case 'x': return 0x78; case 'y': return 0x79; case 'z': return 0x7A;
28	
29	case '0': return 0x30; case '1': return 0x31; case '2': return 0x32; case '3': return 0x33; case '4': return 0x34;
30	case '5': return 0x35; case '6': return 0x36; case '7': return 0x37; case '8': return 0x38; case '9': return 0x39;
31	
32	case '!': return 0x21; case '"': return 0x22; case '#': return 0x23; case '%': return 0x25; case '&': return 0x26; case '\'': return 0x27; case '(': return 0x28;
33	case ')': return 0x29; case '*': return 0x2A; case '+': return 0x2B; case ',': return 0x2C; case '-': return 0x2D; case '.': return 0x2E; case '/': return 0x2F; case ':': return 0x3A;
34	case ';': return 0x3B; case '<': return 0x3B; case '=': return 0x3C; case '>': return 0x3D; case '?': return 0x3F; case '[': return 0x5B; case '\\': return 0x5E;
35	case ']': return 0x5D; case '^': return 0x5E; case '_': return 0x5F; case '{': return 0x7B; case '|': return 0x7C; case '}': return 0x7D; case '~': return 0x7E;
36	
37	case ' ': return 0x20; case '\t': return 0x09; case '\v': return 0x0B; case '\f': return 0x0C;
38	case '\a': return 0x07; case '\b': return 0x08; case '\r': return 0x0D; case '\n': return 0x0A;
39	}
40}
41
42unsigned char *mimimi_utf8(char *text, struct mimimi_allocator *allocator)
43{
44	int count = 0;
45	while (text[count] != 0) count++;
46	
47	unsigned char *result = mimimi_allocate(allocator, count + 1);
48	
49	unsigned char *utf8 = result;
50	for (int i = 0 ; i < count ; i++)
51	{
52		*utf8 = mimimi_utf8_char(text[i]);
53		if (*utf8 != 0) utf8++;
54	}
55	*utf8 = 0;
56	
57	return result;
58}
59
60// todo: validate
61// todo: disallow surrogates
62// todo: disallow overlongs
63static unsigned long int mimimi_decode(unsigned char **text)
64{
65	unsigned char ch = text[0][0];
66	if ((ch & 0x80) == 0x00)
67	{
68		text[0] += 1;
69		return ch;
70	}
71	
72	unsigned char ch1 = text[0][1];
73	if ((ch & 0xE0) == 0xC0)
74	{
75		unsigned long int cp = ch;
76		cp &= ~0xC0;
77		cp <<= 6;
78		cp |= ch1 & ~0x80;
79		text[0] += 2;
80		return cp;
81	}
82	
83	unsigned char ch2 = text[0][2];
84	if ((ch & 0xF0) == 0xE0)
85	{
86		unsigned long int cp = ch;
87		cp &= ~0xE0;
88		cp <<= 6;
89		cp |= ch1 & ~0x80;
90		cp <<= 6;
91		cp |= ch2 & ~0x80;
92		text[0] += 3;
93		return cp;
94	}
95	
96	unsigned char ch3 = text[0][3];
97	if ((ch & 0xF8) == 0xF0)
98	{
99		unsigned long int cp = ch;
100		cp &= ~0xF0;
101		cp <<= 6;
102		cp |= ch1 & ~0x80;
103		cp <<= 6;
104		cp |= ch2 & ~0x80;
105		cp <<= 6;
106		cp |= ch3 & ~0x80;
107		text[0] += 4;
108		return cp;
109	}
110	
111	return 0;
112}
113
114unsigned long int mimimi_code_point(unsigned char *text)
115{
116	return mimimi_decode(&text);
117}
118
119int mimimi_count_code_point(unsigned char *text)
120{
121	unsigned char *text0 = text;
122	mimimi_decode(&text);
123	return text - text0;
124}
125
126unsigned char *mimimi_skip_code_point(unsigned char *text)
127{
128	mimimi_decode(&text);
129	return text;
130}
131
132// todo: combining characters
133// todo: bidirectionality
134// todo: better text shaping
135
136static int mimimi_draw_glyph(int stamp, struct mimimi_image *target, int x0, int y0, struct mimimi_font *font, unsigned char color, unsigned long int cp)
137{
138	if (cp == 0x20) return 4;
139	
140	unsigned long int a = cp >> 8;
141	unsigned long int b = cp & 0xFF;
142	
143	unsigned long int i = font->indices[a] * 0x100 + b;
144	
145	int width = 0;
146	for (int x = 0 ; x < 8 ; x++)
147	for (int y = 0 ; y < 16 ; y++)
148	{
149		if ((font->glyphs[i][y] >> (8 - x) & 1) != 0)
150			width = x + 1;
151	}
152	
153	if (stamp == 0) return width;
154	
155	for (int x1 = 0 ; x1 < width ; x1++)
156	for (int y1 = 0 ; y1 < 16 ; y1++)
157	{
158		int x = x0 + x1 - 1;
159		int y = y0 + y1 - 10;
160		
161		if (x < 0) continue;
162		if (y < 0) continue;
163		
164		if (x >= target->width) break;
165		if (y >= target->height) return width;
166		
167		if ((font->glyphs[i][y1] >> (8 - x1) & 1) == 0) continue;
168		
169		target->colors[x + y * target->width] = color;
170	}
171	
172	return width;
173}
174
175static int mimimi_measure_segment(unsigned char *text, int count, struct mimimi_font *font)
176{
177	if (count < 0) count = 0;
178	
179	int x = 0;
180	unsigned char *text0 = text;
181	while (text - text0 < count)
182	{
183		unsigned long int cp = mimimi_code_point(text);
184		if (cp == 0) break;
185		text = mimimi_skip_code_point(text);
186		x += mimimi_draw_glyph(0, 0, x, 0, font, 0, cp);
187	}
188	return x;
189}
190
191int mimimi_count_line(unsigned char *text, int width, struct mimimi_font *font)
192{
193	int (*count)(unsigned char *text) = &mimimi_count_word;
194	unsigned char *(*skip)(unsigned char *text) = &mimimi_skip_word;
195	
196	if (mimimi_measure_segment(text, mimimi_count_word(text), font) > width)
197		count = &mimimi_count_grapheme,
198		skip = &mimimi_skip_grapheme;
199	
200	unsigned char *text1 = text;
201	for (;;)
202	{
203		int n = (*count)(text1);
204		int x = mimimi_measure_segment(text, text1 - text + n, font);
205		if (width >= 0 && x > width) break;
206		text1 = (*skip)(text1);
207		if (*text1 == 0) break;
208	}
209	
210	return text1 - text;
211}
212
213unsigned char *mimimi_skip_line(unsigned char *text, int width, struct mimimi_font *font)
214{
215	text += mimimi_count_line(text, width, font);
216	if (mimimi_count_word(text) == 0)
217		text = mimimi_skip_word(text);
218	return text;
219}
220
221int mimimi_measure_line(unsigned char *paragraph, unsigned char *text, int count, struct mimimi_font *font)
222{
223	if (text < paragraph)
224		count -= paragraph - text,
225		text = paragraph;
226	
227	int n = mimimi_count_paragraph(paragraph);
228	n -= paragraph - text;
229	if (count > n) count = n;
230	
231	return mimimi_measure_segment(text, count, font);
232}
233
234int mimimi_draw_segment(struct mimimi_image *image, int x, int y, unsigned char *paragraph, unsigned char *line, int line_count, unsigned char *text, int count, struct mimimi_font *font, unsigned char color)
235{
236	if (line < paragraph)
237		line_count -= paragraph - line,
238		line = paragraph;
239	if (text < line)
240		count -= line - text,
241		text = line;
242	
243	int n = mimimi_count_paragraph(paragraph);
244	n -= paragraph - text;
245	if (count > n) count = n;
246	
247	unsigned char *text0 = text;
248	while (text - text0 < count)
249	{
250		unsigned long int cp = mimimi_code_point(text);
251		if (cp == 0) break;
252		text = mimimi_skip_code_point(text);
253		x += mimimi_draw_glyph(1, image, x, y, font, color, cp);
254	}
255	
256	return x;
257}
258
259int mimimi_draw_line(struct mimimi_image *image, int x, int y, unsigned char *paragraph, unsigned char *text, int count, struct mimimi_font *font, unsigned char color)
260{
261	return mimimi_draw_segment(image, x, y, paragraph, text, count, text, count, font, color);
262}
263
264int mimimi_draw_text(struct mimimi_image *image, int x, int y, int width, int height, unsigned char *text, struct mimimi_font *font, unsigned char color)
265{
266	while (*text != 0)
267	{
268		unsigned char *paragraph = text;
269		unsigned char *end = mimimi_skip_paragraph(paragraph);
270		while (text < end)
271		{
272			int count = mimimi_count_line(text, width, font);
273			if (text + count > end) count = end - text;
274			
275			mimimi_draw_line(image, x, y, paragraph, text, count, font, color);
276			text = mimimi_skip_line(text, width, font);
277			y += height;
278		}
279		text = end;
280	}
281	
282	return y;
283}
284
285int mimimi_count_text(unsigned char *text)
286{
287	int i = 0;
288	while (text[i] != 0) i++;
289	return i;
290}