/* * utf-8文字列を描画した際の幅を取得する */ int get_utf8_width(const char *mbs) { uint32_t c; int mblen, w; /* 1文字ずつ描画する */ w = 0; c = 0; /* warning avoidance on gcc 5.3.1 */ while (*mbs != '\0') { /* 文字を取得する */ mblen = utf8_to_utf32(mbs, &c); if (mblen == -1) return -1; /* 幅を取得する */ w += get_glyph_width(c); /* 次の文字へ移動する */ mbs += mblen; } return w; }
wraptext_t* word_wrap_text(const font_t* font, const char* text, int width) { char* buffer = NULL; uint8_t ch_byte; char* carry; size_t ch_size; uint32_t cp; int glyph_width; bool is_line_end = false; int line_idx; int line_width; int max_lines = 10; char* last_break; char* last_space; char* last_tab; char* line_buffer; size_t line_length; char* new_buffer; size_t pitch; uint32_t utf8state; wraptext_t* wraptext; const char *p, *start; if (!(wraptext = calloc(1, sizeof(wraptext_t)))) goto on_error; // allocate initial buffer get_font_metrics(font, &glyph_width, NULL, NULL); pitch = 4 * (glyph_width > 0 ? width / glyph_width : width) + 3; if (!(buffer = malloc(max_lines * pitch))) goto on_error; if (!(carry = malloc(pitch))) goto on_error; // run through one character at a time, carrying as necessary line_buffer = buffer; line_buffer[0] = '\0'; line_idx = 0; line_width = 0; line_length = 0; memset(line_buffer, 0, pitch); // fill line with NULs p = text; do { utf8state = UTF8_ACCEPT; start = p; while (utf8decode(&utf8state, &cp, ch_byte = *p++) > UTF8_REJECT); if (utf8state == UTF8_REJECT && ch_byte == '\0') --p; // don't eat NUL terminator ch_size = p - start; cp = cp == 0x20AC ? 128 : cp == 0x201A ? 130 : cp == 0x0192 ? 131 : cp == 0x201E ? 132 : cp == 0x2026 ? 133 : cp == 0x2020 ? 134 : cp == 0x2021 ? 135 : cp == 0x02C6 ? 136 : cp == 0x2030 ? 137 : cp == 0x0160 ? 138 : cp == 0x2039 ? 139 : cp == 0x0152 ? 140 : cp == 0x017D ? 142 : cp == 0x2018 ? 145 : cp == 0x2019 ? 146 : cp == 0x201C ? 147 : cp == 0x201D ? 148 : cp == 0x2022 ? 149 : cp == 0x2013 ? 150 : cp == 0x2014 ? 151 : cp == 0x02DC ? 152 : cp == 0x2122 ? 153 : cp == 0x0161 ? 154 : cp == 0x203A ? 155 : cp == 0x0153 ? 156 : cp == 0x017E ? 158 : cp == 0x0178 ? 159 : cp; cp = utf8state == UTF8_ACCEPT ? cp < (uint32_t)font->num_glyphs ? cp : 0x1A : 0x1A; switch (cp) { case '\n': case '\r': // explicit newline if (cp == '\r' && *p == '\n') ++text; // CRLF is_line_end = true; break; case '\t': // tab line_buffer[line_length++] = cp; line_width += get_text_width(font, " "); is_line_end = false; break; case '\0': // NUL terminator is_line_end = line_length > 0; // commit last line on EOT break; default: // default case, copy character as-is memcpy(line_buffer + line_length, start, ch_size); line_length += ch_size; line_width += get_glyph_width(font, cp); is_line_end = false; } if (is_line_end) carry[0] = '\0'; if (line_width > width || line_length >= pitch - 1) { // wrap width exceeded, carry current word to next line is_line_end = true; last_space = strrchr(line_buffer, ' '); last_tab = strrchr(line_buffer, '\t'); last_break = last_space > last_tab ? last_space : last_tab; if (last_break != NULL) // word break (space or tab) found strcpy(carry, last_break + 1); else // no word break, so just carry last character sprintf(carry, "%c", line_buffer[line_length - 1]); line_buffer[line_length - strlen(carry)] = '\0'; } if (is_line_end) { // do we need to enlarge the buffer? if (++line_idx >= max_lines) { max_lines *= 2; if (!(new_buffer = realloc(buffer, max_lines * pitch))) goto on_error; buffer = new_buffer; line_buffer = buffer + line_idx * pitch; } else line_buffer += pitch; memset(line_buffer, 0, pitch); // fill line with NULs // copy carry text into new line line_width = get_text_width(font, carry); line_length = strlen(carry); strcpy(line_buffer, carry); } } while (cp != '\0'); free(carry); wraptext->num_lines = line_idx; wraptext->buffer = buffer; wraptext->pitch = pitch; return wraptext; on_error: free(buffer); free(wraptext); return NULL; }