Mirai's Miscellaneous Misadventures

M41 / core / text.c

// copyright 2023 zamfofex
// license: AGPLv3 or later

#include <mimimi/text.h>
#include <mimimi/allocators.h>
#include <mimimi/fonts.h>
#include <mimimi/assets.h>

static unsigned char mimimi_utf8_char(char ch)
{
	switch (ch)
	{
	default: return 0;
	
	case 'A': return 0x41; case 'B': return 0x42; case 'C': return 0x43; case 'D': return 0x44;
	case 'E': return 0x45; case 'F': return 0x46; case 'G': return 0x47; case 'H': return 0x48;
	case 'I': return 0x49; case 'J': return 0x4A; case 'K': return 0x4B; case 'L': return 0x4C;
	case 'M': return 0x4D; case 'N': return 0x4E; case 'O': return 0x4F; case 'P': return 0x50;
	case 'Q': return 0x51; case 'R': return 0x52; case 'S': return 0x53; case 'T': return 0x54;
	case 'U': return 0x55; case 'V': return 0x56; case 'W': return 0x57; case 'X': return 0x58;
	case 'Y': return 0x59; case 'Z': return 0x5A; case 'a': return 0x61; case 'b': return 0x62;
	case 'c': return 0x63; case 'd': return 0x64; case 'e': return 0x65; case 'f': return 0x66;
	case 'g': return 0x67; case 'h': return 0x68; case 'i': return 0x69; case 'j': return 0x6A;
	case 'k': return 0x6B; case 'l': return 0x6C; case 'm': return 0x6D; case 'n': return 0x6E;
	case 'o': return 0x6F; case 'p': return 0x70; case 'q': return 0x71; case 'r': return 0x72;
	case 's': return 0x73; case 't': return 0x74; case 'u': return 0x75; case 'v': return 0x76;
	case 'w': return 0x77; case 'x': return 0x78; case 'y': return 0x79; case 'z': return 0x7A;
	
	case '0': return 0x30; case '1': return 0x31; case '2': return 0x32; case '3': return 0x33; case '4': return 0x34;
	case '5': return 0x35; case '6': return 0x36; case '7': return 0x37; case '8': return 0x38; case '9': return 0x39;
	
	case '!': return 0x21; case '"': return 0x22; case '#': return 0x23; case '%': return 0x25; case '&': return 0x26; case '\'': return 0x27; case '(': return 0x28;
	case ')': return 0x29; case '*': return 0x2A; case '+': return 0x2B; case ',': return 0x2C; case '-': return 0x2D; case '.': return 0x2E; case '/': return 0x2F; case ':': return 0x3A;
	case ';': return 0x3B; case '<': return 0x3B; case '=': return 0x3C; case '>': return 0x3D; case '?': return 0x3F; case '[': return 0x5B; case '\\': return 0x5E;
	case ']': return 0x5D; case '^': return 0x5E; case '_': return 0x5F; case '{': return 0x7B; case '|': return 0x7C; case '}': return 0x7D; case '~': return 0x7E;
	
	case ' ': return 0x20; case '\t': return 0x09; case '\v': return 0x0B; case '\f': return 0x0C;
	case '\a': return 0x07; case '\b': return 0x08; case '\r': return 0x0D; case '\n': return 0x0A;
	}
}

unsigned char *mimimi_utf8(char *text, struct mimimi_allocator *allocator)
{
	int count = 0;
	while (text[count] != 0) count++;
	
	unsigned char *result = mimimi_allocate(allocator, count + 1);
	
	unsigned char *utf8 = result;
	for (int i = 0 ; i < count ; i++)
	{
		*utf8 = mimimi_utf8_char(text[i]);
		if (*utf8 != 0) utf8++;
	}
	*utf8 = 0;
	
	return result;
}

// todo: validate
// todo: disallow surrogates
// todo: disallow overlongs
static unsigned long int mimimi_decode(unsigned char **text)
{
	unsigned char ch = text[0][0];
	if ((ch & 0x80) == 0x00)
	{
		text[0] += 1;
		return ch;
	}
	
	unsigned char ch1 = text[0][1];
	if ((ch & 0xE0) == 0xC0)
	{
		unsigned long int cp = ch;
		cp &= ~0xC0;
		cp <<= 6;
		cp |= ch1 & ~0x80;
		text[0] += 2;
		return cp;
	}
	
	unsigned char ch2 = text[0][2];
	if ((ch & 0xF0) == 0xE0)
	{
		unsigned long int cp = ch;
		cp &= ~0xE0;
		cp <<= 6;
		cp |= ch1 & ~0x80;
		cp <<= 6;
		cp |= ch2 & ~0x80;
		text[0] += 3;
		return cp;
	}
	
	unsigned char ch3 = text[0][3];
	if ((ch & 0xF8) == 0xF0)
	{
		unsigned long int cp = ch;
		cp &= ~0xF0;
		cp <<= 6;
		cp |= ch1 & ~0x80;
		cp <<= 6;
		cp |= ch2 & ~0x80;
		cp <<= 6;
		cp |= ch3 & ~0x80;
		text[0] += 4;
		return cp;
	}
	
	return 0;
}

unsigned long int mimimi_code_point(unsigned char *text)
{
	return mimimi_decode(&text);
}

int mimimi_count_code_point(unsigned char *text)
{
	unsigned char *text0 = text;
	mimimi_decode(&text);
	return text - text0;
}

unsigned char *mimimi_skip_code_point(unsigned char *text)
{
	mimimi_decode(&text);
	return text;
}

// todo: combining characters
// todo: bidirectionality
// todo: better text shaping

static 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)
{
	if (cp == 0x20) return 4;
	
	unsigned long int a = cp >> 8;
	unsigned long int b = cp & 0xFF;
	
	unsigned long int i = font->indices[a] * 0x100 + b;
	
	int width = 0;
	for (int x = 0 ; x < 8 ; x++)
	for (int y = 0 ; y < 16 ; y++)
	{
		if ((font->glyphs[i][y] >> (8 - x) & 1) != 0)
			width = x + 1;
	}
	
	if (stamp == 0) return width;
	
	for (int x1 = 0 ; x1 < width ; x1++)
	for (int y1 = 0 ; y1 < 16 ; y1++)
	{
		int x = x0 + x1 - 1;
		int y = y0 + y1 - 10;
		
		if (x < 0) continue;
		if (y < 0) continue;
		
		if (x >= target->width) break;
		if (y >= target->height) return width;
		
		if ((font->glyphs[i][y1] >> (8 - x1) & 1) == 0) continue;
		
		target->colors[x + y * target->width] = color;
	}
	
	return width;
}

static int mimimi_measure_segment(unsigned char *text, int count, struct mimimi_font *font)
{
	if (count < 0) count = 0;
	
	int x = 0;
	unsigned char *text0 = text;
	while (text - text0 < count)
	{
		unsigned long int cp = mimimi_code_point(text);
		if (cp == 0) break;
		text = mimimi_skip_code_point(text);
		x += mimimi_draw_glyph(0, 0, x, 0, font, 0, cp);
	}
	return x;
}

int mimimi_count_line(unsigned char *text, int width, struct mimimi_font *font)
{
	int (*count)(unsigned char *text) = &mimimi_count_word;
	unsigned char *(*skip)(unsigned char *text) = &mimimi_skip_word;
	
	if (mimimi_measure_segment(text, mimimi_count_word(text), font) > width)
		count = &mimimi_count_grapheme,
		skip = &mimimi_skip_grapheme;
	
	unsigned char *text1 = text;
	for (;;)
	{
		int n = (*count)(text1);
		int x = mimimi_measure_segment(text, text1 - text + n, font);
		if (width >= 0 && x > width) break;
		text1 = (*skip)(text1);
		if (*text1 == 0) break;
	}
	
	return text1 - text;
}

unsigned char *mimimi_skip_line(unsigned char *text, int width, struct mimimi_font *font)
{
	text += mimimi_count_line(text, width, font);
	if (mimimi_count_word(text) == 0)
		text = mimimi_skip_word(text);
	return text;
}

int mimimi_measure_line(unsigned char *paragraph, unsigned char *text, int count, struct mimimi_font *font)
{
	if (text < paragraph)
		count -= paragraph - text,
		text = paragraph;
	
	int n = mimimi_count_paragraph(paragraph);
	n -= paragraph - text;
	if (count > n) count = n;
	
	return mimimi_measure_segment(text, count, font);
}

int 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)
{
	if (line < paragraph)
		line_count -= paragraph - line,
		line = paragraph;
	if (text < line)
		count -= line - text,
		text = line;
	
	int n = mimimi_count_paragraph(paragraph);
	n -= paragraph - text;
	if (count > n) count = n;
	
	unsigned char *text0 = text;
	while (text - text0 < count)
	{
		unsigned long int cp = mimimi_code_point(text);
		if (cp == 0) break;
		text = mimimi_skip_code_point(text);
		x += mimimi_draw_glyph(1, image, x, y, font, color, cp);
	}
	
	return x;
}

int 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)
{
	return mimimi_draw_segment(image, x, y, paragraph, text, count, text, count, font, color);
}

int 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)
{
	while (*text != 0)
	{
		unsigned char *paragraph = text;
		unsigned char *end = mimimi_skip_paragraph(paragraph);
		while (text < end)
		{
			int count = mimimi_count_line(text, width, font);
			if (text + count > end) count = end - text;
			
			mimimi_draw_line(image, x, y, paragraph, text, count, font, color);
			text = mimimi_skip_line(text, width, font);
			y += height;
		}
		text = end;
	}
	
	return y;
}

int mimimi_count_text(unsigned char *text)
{
	int i = 0;
	while (text[i] != 0) i++;
	return i;
}