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:
parent
ed6ba64542
commit
3b040aabc7
|
@ -6,6 +6,12 @@ This files document the changes done in Lite XL for each release.
|
|||
|
||||
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
|
||||
|
||||
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122
|
||||
|
|
|
@ -59,7 +59,7 @@ end
|
|||
|
||||
|
||||
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
|
||||
require("core.commands." .. name)
|
||||
end
|
||||
|
|
|
@ -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,
|
||||
})
|
|
@ -21,6 +21,7 @@ config.max_project_files = 2000
|
|||
config.transitions = true
|
||||
config.animation_rate = 1.0
|
||||
config.blink_period = 0.8
|
||||
config.draw_whitespace = false
|
||||
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
|
|
|
@ -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()
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
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
|
||||
|
||||
|
|
|
@ -345,6 +345,14 @@ function core.remove_project_directory(path)
|
|||
end
|
||||
|
||||
|
||||
local function whitespace_replacements()
|
||||
local r = renderer.replacements.new()
|
||||
r:add(" ", "·")
|
||||
r:add("\t", "»")
|
||||
return r
|
||||
end
|
||||
|
||||
|
||||
function core.init()
|
||||
command = require "core.command"
|
||||
keymap = require "core.keymap"
|
||||
|
@ -416,6 +424,7 @@ function core.init()
|
|||
core.redraw = true
|
||||
core.visited_files = {}
|
||||
core.restart_request = false
|
||||
core.replacements = whitespace_replacements()
|
||||
|
||||
core.root_view = RootView()
|
||||
core.command_view = CommandView()
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "lualib.h"
|
||||
|
||||
#define API_TYPE_FONT "Font"
|
||||
#define API_TYPE_REPLACE "Replace"
|
||||
|
||||
void api_load_libs(lua_State *L);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -71,28 +71,37 @@ static int f_draw_rect(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_draw_text(lua_State *L) {
|
||||
static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
|
||||
RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
static int f_draw_text(lua_State *L) {
|
||||
return draw_text_subpixel_impl(L, false);
|
||||
}
|
||||
|
||||
|
||||
static int f_draw_text_subpixel(lua_State *L) {
|
||||
RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
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;
|
||||
return draw_text_subpixel_impl(L, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -110,10 +119,13 @@ static const luaL_Reg lib[] = {
|
|||
|
||||
|
||||
int luaopen_renderer_font(lua_State *L);
|
||||
int luaopen_renderer_replacements(lua_State *L);
|
||||
|
||||
int luaopen_renderer(lua_State *L) {
|
||||
luaL_newlib(L, lib);
|
||||
luaopen_renderer_font(L);
|
||||
lua_setfield(L, -2, "font");
|
||||
luaopen_renderer_replacements(L);
|
||||
lua_setfield(L, -2, "replacements");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
lite_sources = [
|
||||
'api/api.c',
|
||||
'api/cp_replace.c',
|
||||
'api/renderer.c',
|
||||
'api/renderer_font.c',
|
||||
'api/system.c',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "rencache.h"
|
||||
|
||||
|
@ -14,13 +15,16 @@
|
|||
enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
|
||||
|
||||
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;
|
||||
RenColor color;
|
||||
RenFont *font;
|
||||
short int subpixel_scale;
|
||||
short int x_subpixel_offset;
|
||||
int tab_size;
|
||||
CPReplaceTable *replacements;
|
||||
RenColor replace_color;
|
||||
char text[0];
|
||||
} 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 w_subpixel = ren_get_font_width(font, text, &subpixel_scale);
|
||||
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->x_subpixel_offset = x - subpixel_scale * rect.x;
|
||||
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;
|
||||
case DRAW_TEXT:
|
||||
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;
|
||||
case DRAW_TEXT_SUBPIXEL:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ void rencache_show_debug(bool enable);
|
|||
void rencache_free_font(RenFont *font);
|
||||
void rencache_set_clip_rect(RenRect rect);
|
||||
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_begin_frame(void);
|
||||
void rencache_end_frame(void);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "font_renderer.h"
|
||||
|
||||
#define MAX_GLYPHSET 256
|
||||
#define REPLACEMENT_CHUNK_SIZE 8
|
||||
|
||||
struct RenImage {
|
||||
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) {
|
||||
assert(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;
|
||||
unsigned codepoint;
|
||||
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) {
|
||||
FR_Color color_rep;
|
||||
p = utf8_to_codepoint(p, &codepoint);
|
||||
GlyphSet *set = get_glyphset(font, codepoint);
|
||||
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) {
|
||||
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);
|
||||
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 w_mult;
|
||||
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) {
|
||||
return FR_Subpixel_Scale(font->renderer);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,19 @@ enum {
|
|||
typedef struct { uint8_t b, g, r, a; } RenColor;
|
||||
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_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_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_subpixel(RenFont *font, const char *text, int x_subpixel, 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, 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
|
||||
|
|
Loading…
Reference in New Issue