diff --git a/build-packages.sh b/build-packages.sh index f2a57397..6fd6a0c4 100755 --- a/build-packages.sh +++ b/build-packages.sh @@ -84,11 +84,11 @@ lite_build_package_windows () { rm -fr ".package-build" echo "created package $package_name" } - -lite_build_package_macosx () { + +lite_build_package_macos () { local build="$1" local arch="$2" - local os="macosx" + local os="macos" local appdir=".package-build/lite-xl.app" local bindir="$appdir/Contents/MacOS" @@ -110,10 +110,7 @@ lite_build_package_macosx () { mv "$package_name" .. popd rm -fr ".package-build" - local dmg_name="lite-xl-$os-$arch.dmg" - rm -f "$dmg_name" && hdiutil create -volname lite-xl -srcfolder lite-xl.app -ov -format UDBZ "$dmg_name" echo "created package $package_name" - echo "created disk image $dmg_name" } lite_build_package_linux () { @@ -161,7 +158,7 @@ lite_build_package () { if [[ "$OSTYPE" == msys || "$OSTYPE" == win32 ]]; then lite_build_package_windows "$@" elif [[ "$OSTYPE" == "darwin"* ]]; then - lite_build_package_macosx "$@" + lite_build_package_macos "$@" elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then lite_build_package_linux "$@" else diff --git a/dev-utils/macos-retina-display.md b/dev-utils/macos-retina-display.md new file mode 100644 index 00000000..ae464dc4 --- /dev/null +++ b/dev-utils/macos-retina-display.md @@ -0,0 +1,97 @@ +## Window creation for Retina displays + +The file info.plist sets NSHighResolutionCapable to true. This is fine for High-DPI +and retina displays. + +The `SDL_CreateWindow` is called with the flag `SDL_WINDOW_ALLOW_HIGHDPI`. +On Mac OS it means that, in source file `video/cocoa/SDL_cocoawindow.m`, from +function `Cocoa_CreateWindow`, SDL calls: + +```objc +/* highdpi will be TRUE below */ +BOOL highdpi = (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) != 0; +[contentView setWantsBestResolutionOpenGLSurface:highdpi] +``` + +Documentation for `setWantsBestResolutionOpenGLSurface`: + +https://developer.apple.com/documentation/appkit/nsview/1414938-wantsbestresolutionopenglsurface + +with more details in "OpenGL Programming Guide for Mac", chapter +"Optimizing OpenGL for High Resolution": + +https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/EnablingOpenGLforHighResolution/EnablingOpenGLforHighResolution.html#//apple_ref/doc/uid/TP40001987-CH1001-SW4 + +Citation from the official documentation: + +You can opt in to high resolution by calling the method +`setWantsBestResolutionOpenGLSurface:` when you initialize the view, and +supplying YES as an argument: + +```objc +[self setWantsBestResolutionOpenGLSurface:YES]; +``` + +If you don’t opt in, the system magnifies the rendered results. + +The wantsBestResolutionOpenGLSurface property is relevant only for views to +which an NSOpenGLContext object is bound. Its value does not affect the behavior +of other views. For compatibility, wantsBestResolutionOpenGLSurface defaults to +NO, providing a 1-pixel-per-point framebuffer regardless of the backing scale +factor for the display the view occupies. Setting this property to YES for a +given view causes AppKit to allocate a higher-resolution framebuffer when +appropriate for the backing scale factor and target display. + +To function correctly with wantsBestResolutionOpenGLSurface set to YES, a view +must perform correct conversions between view units (points) and pixel units as +needed. For example, the common practice of passing the width and height of +[self bounds] to glViewport() will yield incorrect results at high resolution, +because the parameters passed to the glViewport() function must be in pixels. As +a result, you’ll get only partial instead of complete coverage of the render +surface. Instead, use the backing store bounds: + +```objc +[self convertRectToBacking:[self bounds]]; +``` + +## Coordinates + +The SDL function `SDL_GL_GetDrawableSize` will provide the size of the underlying drawable +in pixels. From the `SDL_video.h` header. + +```c +/** + * \brief Get the size of a window's underlying drawable in pixels (for use + * with glViewport). + * + * \param window Window from which the drawable size should be queried + * \param w Pointer to variable for storing the width in pixels, may be NULL + * \param h Pointer to variable for storing the height in pixels, may be NULL + * + * This may differ from SDL_GetWindowSize() if we're rendering to a high-DPI + * drawable, i.e. the window was created with SDL_WINDOW_ALLOW_HIGHDPI on a + * platform with high-DPI support (Apple calls this "Retina"), and not disabled + * by the SDL_HINT_VIDEO_HIGHDPI_DISABLED hint. + * + * \sa SDL_GetWindowSize() + * \sa SDL_CreateWindow() + */ +extern DECLSPEC void SDLCALL SDL_GL_GetDrawableSize(SDL_Window * window, int *w, + int *h); +``` + +In turns it calls `Cocoa_GL_GetDrawableSize` from source file +`video/cocoa/SDL_cocoaopengl.m`. The function use the method +`[contentView convertRectToBacking:viewport]`: + +```objc + if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { + /* This gives us the correct viewport for a Retina-enabled view, only + * supported on 10.7+. */ + if ([contentView respondsToSelector:@selector(convertRectToBacking:)]) { + viewport = [contentView convertRectToBacking:viewport]; + } + } +``` + +to give back the sizes in pixels. diff --git a/meson.build b/meson.build index 1f31b509..5b9576d4 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,11 @@ if host_machine.system() == 'windows' lite_rc += windows.compile_resources('res.rc') endif +# On macos we need to use the SDL renderer to support retina displays +if get_option('renderer') or host_machine.system() == 'darwin' + lite_cargs += '-DLITE_USE_SDL_RENDERER' +endif + subdir('lib/font_renderer') subdir('src') diff --git a/meson_options.txt b/meson_options.txt index 2714cdf4..73a542f2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1,3 @@ option('portable', type : 'boolean', value : false, description: 'Portable install') +option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') diff --git a/src/api/renderer.c b/src/api/renderer.c index 6e4ebf40..c6883a95 100644 --- a/src/api/renderer.c +++ b/src/api/renderer.c @@ -72,9 +72,9 @@ static int f_draw_rect(lua_State *L) { } static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) { - RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); + FontDesc *font_desc = luaL_checkudata(L, 1, API_TYPE_FONT); const char *text = luaL_checkstring(L, 2); - /* The coordinate below will be in subpixels iff draw_subpixel is true. + /* The coordinate below will be in subpixel iff draw_subpixel is true. Otherwise it will be in pixels. */ int x_subpixel = luaL_checknumber(L, 3); int y = luaL_checknumber(L, 4); @@ -90,7 +90,7 @@ static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) { replace_color = (RenColor) {0}; } - x_subpixel = rencache_draw_text(*font, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color); + x_subpixel = rencache_draw_text(font_desc, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color); lua_pushnumber(L, x_subpixel); return 1; } @@ -114,7 +114,7 @@ static const luaL_Reg lib[] = { { "draw_rect", f_draw_rect }, { "draw_text", f_draw_text }, { "draw_text_subpixel", f_draw_text_subpixel }, - { NULL, NULL } + { NULL, NULL } }; diff --git a/src/api/renderer_font.c b/src/api/renderer_font.c index dd5069bc..3d85597b 100644 --- a/src/api/renderer_font.c +++ b/src/api/renderer_font.c @@ -1,8 +1,8 @@ #include "api.h" +#include "fontdesc.h" #include "renderer.h" #include "rencache.h" - static int f_load(lua_State *L) { const char *filename = luaL_checkstring(L, 1); float size = luaL_checknumber(L, 2); @@ -40,57 +40,63 @@ static int f_load(lua_State *L) { } lua_pop(L, 1); } - RenFont **self = lua_newuserdata(L, sizeof(*self)); + + if (ren_verify_font(filename)) { + luaL_error(L, "failed to load font"); + } + + FontDesc *font_desc = lua_newuserdata(L, font_desc_alloc_size(filename)); + font_desc_init(font_desc, filename, size, font_options); luaL_setmetatable(L, API_TYPE_FONT); - *self = ren_load_font(filename, size, font_options); - if (!*self) { luaL_error(L, "failed to load font"); } return 1; } static int f_set_tab_size(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); int n = luaL_checknumber(L, 2); - ren_set_font_tab_size(*self, n); + font_desc_set_tab_size(self, n); return 0; } static int f_gc(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); - if (*self) { rencache_free_font(*self); } + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); + rencache_free_font(self); return 0; } - static int f_get_width(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); const char *text = luaL_checkstring(L, 2); - int subpixel_scale; - int w = ren_get_font_width(*self, text, &subpixel_scale); - lua_pushnumber(L, ren_font_subpixel_round(w, subpixel_scale, 0)); + /* By calling ren_get_font_width with NULL as third arguments + we will obtain the width in points. */ + int w = ren_get_font_width(self, text, NULL); + lua_pushnumber(L, w); return 1; } static int f_subpixel_scale(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); - lua_pushnumber(L, ren_get_font_subpixel_scale(*self)); + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); + lua_pushnumber(L, ren_get_font_subpixel_scale(self)); return 1; } - static int f_get_width_subpixel(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); const char *text = luaL_checkstring(L, 2); - lua_pushnumber(L, ren_get_font_width(*self, text, NULL)); + int subpixel_scale; + /* We need to pass a non-null subpixel_scale pointer to force + subpixel width calculation. */ + lua_pushnumber(L, ren_get_font_width(self, text, &subpixel_scale)); return 1; } static int f_get_height(lua_State *L) { - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); - lua_pushnumber(L, ren_get_font_height(*self) ); + FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT); + lua_pushnumber(L, ren_get_font_height(self) ); return 1; } diff --git a/src/api/system.c b/src/api/system.c index 99180ad8..1b15a2ec 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -109,7 +109,9 @@ top: case SDL_WINDOWEVENT: if (e.window.event == SDL_WINDOWEVENT_RESIZED) { + ren_resize_window(); lua_pushstring(L, "resized"); + /* The size below will be in points. */ lua_pushnumber(L, e.window.data1); lua_pushnumber(L, e.window.data2); return 3; @@ -316,6 +318,7 @@ static int f_set_window_size(lua_State *L) { double y = luaL_checknumber(L, 4); SDL_SetWindowSize(window, w, h); SDL_SetWindowPosition(window, x, y); + ren_resize_window(); return 0; } diff --git a/src/fontdesc.c b/src/fontdesc.c new file mode 100644 index 00000000..d1d0825f --- /dev/null +++ b/src/fontdesc.c @@ -0,0 +1,76 @@ +#include +#include + +#include "fontdesc.h" +#include "renderer.h" + + +int font_desc_alloc_size(const char *filename) { + return offsetof(FontDesc, filename) + strlen(filename) + 1; +} + +void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options) { + memcpy(font_desc->filename, filename, strlen(filename) + 1); + font_desc->size = size; + font_desc->options = font_options; + font_desc->tab_size = 4; + font_desc->cache_length = 0; + font_desc->cache_last_index = 0; /* Normally no need to initialize. */ +} + +void font_desc_free(FontDesc *font_desc) { + for (int i = 0; i < font_desc->cache_length; i++) { + ren_free_font(font_desc->cache[i].font); + } + font_desc->cache_length = 0; +} + +void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) { + font_desc->tab_size = tab_size; + for (int i = 0; i < font_desc->cache_length; i++) { + ren_set_font_tab_size(font_desc->cache[i].font, tab_size); + } +} + +int font_desc_get_tab_size(FontDesc *font_desc) { + return font_desc->tab_size; +} + +static void load_scaled_font(FontDesc *font_desc, int index, int scale) { + RenFont *font = ren_load_font(font_desc->filename, scale * font_desc->size, font_desc->options); + if (!font) { + /* The font was able to load when initially loaded using renderer.load.font. + If now is no longer available we just abort the application. */ + fprintf(stderr, "Fatal error: unable to load font %s. Application will abort.\n", + font_desc->filename); + exit(1); + } + font_desc->cache[index].font = font; + font_desc->cache[index].scale = scale; +} + +RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale) { + int index = -1; + for (int i = 0; i < font_desc->cache_length; i++) { + if (font_desc->cache[i].scale == scale) { + index = i; + break; + } + } + if (index < 0) { + index = font_desc->cache_length; + if (index < FONT_CACHE_ARRAY_MAX) { + load_scaled_font(font_desc, index, scale); + font_desc->cache_length = index + 1; + } else { + // FIXME: should not print into the stderr or stdout. + fprintf(stderr, "Warning: max array of font scale reached.\n"); + index = (font_desc->cache_last_index == 0 ? 1 : 0); + ren_free_font(font_desc->cache[index].font); + load_scaled_font(font_desc, index, scale); + } + } + font_desc->cache_last_index = index; + return font_desc->cache[index].font; +} + diff --git a/src/fontdesc.h b/src/fontdesc.h new file mode 100644 index 00000000..2f4702ab --- /dev/null +++ b/src/fontdesc.h @@ -0,0 +1,33 @@ +#ifndef FONT_DESC_H +#define FONT_DESC_H + +typedef struct RenFont RenFont; + +struct FontInstance { + RenFont *font; + short int scale; +}; +typedef struct FontInstance FontInstance; + +#define FONT_CACHE_ARRAY_MAX 2 + +struct FontDesc { + float size; + unsigned int options; + short int tab_size; + FontInstance cache[FONT_CACHE_ARRAY_MAX]; + short int cache_length; + short int cache_last_index; /* More recently used instance. */ + char filename[0]; +}; +typedef struct FontDesc FontDesc; + +void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options); +int font_desc_alloc_size(const char *filename); +int font_desc_get_tab_size(FontDesc *font_desc); +void font_desc_set_tab_size(FontDesc *font_desc, int tab_size); +void font_desc_free(FontDesc *font_desc); +RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale); + +#endif + diff --git a/src/main.c b/src/main.c index e2ae548b..43652fbc 100644 --- a/src/main.c +++ b/src/main.c @@ -200,7 +200,7 @@ init_lua: } lua_close(L); - SDL_DestroyWindow(window); + ren_free_window_resources(); return EXIT_SUCCESS; } diff --git a/src/meson.build b/src/meson.build index 2800d3d4..881014be 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,8 @@ lite_sources = [ 'api/renderer_font.c', 'api/system.c', 'renderer.c', + 'renwindow.c', + 'fontdesc.c', 'rencache.c', 'main.c', ] diff --git a/src/rencache.c b/src/rencache.c index 3d607094..36734b24 100644 --- a/src/rencache.c +++ b/src/rencache.c @@ -24,7 +24,7 @@ typedef struct { int32_t size; RenRect rect; RenColor color; - RenFont *font; + FontDesc *font_desc; CPReplaceTable *replacements; RenColor replace_color; char text[0]; @@ -115,9 +115,9 @@ void rencache_show_debug(bool enable) { } -void rencache_free_font(RenFont *font) { +void rencache_free_font(FontDesc *font_desc) { Command *cmd = push_command(FREE_FONT, COMMAND_BARE_SIZE); - if (cmd) { cmd->font = font; } + if (cmd) { cmd->font_desc = font_desc; } } @@ -136,17 +136,17 @@ void rencache_draw_rect(RenRect rect, RenColor color) { } } -int rencache_draw_text(RenFont *font, +int rencache_draw_text(FontDesc *font_desc, 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); + int w_subpixel = ren_get_font_width(font_desc, text, &subpixel_scale); RenRect rect; rect.x = (draw_subpixel ? ren_font_subpixel_round(x, subpixel_scale, -1) : x); rect.y = y; rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0); - rect.height = ren_get_font_height(font); + rect.height = ren_get_font_height(font_desc); if (rects_overlap(screen_rect, rect)) { int sz = strlen(text) + 1; @@ -154,11 +154,11 @@ int rencache_draw_text(RenFont *font, if (cmd) { memcpy(cmd->text, text, sz); cmd->color = color; - cmd->font = font; + cmd->font_desc = font_desc; cmd->rect = rect; 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->tab_size = font_desc_get_tab_size(font_desc); cmd->replacements = replacements; cmd->replace_color = replace_color; } @@ -272,13 +272,13 @@ void rencache_end_frame(void) { ren_draw_rect(cmd->rect, cmd->color); 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, + font_desc_set_tab_size(cmd->font_desc, cmd->tab_size); + ren_draw_text(cmd->font_desc, 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, + font_desc_set_tab_size(cmd->font_desc, cmd->tab_size); + ren_draw_text_subpixel(cmd->font_desc, cmd->text, cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color, cmd->replacements, cmd->replace_color); break; @@ -301,7 +301,7 @@ void rencache_end_frame(void) { cmd = NULL; while (next_command(&cmd)) { if (cmd->type == FREE_FONT) { - ren_free_font(cmd->font); + font_desc_free(cmd->font_desc); } } } diff --git a/src/rencache.h b/src/rencache.h index a532c9b5..b5c241ab 100644 --- a/src/rencache.h +++ b/src/rencache.h @@ -5,10 +5,11 @@ #include "renderer.h" void rencache_show_debug(bool enable); -void rencache_free_font(RenFont *font); +void rencache_free_font(FontDesc *font_desc); 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, CPReplaceTable *replacements, RenColor replace_color); +int rencache_draw_text(FontDesc *font_desc, 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); diff --git a/src/renderer.c b/src/renderer.c index 36a76604..b08c25d9 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -2,8 +2,9 @@ #include #include #include -#include "renderer.h" #include "font_renderer.h" +#include "renderer.h" +#include "renwindow.h" #define MAX_GLYPHSET 256 #define REPLACEMENT_CHUNK_SIZE 8 @@ -31,9 +32,7 @@ struct RenFont { FR_Renderer *renderer; }; - -static SDL_Window *window; -static FR_Clip_Area clip; +static RenWindow window_renderer = {0}; static void* check_alloc(void *ptr) { if (!ptr) { @@ -91,37 +90,44 @@ void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char * rep_table->size = table_size + 1; } +void ren_free_window_resources() { + renwin_free(&window_renderer); +} void ren_init(SDL_Window *win) { assert(win); - window = win; - SDL_Surface *surf = SDL_GetWindowSurface(window); - ren_set_clip_rect( (RenRect) { 0, 0, surf->w, surf->h } ); + window_renderer.window = win; + renwin_init_surface(&window_renderer); + renwin_clip_to_surface(&window_renderer); +} + + +void ren_resize_window() { + renwin_resize_surface(&window_renderer); } void ren_update_rects(RenRect *rects, int count) { - SDL_UpdateWindowSurfaceRects(window, (SDL_Rect*) rects, count); static bool initial_frame = true; if (initial_frame) { - SDL_ShowWindow(window); + renwin_show_window(&window_renderer); initial_frame = false; } + renwin_update_rects(&window_renderer, rects, count); } void ren_set_clip_rect(RenRect rect) { - clip.left = rect.x; - clip.top = rect.y; - clip.right = rect.x + rect.width; - clip.bottom = rect.y + rect.height; + renwin_set_clip_rect(&window_renderer, rect); } void ren_get_size(int *x, int *y) { - SDL_Surface *surf = SDL_GetWindowSurface(window); - *x = surf->w; - *y = surf->h; + RenWindow *ren = &window_renderer; + const int scale = renwin_surface_scale(ren); + SDL_Surface *surface = renwin_get_surface(ren); + *x = surface->w / scale; + *y = surface->h / scale; } @@ -158,6 +164,17 @@ static GlyphSet* get_glyphset(RenFont *font, int codepoint) { } +int ren_verify_font(const char *filename) { + RenFont font[1]; + font->renderer = FR_Renderer_New(0); + if (FR_Load_Font(font->renderer, filename)) { + return 1; + } + FR_Renderer_Free(font->renderer); + return 0; +} + + RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags) { RenFont *font = NULL; @@ -218,25 +235,34 @@ int ren_get_font_tab_size(RenFont *font) { } -int ren_get_font_width(RenFont *font, const char *text, int *subpixel_scale) { +/* Important: if subpixel_scale is NULL we will return width in points. Otherwise we will + return width in subpixels. */ +int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale) { int x = 0; const char *p = text; unsigned codepoint; + const int surface_scale = renwin_surface_scale(&window_renderer); + RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale); while (*p) { p = utf8_to_codepoint(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff]; x += g->xadvance; } + /* At this point here x is in subpixel units */ + const int x_scale_to_points = FR_Subpixel_Scale(font->renderer) * surface_scale; if (subpixel_scale) { - *subpixel_scale = FR_Subpixel_Scale(font->renderer); + *subpixel_scale = x_scale_to_points; + return x; } - return x; + return (x + x_scale_to_points / 2) / x_scale_to_points; } -int ren_get_font_height(RenFont *font) { - return font->height; +int ren_get_font_height(FontDesc *font_desc) { + const int surface_scale = renwin_surface_scale(&window_renderer); + RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale); + return (font->height + surface_scale / 2) / surface_scale; } @@ -249,16 +275,6 @@ static inline RenColor blend_pixel(RenColor dst, RenColor src) { } -static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color) { - src.a = (src.a * color.a) >> 8; - int ia = 0xff - src.a; - dst.r = ((src.r * color.r * src.a) >> 16) + ((dst.r * ia) >> 8); - dst.g = ((src.g * color.g * src.a) >> 16) + ((dst.g * ia) >> 8); - dst.b = ((src.b * color.b * src.a) >> 16) + ((dst.b * ia) >> 8); - return dst; -} - - #define rect_draw_loop(expr) \ for (int j = y1; j < y2; j++) { \ for (int i = x1; i < x2; i++) { \ @@ -271,17 +287,26 @@ static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color) void ren_draw_rect(RenRect rect, RenColor color) { if (color.a == 0) { return; } - int x1 = rect.x < clip.left ? clip.left : rect.x; - int y1 = rect.y < clip.top ? clip.top : rect.y; + const int surface_scale = renwin_surface_scale(&window_renderer); + + /* transforms coordinates in pixels. */ + rect.x *= surface_scale; + rect.y *= surface_scale; + rect.width *= surface_scale; + rect.height *= surface_scale; + + const RenRect clip = window_renderer.clip; + int x1 = rect.x < clip.x ? clip.x : rect.x; + int y1 = rect.y < clip.y ? clip.y : rect.y; int x2 = rect.x + rect.width; int y2 = rect.y + rect.height; - x2 = x2 > clip.right ? clip.right : x2; - y2 = y2 > clip.bottom ? clip.bottom : y2; + x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2; + y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2; - SDL_Surface *surf = SDL_GetWindowSurface(window); - RenColor *d = (RenColor*) surf->pixels; - d += x1 + y1 * surf->w; - int dr = surf->w - (x2 - x1); + SDL_Surface *surface = renwin_get_surface(&window_renderer); + RenColor *d = (RenColor*) surface->pixels; + d += x1 + y1 * surface->w; + int dr = surface->w - (x2 - x1); if (color.a == 0xff) { rect_draw_loop(color); @@ -290,38 +315,6 @@ void ren_draw_rect(RenRect rect, RenColor color) { } } -void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) { - if (color.a == 0) { return; } - - int n; - if ((n = clip.left - x) > 0) { sub->width -= n; sub->x += n; x += n; } - if ((n = clip.top - y) > 0) { sub->height -= n; sub->y += n; x += n; } - if ((n = x + sub->width - clip.right ) > 0) { sub->width -= n; } - if ((n = y + sub->height - clip.bottom) > 0) { sub->height -= n; } - - if (sub->width <= 0 || sub->height <= 0) { - return; - } - - /* draw */ - SDL_Surface *surf = SDL_GetWindowSurface(window); - RenColor *s = image->pixels; - RenColor *d = (RenColor*) surf->pixels; - s += sub->x + sub->y * image->width; - d += x + y * surf->w; - int sr = image->width - sub->width; - int dr = surf->w - sub->width; - - for (int j = 0; j < sub->height; j++) { - for (int i = 0; i < sub->width; i++) { - *d = blend_pixel2(*d, *s, color); - d++; - s++; - } - d += dr; - s += sr; - } -} static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) { for (int i = 0; i < rep_table->size; i++) { @@ -335,12 +328,18 @@ static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) { } -void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int y, RenColor color, +static FR_Clip_Area clip_area_from_rect(const RenRect r) { + return (FR_Clip_Area) {r.x, r.y, r.x + r.width, r.y + r.height}; +} + + +static void draw_text_impl(RenFont *font, const char *text, int x_subpixel, int y_pixel, RenColor color, CPReplaceTable *replacements, RenColor replace_color) { + SDL_Surface *surf = renwin_get_surface(&window_renderer); + FR_Clip_Area clip = clip_area_from_rect(window_renderer.clip); const char *p = text; unsigned codepoint; - SDL_Surface *surf = SDL_GetWindowSurface(window); const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b }; while (*p) { FR_Color color_rep; @@ -358,17 +357,28 @@ void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int } if (color.a != 0) { FR_Blend_Glyph(font->renderer, &clip, - x_subpixel, y, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep); + x_subpixel, y_pixel, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep); } x_subpixel += xadvance_original_cp; } } -void ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color, + +void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, 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, replacements, replace_color); + const int surface_scale = renwin_surface_scale(&window_renderer); + RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale); + draw_text_impl(font, text, x_subpixel, surface_scale * y, color, replacements, replace_color); +} + +void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color, + CPReplaceTable *replacements, RenColor replace_color) +{ + const int surface_scale = renwin_surface_scale(&window_renderer); + RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale); + const int subpixel_scale = surface_scale * FR_Subpixel_Scale(font->renderer); + draw_text_impl(font, text, subpixel_scale * x, surface_scale * y, color, replacements, replace_color); } // Could be declared as static inline @@ -385,7 +395,8 @@ 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); +int ren_get_font_subpixel_scale(FontDesc *font_desc) { + const int surface_scale = renwin_surface_scale(&window_renderer); + RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale); + return FR_Subpixel_Scale(font->renderer) * surface_scale; } - diff --git a/src/renderer.h b/src/renderer.h index 2b1f29d3..bab059bb 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -3,9 +3,9 @@ #include #include +#include "fontdesc.h" typedef struct RenImage RenImage; -typedef struct RenFont RenFont; enum { RenFontAntialiasingMask = 1, @@ -36,26 +36,29 @@ typedef struct CPReplaceTable CPReplaceTable; void ren_init(SDL_Window *win); +void ren_resize_window(); void ren_update_rects(RenRect *rects, int count); void ren_set_clip_rect(RenRect rect); -void ren_get_size(int *x, int *y); +void ren_get_size(int *x, int *y); /* Reports the size in points. */ +void ren_free_window_resources(); RenImage* ren_new_image(int width, int height); void ren_free_image(RenImage *image); RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags); +int ren_verify_font(const char *filename); void ren_free_font(RenFont *font); void ren_set_font_tab_size(RenFont *font, int n); int ren_get_font_tab_size(RenFont *font); -int ren_get_font_width(RenFont *font, const char *text, int *subpixel_scale); -int ren_get_font_height(RenFont *font); -int ren_get_font_subpixel_scale(RenFont *font); + +int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale); +int ren_get_font_height(FontDesc *font_desc); +int ren_get_font_subpixel_scale(FontDesc *font_desc); 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, 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_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color); +void ren_draw_text_subpixel(FontDesc *font_desc, 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); diff --git a/src/renwindow.c b/src/renwindow.c new file mode 100644 index 00000000..0800f7c8 --- /dev/null +++ b/src/renwindow.c @@ -0,0 +1,125 @@ +#include +#include "renwindow.h" + +#ifdef LITE_USE_SDL_RENDERER +static int query_surface_scale(RenWindow *ren) { + int w_pixels, h_pixels; + int w_points, h_points; + SDL_GL_GetDrawableSize(ren->window, &w_pixels, &h_pixels); + SDL_GetWindowSize(ren->window, &w_points, &h_points); + /* We consider that the ratio pixel/point will always be an integer and + it is the same along the x and the y axis. */ + assert(w_pixels % w_points == 0 && h_pixels % h_points == 0 && w_pixels / w_points == h_pixels / h_points); + return w_pixels / w_points; +} +#endif + + +void renwin_init_surface(RenWindow *ren) { +#ifdef LITE_USE_SDL_RENDERER + if (ren->surface) { + SDL_FreeSurface(ren->surface); + } + int w, h; + SDL_GL_GetDrawableSize(ren->window, &w, &h); + ren->surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_BGRA32); + ren->surface_scale = query_surface_scale(ren); +#endif +} + +int renwin_surface_scale(RenWindow *ren) { +#ifdef LITE_USE_SDL_RENDERER + return ren->surface_scale; +#else + return 1; +#endif +} + + +static RenRect scaled_rect(const RenRect rect, const int scale) { + return (RenRect) {rect.x * scale, rect.y * scale, rect.width * scale, rect.height * scale}; +} + + +void renwin_clip_to_surface(RenWindow *ren) { + SDL_Surface *surface = renwin_get_surface(ren); + ren->clip = (RenRect) {0, 0, surface->w, surface->h}; +} + + +void renwin_set_clip_rect(RenWindow *ren, RenRect rect) { + ren->clip = scaled_rect(rect, renwin_surface_scale(ren)); +} + + +SDL_Surface *renwin_get_surface(RenWindow *ren) { +#ifdef LITE_USE_SDL_RENDERER + return ren->surface; +#else + return SDL_GetWindowSurface(ren->window); +#endif +} + +#ifdef LITE_USE_SDL_RENDERER +static void setup_renderer(RenWindow *ren, int w, int h) { + /* Note that w and h here should always be in pixels and obtained from + a call to SDL_GL_GetDrawableSize(). */ + if (ren->renderer) { + SDL_DestroyRenderer(ren->renderer); + SDL_DestroyTexture(ren->texture); + } + ren->renderer = SDL_CreateRenderer(ren->window, -1, 0); + ren->texture = SDL_CreateTexture(ren->renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, w, h); + ren->surface_scale = query_surface_scale(ren); +} +#endif + +void renwin_resize_surface(RenWindow *ren) { +#ifdef LITE_USE_SDL_RENDERER + int new_w, new_h; + SDL_GL_GetDrawableSize(ren->window, &new_w, &new_h); + /* Note that (w, h) may differ from (new_w, new_h) on retina displays. */ + if (new_w != ren->surface->w || new_h != ren->surface->h) { + renwin_init_surface(ren); + renwin_clip_to_surface(ren); + setup_renderer(ren, new_w, new_h); + } +#endif +} + +void renwin_show_window(RenWindow *ren) { + SDL_ShowWindow(ren->window); +#ifdef LITE_USE_SDL_RENDERER + int w, h; + SDL_GL_GetDrawableSize(ren->window, &w, &h); + setup_renderer(ren, w, h); +#endif +} + +void renwin_update_rects(RenWindow *ren, RenRect *rects, int count) { +#ifdef LITE_USE_SDL_RENDERER + const int scale = ren->surface_scale; + for (int i = 0; i < count; i++) { + const RenRect *r = &rects[i]; + const int x = scale * r->x, y = scale * r->y; + const int w = scale * r->width, h = scale * r->height; + const SDL_Rect sr = {.x = x, .y = y, .w = w, .h = h}; + int32_t *pixels = ((int32_t *) ren->surface->pixels) + x + ren->surface->w * y; + SDL_UpdateTexture(ren->texture, &sr, pixels, ren->surface->w * 4); + } + SDL_RenderCopy(ren->renderer, ren->texture, NULL, NULL); + SDL_RenderPresent(ren->renderer); +#else + SDL_UpdateWindowSurfaceRects(ren->window, (SDL_Rect*) rects, count); +#endif +} + +void renwin_free(RenWindow *ren) { + SDL_DestroyWindow(ren->window); + ren->window = NULL; +#ifdef LITE_USE_SDL_RENDERER + SDL_DestroyRenderer(ren->renderer); + SDL_DestroyTexture(ren->texture); + SDL_FreeSurface(ren->surface); +#endif +} diff --git a/src/renwindow.h b/src/renwindow.h new file mode 100644 index 00000000..8edc03d8 --- /dev/null +++ b/src/renwindow.h @@ -0,0 +1,25 @@ +#include +#include "renderer.h" + +struct RenWindow { + SDL_Window *window; + RenRect clip; /* Clipping rect in pixel coordinates. */ +#ifdef LITE_USE_SDL_RENDERER + SDL_Renderer *renderer; + SDL_Texture *texture; + SDL_Surface *surface; + int surface_scale; +#endif +}; +typedef struct RenWindow RenWindow; + +void renwin_init_surface(RenWindow *ren); +int renwin_surface_scale(RenWindow *ren); +void renwin_clip_to_surface(RenWindow *ren); +void renwin_set_clip_rect(RenWindow *ren, RenRect rect); +void renwin_resize_surface(RenWindow *ren); +void renwin_show_window(RenWindow *ren); +void renwin_update_rects(RenWindow *ren, RenRect *rects, int count); +void renwin_free(RenWindow *ren); +SDL_Surface *renwin_get_surface(RenWindow *ren); +