This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/space_info/textview.c
Shinsuke Irie 5792e77239 Patch [#34373] Use i18n monospace font in Text editor and Python console
This patch allows Blender to display i18n monospace font in the text
editor and the Python interactive console. Wide characters that occupy
multiple columns such as CJK characters can be displayed correctly.
Furthermore, wrapping, selection, suggestion, cursor drawing, and
syntax highlighting should work.

Also fixes a bug [#34543]: In Text Editor false color in comment on cyrillic

To estimate how many columns each character occupies, this patch uses
wcwidth.c written by Markus Kuhn and distributed under MIT-style license:

  http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c

wcwidth.c is stored in extern/wcwidth and used as a static library.

This patch adds new API to blenfont, blenlib and blenkernel:

BLF_get_unifont_mono()
BLF_free_unifont_mono()
BLF_draw_mono()
BLI_wcwidth()
BLI_wcswidth()
BLI_str_utf8_char_width()
BLI_str_utf8_char_width_safe()
txt_utf8_offset_to_column()
txt_utf8_column_to_offset()
2013-03-12 07:25:53 +00:00

362 lines
9.2 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Campbell Barton
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/space_info/textview.c
* \ingroup spinfo
*/
#include <math.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include <assert.h>
#include "MEM_guardedalloc.h"
#include "BLF_api.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLI_string_utf8.h"
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "BKE_text.h"
#include "ED_datafiles.h"
#include "textview.h"
static void console_font_begin(TextViewContext *sc)
{
/* 0.875 is based on: 16 pixels lines get 14 pixel text */
BLF_size(blf_mono_font, 0.875 * sc->lheight, 72);
}
typedef struct ConsoleDrawContext {
int cwidth;
int lheight;
int console_width; /* number of characters that fit into the width of the console (fixed width) */
int winx;
int ymin, ymax;
int *xy; // [2]
int *sel; // [2]
int *pos_pick; // bottom of view == 0, top of file == combine chars, end of line is lower then start.
int *mval; // [2]
int draw;
} ConsoleDrawContext;
BLI_INLINE void console_step_sel(ConsoleDrawContext *cdc, const int step)
{
cdc->sel[0] += step;
cdc->sel[1] += step;
}
static void console_draw_sel(const char *str, const int sel[2], const int xy[2], const int str_len_draw,
int cwidth, int lheight, const unsigned char bg_sel[4])
{
if (sel[0] <= str_len_draw && sel[1] >= 0) {
const int sta = txt_utf8_offset_to_column(str, max_ii(sel[0], 0));
const int end = txt_utf8_offset_to_column(str, min_ii(sel[1], str_len_draw));
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4ubv(bg_sel);
glRecti(xy[0] + (cwidth * sta), xy[1] - 2 + lheight, xy[0] + (cwidth * end), xy[1] - 2);
glDisable(GL_BLEND);
}
}
/* warning: allocated memory for 'offsets' must be freed by caller */
static int console_wrap_offsets(const char *str, int len, int width, int *lines, int **offsets)
{
int i, end; /* column */
int j; /* mem */
*lines = 1;
*offsets = MEM_callocN(sizeof(**offsets) * (len * BLI_UTF8_WIDTH_MAX / MAX2(1, width - (BLI_UTF8_WIDTH_MAX - 1)) + 1),
"console_wrap_offsets");
(*offsets)[0] = 0;
for (i = 0, end = width, j = 0; j < len && str[j]; j += BLI_str_utf8_size_safe(str + j)) {
int columns = BLI_str_utf8_char_width_safe(str + j);
if (i + columns > end) {
(*offsets)[*lines] = j;
(*lines)++;
end = i + width;
}
i += columns;
}
return j; /* return actual length */
}
/* return 0 if the last line is off the screen
* should be able to use this for any string type */
static int console_draw_string(ConsoleDrawContext *cdc, const char *str, int str_len,
const unsigned char fg[3], const unsigned char bg[3], const unsigned char bg_sel[4])
{
int rct_ofs = cdc->lheight / 4;
int tot_lines; /* total number of lines for wrapping */
int *offsets; /* offsets of line beginnings for wrapping */
int y_next;
const int mono = blf_mono_font;
str_len = console_wrap_offsets(str, str_len, cdc->console_width, &tot_lines, &offsets);
y_next = cdc->xy[1] + cdc->lheight * tot_lines;
/* just advance the height */
if (cdc->draw == 0) {
if (cdc->pos_pick && cdc->mval[1] != INT_MAX && cdc->xy[1] <= cdc->mval[1]) {
if (y_next >= cdc->mval[1]) {
int ofs = 0;
/* wrap */
if (tot_lines > 1) {
int iofs = (int)((float)(y_next - cdc->mval[1]) / cdc->lheight);
ofs += offsets[MIN2(iofs, tot_lines - 1)];
}
/* last part */
ofs += txt_utf8_column_to_offset(str + ofs,
(int)floor((float)cdc->mval[0] / cdc->cwidth));
CLAMP(ofs, 0, str_len);
*cdc->pos_pick += str_len - ofs;
}
else
*cdc->pos_pick += str_len + 1;
}
cdc->xy[1] = y_next;
MEM_freeN(offsets);
return 1;
}
else if (y_next - cdc->lheight < cdc->ymin) {
/* have not reached the drawable area so don't break */
cdc->xy[1] = y_next;
/* adjust selection even if not drawing */
if (cdc->sel[0] != cdc->sel[1]) {
console_step_sel(cdc, -(str_len + 1));
}
MEM_freeN(offsets);
return 1;
}
if (tot_lines > 1) { /* wrap? */
const int initial_offset = offsets[tot_lines - 1];
size_t len = str_len - initial_offset;
const char *s = str + initial_offset;
int i;
int sel_orig[2];
copy_v2_v2_int(sel_orig, cdc->sel);
/* invert and swap for wrapping */
cdc->sel[0] = str_len - sel_orig[1];
cdc->sel[1] = str_len - sel_orig[0];
if (bg) {
glColor3ubv(bg);
glRecti(0, cdc->xy[1] - rct_ofs, cdc->winx, (cdc->xy[1] + (cdc->lheight * tot_lines)) + rct_ofs);
}
glColor3ubv(fg);
/* last part needs no clipping */
BLF_position(mono, cdc->xy[0], cdc->xy[1], 0);
BLF_draw_mono(mono, s, len, cdc->cwidth);
if (cdc->sel[0] != cdc->sel[1]) {
console_step_sel(cdc, -initial_offset);
// glColor4ub(255, 0, 0, 96); // debug
console_draw_sel(s, cdc->sel, cdc->xy, len, cdc->cwidth, cdc->lheight, bg_sel);
glColor3ubv(fg);
}
cdc->xy[1] += cdc->lheight;
for (i = tot_lines - 1; i > 0; i--) {
len = offsets[i] - offsets[i - 1];
s = str + offsets[i - 1];
BLF_position(mono, cdc->xy[0], cdc->xy[1], 0);
BLF_draw_mono(mono, s, len, cdc->cwidth);
if (cdc->sel[0] != cdc->sel[1]) {
console_step_sel(cdc, len);
// glColor4ub(0, 255, 0, 96); // debug
console_draw_sel(s, cdc->sel, cdc->xy, len, cdc->cwidth, cdc->lheight, bg_sel);
glColor3ubv(fg);
}
cdc->xy[1] += cdc->lheight;
/* check if were out of view bounds */
if (cdc->xy[1] > cdc->ymax) {
MEM_freeN(offsets);
return 0;
}
}
copy_v2_v2_int(cdc->sel, sel_orig);
console_step_sel(cdc, -(str_len + 1));
}
else { /* simple, no wrap */
if (bg) {
glColor3ubv(bg);
glRecti(0, cdc->xy[1] - rct_ofs, cdc->winx, cdc->xy[1] + cdc->lheight - rct_ofs);
}
glColor3ubv(fg);
BLF_position(mono, cdc->xy[0], cdc->xy[1], 0);
BLF_draw_mono(mono, str, str_len, cdc->cwidth);
if (cdc->sel[0] != cdc->sel[1]) {
int isel[2];
isel[0] = str_len - cdc->sel[1];
isel[1] = str_len - cdc->sel[0];
// glColor4ub(255, 255, 0, 96); // debug
console_draw_sel(str, isel, cdc->xy, str_len, cdc->cwidth, cdc->lheight, bg_sel);
console_step_sel(cdc, -(str_len + 1));
}
cdc->xy[1] += cdc->lheight;
if (cdc->xy[1] > cdc->ymax) {
MEM_freeN(offsets);
return 0;
}
}
MEM_freeN(offsets);
return 1;
}
#define CONSOLE_DRAW_MARGIN 4
int textview_draw(TextViewContext *tvc, const int draw, int mval[2], void **mouse_pick, int *pos_pick)
{
ConsoleDrawContext cdc = {0};
int x_orig = CONSOLE_DRAW_MARGIN, y_orig = CONSOLE_DRAW_MARGIN + tvc->lheight / 6;
int xy[2], y_prev;
int sel[2] = {-1, -1}; /* defaults disabled */
unsigned char fg[3], bg[3];
const int mono = blf_mono_font;
console_font_begin(tvc);
xy[0] = x_orig; xy[1] = y_orig;
if (mval[1] != INT_MAX)
mval[1] += (tvc->ymin + CONSOLE_DRAW_MARGIN);
if (pos_pick)
*pos_pick = 0;
/* constants for the sequencer context */
cdc.cwidth = (int)BLF_fixed_width(mono);
assert(cdc.cwidth > 0);
cdc.lheight = tvc->lheight;
/* note, scroll bar must be already subtracted () */
cdc.console_width = (tvc->winx - (CONSOLE_DRAW_MARGIN * 2) ) / cdc.cwidth;
CLAMP(cdc.console_width, 1, INT_MAX); /* avoid divide by zero on small windows */
cdc.winx = tvc->winx - CONSOLE_DRAW_MARGIN;
cdc.ymin = tvc->ymin;
cdc.ymax = tvc->ymax;
cdc.xy = xy;
cdc.sel = sel;
cdc.pos_pick = pos_pick;
cdc.mval = mval;
cdc.draw = draw;
/* shouldnt be needed */
tvc->cwidth = cdc.cwidth;
tvc->console_width = cdc.console_width;
tvc->iter_index = 0;
if (tvc->sel_start != tvc->sel_end) {
sel[0] = tvc->sel_start;
sel[1] = tvc->sel_end;
}
if (tvc->begin(tvc)) {
unsigned char bg_sel[4] = {0};
if (draw && tvc->const_colors) {
tvc->const_colors(tvc, bg_sel);
}
do {
const char *ext_line;
int ext_len;
int color_flag = 0;
y_prev = xy[1];
if (draw)
color_flag = tvc->line_color(tvc, fg, bg);
tvc->line_get(tvc, &ext_line, &ext_len);
if (!console_draw_string(&cdc, ext_line, ext_len,
(color_flag & TVC_LINE_FG) ? fg : NULL,
(color_flag & TVC_LINE_BG) ? bg : NULL,
bg_sel))
{
/* when drawing, if we pass v2d->cur.ymax, then quit */
if (draw) {
break; /* past the y limits */
}
}
if ((mval[1] != INT_MAX) && (mval[1] >= y_prev && mval[1] <= xy[1])) {
*mouse_pick = (void *)tvc->iter;
break;
}
tvc->iter_index++;
} while (tvc->step(tvc));
}
tvc->end(tvc);
xy[1] += tvc->lheight * 2;
return xy[1] - y_orig;
}