Implement unicode character replacements

Useful to draw whitespaces with alternate characters and colors
without slowing down the text rendering.

A new API is implemented. A renderer.replacements object can be created
to list the replacements.

In turns the function renderer.draw_text and draw_text_subpixel now accept
two optional arguments for replacements.
This commit is contained in:
Francesco Abbate 2021-03-31 17:33:35 +02:00
parent ed6ba64542
commit 3b040aabc7
14 changed files with 212 additions and 33 deletions

View File

@ -6,6 +6,12 @@ This files document the changes done in Lite XL for each release.
Add `system.set_window_opacity` function. Add `system.set_window_opacity` function.
Add codepoint replacement API to support natively the "draw whitespaces" option.
It replace the `drawwhitespace` plugin. If can be configured using the
`config.draw_whitespace` boolean variable and enabled and disables using the
commands `draw-whitespace:toggle`, `draw-whitespace:enable`,
`draw-whitespace:disable`.
### 1.16.5 ### 1.16.5
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122 Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122

View File

@ -59,7 +59,7 @@ end
function command.add_defaults() function command.add_defaults()
local reg = { "core", "root", "command", "doc", "findreplace", "files" } local reg = { "core", "root", "command", "doc", "findreplace", "files", "drawwhitespace" }
for _, name in ipairs(reg) do for _, name in ipairs(reg) do
require("core.commands." .. name) require("core.commands." .. name)
end end

View File

@ -0,0 +1,16 @@
local command = require "core.command"
local config = require "core.config"
command.add(nil, {
["draw-whitespace:toggle"] = function()
config.draw_whitespace = not config.draw_whitespace
end,
["draw-whitespace:disable"] = function()
config.draw_whitespace = false
end,
["draw-whitespace:enable"] = function()
config.draw_whitespace = true
end,
})

View File

@ -21,6 +21,7 @@ config.max_project_files = 2000
config.transitions = true config.transitions = true
config.animation_rate = 1.0 config.animation_rate = 1.0
config.blink_period = 0.8 config.blink_period = 0.8
config.draw_whitespace = false
-- Disable plugin loading setting to false the config entry -- Disable plugin loading setting to false the config entry
-- of the same name. -- of the same name.

View File

@ -314,7 +314,11 @@ function DocView:draw_line_text(idx, x, y)
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset() local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
for _, type, text in self.doc.highlighter:each_token(idx) do for _, type, text in self.doc.highlighter:each_token(idx) do
local color = style.syntax[type] local color = style.syntax[type]
tx = renderer.draw_text_subpixel(font, text, tx, ty, color) if config.draw_whitespace then
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
else
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
end
end end
end end

View File

@ -345,6 +345,14 @@ function core.remove_project_directory(path)
end end
local function whitespace_replacements()
local r = renderer.replacements.new()
r:add(" ", "·")
r:add("\t", "»")
return r
end
function core.init() function core.init()
command = require "core.command" command = require "core.command"
keymap = require "core.keymap" keymap = require "core.keymap"
@ -416,6 +424,7 @@ function core.init()
core.redraw = true core.redraw = true
core.visited_files = {} core.visited_files = {}
core.restart_request = false core.restart_request = false
core.replacements = whitespace_replacements()
core.root_view = RootView() core.root_view = RootView()
core.command_view = CommandView() core.command_view = CommandView()

View File

@ -6,6 +6,7 @@
#include "lualib.h" #include "lualib.h"
#define API_TYPE_FONT "Font" #define API_TYPE_FONT "Font"
#define API_TYPE_REPLACE "Replace"
void api_load_libs(lua_State *L); void api_load_libs(lua_State *L);

42
src/api/cp_replace.c Normal file
View File

@ -0,0 +1,42 @@
#include "api.h"
#include "renderer.h"
static int f_new(lua_State *L) {
CPReplaceTable *rep_table = lua_newuserdata(L, sizeof(CPReplaceTable));
luaL_setmetatable(L, API_TYPE_REPLACE);
ren_cp_replace_init(rep_table);
return 1;
}
static int f_gc(lua_State *L) {
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
ren_cp_replace_free(rep_table);
return 0;
}
static int f_add(lua_State *L) {
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
const char *src = luaL_checkstring(L, 2);
const char *dst = luaL_checkstring(L, 3);
ren_cp_replace_add(rep_table, src, dst);
return 0;
}
static const luaL_Reg lib[] = {
{ "__gc", f_gc },
{ "new", f_new },
{ "add", f_add },
{ NULL, NULL }
};
int luaopen_renderer_replacements(lua_State *L) {
luaL_newmetatable(L, API_TYPE_REPLACE);
luaL_setfuncs(L, lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
return 1;
}

View File

@ -71,28 +71,37 @@ static int f_draw_rect(lua_State *L) {
return 0; return 0;
} }
static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
static int f_draw_text(lua_State *L) {
RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT);
const char *text = luaL_checkstring(L, 2); const char *text = luaL_checkstring(L, 2);
int x = luaL_checknumber(L, 3); /* The coordinate below will be in subpixels iff draw_subpixel is true.
Otherwise it will be in pixels. */
int x_subpixel = luaL_checknumber(L, 3);
int y = luaL_checknumber(L, 4); int y = luaL_checknumber(L, 4);
RenColor color = checkcolor(L, 5, 255); RenColor color = checkcolor(L, 5, 255);
x = rencache_draw_text(*font, text, x, y, color, false);
lua_pushnumber(L, x); CPReplaceTable *rep_table;
RenColor replace_color;
if (lua_gettop(L) >= 7) {
rep_table = luaL_checkudata(L, 6, API_TYPE_REPLACE);
replace_color = checkcolor(L, 7, 255);
} else {
rep_table = NULL;
replace_color = (RenColor) {0};
}
x_subpixel = rencache_draw_text(*font, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color);
lua_pushnumber(L, x_subpixel);
return 1; return 1;
} }
static int f_draw_text(lua_State *L) {
return draw_text_subpixel_impl(L, false);
}
static int f_draw_text_subpixel(lua_State *L) { static int f_draw_text_subpixel(lua_State *L) {
RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); return draw_text_subpixel_impl(L, true);
const char *text = luaL_checkstring(L, 2);
int x_subpixel = luaL_checknumber(L, 3);
int y = luaL_checknumber(L, 4);
RenColor color = checkcolor(L, 5, 255);
x_subpixel = rencache_draw_text(*font, text, x_subpixel, y, color, true);
lua_pushnumber(L, x_subpixel);
return 1;
} }
@ -110,10 +119,13 @@ static const luaL_Reg lib[] = {
int luaopen_renderer_font(lua_State *L); int luaopen_renderer_font(lua_State *L);
int luaopen_renderer_replacements(lua_State *L);
int luaopen_renderer(lua_State *L) { int luaopen_renderer(lua_State *L) {
luaL_newlib(L, lib); luaL_newlib(L, lib);
luaopen_renderer_font(L); luaopen_renderer_font(L);
lua_setfield(L, -2, "font"); lua_setfield(L, -2, "font");
luaopen_renderer_replacements(L);
lua_setfield(L, -2, "replacements");
return 1; return 1;
} }

View File

@ -1,5 +1,6 @@
lite_sources = [ lite_sources = [
'api/api.c', 'api/api.c',
'api/cp_replace.c',
'api/renderer.c', 'api/renderer.c',
'api/renderer_font.c', 'api/renderer_font.c',
'api/system.c', 'api/system.c',

View File

@ -1,3 +1,4 @@
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include "rencache.h" #include "rencache.h"
@ -14,13 +15,16 @@
enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL }; enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
typedef struct { typedef struct {
int type, size; int8_t type;
int8_t tab_size;
int8_t subpixel_scale;
int8_t x_subpixel_offset;
int32_t size;
RenRect rect; RenRect rect;
RenColor color; RenColor color;
RenFont *font; RenFont *font;
short int subpixel_scale; CPReplaceTable *replacements;
short int x_subpixel_offset; RenColor replace_color;
int tab_size;
char text[0]; char text[0];
} Command; } Command;
@ -130,7 +134,10 @@ void rencache_draw_rect(RenRect rect, RenColor color) {
} }
} }
int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel) { int rencache_draw_text(RenFont *font,
const char *text, int x, int y, RenColor color, bool draw_subpixel,
CPReplaceTable *replacements, RenColor replace_color)
{
int subpixel_scale; int subpixel_scale;
int w_subpixel = ren_get_font_width(font, text, &subpixel_scale); int w_subpixel = ren_get_font_width(font, text, &subpixel_scale);
RenRect rect; RenRect rect;
@ -150,6 +157,8 @@ int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor c
cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1); cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1);
cmd->x_subpixel_offset = x - subpixel_scale * rect.x; cmd->x_subpixel_offset = x - subpixel_scale * rect.x;
cmd->tab_size = ren_get_font_tab_size(font); cmd->tab_size = ren_get_font_tab_size(font);
cmd->replacements = replacements;
cmd->replace_color = replace_color;
} }
} }
@ -262,11 +271,14 @@ void rencache_end_frame(void) {
break; break;
case DRAW_TEXT: case DRAW_TEXT:
ren_set_font_tab_size(cmd->font, cmd->tab_size); ren_set_font_tab_size(cmd->font, cmd->tab_size);
ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color); ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color,
cmd->replacements, cmd->replace_color);
break; break;
case DRAW_TEXT_SUBPIXEL: case DRAW_TEXT_SUBPIXEL:
ren_set_font_tab_size(cmd->font, cmd->tab_size); ren_set_font_tab_size(cmd->font, cmd->tab_size);
ren_draw_text_subpixel(cmd->font, cmd->text, cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color); ren_draw_text_subpixel(cmd->font, cmd->text,
cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color,
cmd->replacements, cmd->replace_color);
break; break;
} }
} }

View File

@ -8,7 +8,7 @@ void rencache_show_debug(bool enable);
void rencache_free_font(RenFont *font); void rencache_free_font(RenFont *font);
void rencache_set_clip_rect(RenRect rect); void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, RenColor color); void rencache_draw_rect(RenRect rect, RenColor color);
int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel); int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color);
void rencache_invalidate(void); void rencache_invalidate(void);
void rencache_begin_frame(void); void rencache_begin_frame(void);
void rencache_end_frame(void); void rencache_end_frame(void);

View File

@ -6,6 +6,7 @@
#include "font_renderer.h" #include "font_renderer.h"
#define MAX_GLYPHSET 256 #define MAX_GLYPHSET 256
#define REPLACEMENT_CHUNK_SIZE 8
struct RenImage { struct RenImage {
RenColor *pixels; RenColor *pixels;
@ -60,6 +61,37 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
} }
void ren_cp_replace_init(CPReplaceTable *rep_table) {
rep_table->size = 0;
rep_table->replacements = NULL;
}
void ren_cp_replace_free(CPReplaceTable *rep_table) {
free(rep_table->replacements);
}
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst) {
int table_size = rep_table->size;
if (table_size % REPLACEMENT_CHUNK_SIZE == 0) {
CPReplace *old_replacements = rep_table->replacements;
const int new_size = (table_size / REPLACEMENT_CHUNK_SIZE + 1) * REPLACEMENT_CHUNK_SIZE;
rep_table->replacements = malloc(new_size * sizeof(CPReplace));
if (!rep_table->replacements) {
rep_table->replacements = old_replacements;
return;
}
memcpy(rep_table->replacements, old_replacements, table_size * sizeof(CPReplace));
free(old_replacements);
}
CPReplace *rep = &rep_table->replacements[table_size];
utf8_to_codepoint(src, &rep->codepoint_src);
utf8_to_codepoint(dst, &rep->codepoint_dst);
rep_table->size = table_size + 1;
}
void ren_init(SDL_Window *win) { void ren_init(SDL_Window *win) {
assert(win); assert(win);
window = win; window = win;
@ -291,31 +323,55 @@ void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color)
} }
} }
static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) {
for (int i = 0; i < rep_table->size; i++) {
const CPReplace *rep = &rep_table->replacements[i];
if (*codepoint == rep->codepoint_src) {
*codepoint = rep->codepoint_dst;
return 1;
}
}
return 0;
}
void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color) {
void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
const char *p = text; const char *p = text;
unsigned codepoint; unsigned codepoint;
SDL_Surface *surf = SDL_GetWindowSurface(window); SDL_Surface *surf = SDL_GetWindowSurface(window);
FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b }; const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b };
while (*p) { while (*p) {
FR_Color color_rep;
p = utf8_to_codepoint(p, &codepoint); p = utf8_to_codepoint(p, &codepoint);
GlyphSet *set = get_glyphset(font, codepoint); GlyphSet *set = get_glyphset(font, codepoint);
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff]; FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
const int xadvance_original_cp = g->xadvance;
const int replaced = replacements ? codepoint_replace(replacements, &codepoint) : 0;
if (replaced) {
set = get_glyphset(font, codepoint);
g = &set->glyphs[codepoint & 0xff];
color_rep = (FR_Color) { .r = replace_color.r, .g = replace_color.g, .b = replace_color.b};
} else {
color_rep = color_fr;
}
if (color.a != 0) { if (color.a != 0) {
FR_Blend_Glyph(font->renderer, &clip, FR_Blend_Glyph(font->renderer, &clip,
x_subpixel, y, (uint8_t *) surf->pixels, surf->w, set->image, g, color_fr); x_subpixel, y, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep);
} }
x_subpixel += g->xadvance; x_subpixel += xadvance_original_cp;
} }
} }
void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color,
void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) { CPReplaceTable *replacements, RenColor replace_color)
{
const int subpixel_scale = FR_Subpixel_Scale(font->renderer); const int subpixel_scale = FR_Subpixel_Scale(font->renderer);
ren_draw_text_subpixel(font, text, subpixel_scale * x, y, color); ren_draw_text_subpixel(font, text, subpixel_scale * x, y, color, replacements, replace_color);
} }
// Could be declared as static inline
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) { int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) {
int w_mult; int w_mult;
if (orientation < 0) { if (orientation < 0) {
@ -332,3 +388,4 @@ int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) {
int ren_get_font_subpixel_scale(RenFont *font) { int ren_get_font_subpixel_scale(RenFont *font) {
return FR_Subpixel_Scale(font->renderer); return FR_Subpixel_Scale(font->renderer);
} }

View File

@ -21,6 +21,19 @@ enum {
typedef struct { uint8_t b, g, r, a; } RenColor; typedef struct { uint8_t b, g, r, a; } RenColor;
typedef struct { int x, y, width, height; } RenRect; typedef struct { int x, y, width, height; } RenRect;
struct CPReplace {
unsigned codepoint_src;
unsigned codepoint_dst;
};
typedef struct CPReplace CPReplace;
struct CPReplaceTable {
int size;
CPReplace *replacements;
};
typedef struct CPReplaceTable CPReplaceTable;
void ren_init(SDL_Window *win); void ren_init(SDL_Window *win);
void ren_update_rects(RenRect *rects, int count); void ren_update_rects(RenRect *rects, int count);
@ -41,7 +54,12 @@ int ren_font_subpixel_round(int width, int subpixel_scale, int orientation);
void ren_draw_rect(RenRect rect, RenColor color); void ren_draw_rect(RenRect rect, RenColor color);
void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color); void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color);
void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color); void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color); void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
void ren_cp_replace_init(CPReplaceTable *rep_table);
void ren_cp_replace_free(CPReplaceTable *rep_table);
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst);
void ren_cp_replace_clear(CPReplaceTable *rep_table);
#endif #endif