Customized fork of github.com/rxi/lite

Compare changes

Choose any two refs to compare.

+16
.editorconfig
···
··· 1 + root = true 2 + 3 + [*.{c,h}] 4 + charset = utf-8 5 + end_of_line = lf 6 + indent_style = tab 7 + insert_final_newline = true 8 + trim_trailing_whitespace = true 9 + 10 + [*.{lua}] 11 + charset = utf-8 12 + end_of_line = lf 13 + indent_style = space 14 + indent_size = 2 15 + insert_final_newline = true 16 + trim_trailing_whitespace = true
+2
.gitignore
···
··· 1 + /lite 2 + /*.o
-19
LICENSE
··· 1 - Copyright (c) 2020 rxi 2 - 3 - Permission is hereby granted, free of charge, to any person obtaining a copy of 4 - this software and associated documentation files (the "Software"), to deal in 5 - the Software without restriction, including without limitation the rights to 6 - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 - of the Software, and to permit persons to whom the Software is furnished to do 8 - so, subject to the following conditions: 9 - 10 - The above copyright notice and this permission notice shall be included in all 11 - copies or substantial portions of the Software. 12 - 13 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 - SOFTWARE.
···
-39
README.md
··· 1 - # lite 2 - ![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png) 3 - 4 - A lightweight text editor written in Lua 5 - 6 - * **[Get lite](https://github.com/rxi/lite/releases/latest)** โ€” Download 7 - for Windows and Linux 8 - * **[Get plugins](https://github.com/rxi/lite-plugins)** โ€” Add additional 9 - functionality 10 - * **[Get color themes](https://github.com/rxi/lite-colors)** โ€” Add additional colors 11 - themes 12 - 13 - ## Overview 14 - lite is a lightweight text editor written mostly in Lua โ€” it aims to provide 15 - something practical, pretty, *small* and fast, implemented as simply as 16 - possible; easy to modify and extend, or to use without doing either. 17 - 18 - ## Customization 19 - Additional functionality can be added through plugins which are available from 20 - the [plugins repository](https://github.com/rxi/lite-plugins); additional color 21 - themes can be found in the [colors repository](https://github.com/rxi/lite-colors). 22 - The editor can be customized by making changes to the 23 - [user module](data/user/init.lua). 24 - 25 - ## Building 26 - You can build the project yourself on Linux using the provided `build.sh` 27 - script. Note that the project does not need to be rebuilt if you are only making 28 - changes to the Lua portion of the code. 29 - 30 - ## Contributing 31 - Any additional functionality that can be added through a plugin should be done 32 - so as a plugin, after which a pull request to the 33 - [plugins repository](https://github.com/rxi/lite-plugins) can be made. In hopes 34 - of remaining lightweight, pull requests adding additional functionality to the 35 - core will likely not be merged. Bug reports and bug fixes are welcome. 36 - 37 - ## License 38 - This project is free software; you can redistribute it and/or modify it under 39 - the terms of the MIT license. See [LICENSE](LICENSE) for details.
···
+16
build.bat
···
··· 1 + @echo off 2 + 3 + rem download this: 4 + rem https://nuwen.net/mingw.html 5 + 6 + echo compiling (windows)... 7 + 8 + windres res.rc -O coff -o res.res 9 + gcc src/*.c src/api/*.c src/lib/lua52/*.c src/lib/stb/*.c^ 10 + -O3 -s -std=gnu11 -fno-strict-aliasing -Isrc -DLUA_USE_POPEN^ 11 + -Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include^ 12 + -lmingw32 -lm -lSDL2main -lSDL2 -Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib^ 13 + -mwindows res.res^ 14 + -o lite.exe 15 + 16 + echo done
+2 -2
build.sh
··· 1 #!/bin/bash 2 3 - cflags="-Wall -O3 -g -std=gnu11 -Isrc" 4 - lflags="-lSDL2 -lm" 5 6 if [[ $* == *windows* ]]; then 7 platform="windows"
··· 1 #!/bin/bash 2 3 + cflags="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc" 4 + lflags="-lSDL3 -lm" 5 6 if [[ $* == *windows* ]]; then 7 platform="windows"
+4
compile_flags.txt
···
··· 1 + -Wall 2 + -Isrc 3 + -fno-strict-aliasing 4 + -DLUA_USE_POSIX
+2 -2
data/core/commands/core.lua
··· 35 end) 36 end, 37 38 - ["core:command-finder"] = function() 39 local commands = command.get_all_valid() 40 core.command_view:enter("Do Command", function(text, item) 41 if item then ··· 54 end) 55 end, 56 57 - ["core:file-finder"] = function() 58 core.command_view:enter("Open File From Project", function(text, item) 59 text = item and item.text or text 60 core.root_view:open_doc(core.open_doc(text))
··· 35 end) 36 end, 37 38 + ["core:find-command"] = function() 39 local commands = command.get_all_valid() 40 core.command_view:enter("Do Command", function(text, item) 41 if item then ··· 54 end) 55 end, 56 57 + ["core:find-file"] = function() 58 core.command_view:enter("Open File From Project", function(text, item) 59 text = item and item.text or text 60 core.root_view:open_doc(core.open_doc(text))
+7 -5
data/core/commands/doc.lua
··· 160 local line1, _, line2 = doc():get_selection(true) 161 if line1 == line2 then line2 = line2 + 1 end 162 local text = doc():get_text(line1, 1, line2, math.huge) 163 - text = text:gsub("\n[\t ]*", " ") 164 doc():insert(line1, 1, text) 165 doc():remove(line1, #text + 1, line2, math.huge) 166 if doc():has_selection() then ··· 320 local translations = { 321 ["previous-char"] = translate.previous_char, 322 ["next-char"] = translate.next_char, 323 - ["previous-word-boundary"] = translate.previous_word_boundary, 324 - ["next-word-boundary"] = translate.next_word_boundary, 325 - ["previous-start-of-block"] = translate.previous_start_of_block, 326 - ["next-start-of-block"] = translate.next_start_of_block, 327 ["start-of-doc"] = translate.start_of_doc, 328 ["end-of-doc"] = translate.end_of_doc, 329 ["start-of-line"] = translate.start_of_line,
··· 160 local line1, _, line2 = doc():get_selection(true) 161 if line1 == line2 then line2 = line2 + 1 end 162 local text = doc():get_text(line1, 1, line2, math.huge) 163 + text = text:gsub("(.-)\n[\t ]*", function(x) 164 + return x:find("^%s*$") and x or x .. " " 165 + end) 166 doc():insert(line1, 1, text) 167 doc():remove(line1, #text + 1, line2, math.huge) 168 if doc():has_selection() then ··· 322 local translations = { 323 ["previous-char"] = translate.previous_char, 324 ["next-char"] = translate.next_char, 325 + ["previous-word-start"] = translate.previous_word_start, 326 + ["next-word-end"] = translate.next_word_end, 327 + ["previous-block-start"] = translate.previous_block_start, 328 + ["next-block-end"] = translate.next_block_end, 329 ["start-of-doc"] = translate.start_of_doc, 330 ["end-of-doc"] = translate.end_of_doc, 331 ["start-of-line"] = translate.start_of_line,
+4 -3
data/core/commands/findreplace.lua
··· 45 else 46 core.error("Couldn't find %q", text) 47 dv.doc:set_selection(table.unpack(sel)) 48 end 49 50 end, function(text) 51 local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text) 52 - if text == "" then 53 - dv.doc:set_selection(table.unpack(sel)) 54 - elseif ok and line1 then 55 dv.doc:set_selection(line2, col2, line1, col1) 56 dv:scroll_to_line(line2, true) 57 found = true 58 else 59 found = false 60 end 61 62 end, function(explicit) 63 if explicit then 64 dv.doc:set_selection(table.unpack(sel)) 65 end 66 end) 67 end
··· 45 else 46 core.error("Couldn't find %q", text) 47 dv.doc:set_selection(table.unpack(sel)) 48 + dv:scroll_to_make_visible(sel[1], sel[2]) 49 end 50 51 end, function(text) 52 local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text) 53 + if ok and line1 and text ~= "" then 54 dv.doc:set_selection(line2, col2, line1, col1) 55 dv:scroll_to_line(line2, true) 56 found = true 57 else 58 + dv.doc:set_selection(table.unpack(sel)) 59 found = false 60 end 61 62 end, function(explicit) 63 if explicit then 64 dv.doc:set_selection(table.unpack(sel)) 65 + dv:scroll_to_make_visible(sel[1], sel[2]) 66 end 67 end) 68 end
+1 -1
data/core/doc/highlighter.lua
··· 46 47 48 function Highlighter:invalidate(idx) 49 - self.first_invalid_line = idx 50 self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines) 51 end 52
··· 46 47 48 function Highlighter:invalidate(idx) 49 + self.first_invalid_line = math.min(self.first_invalid_line, idx) 50 self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines) 51 end 52
+2 -1
data/core/doc/init.lua
··· 56 57 58 function Doc:reset_syntax() 59 - local syn = syntax.get(self.filename or "") 60 if self.syntax ~= syn then 61 self.syntax = syn 62 self.highlighter:reset()
··· 56 57 58 function Doc:reset_syntax() 59 + local header = self:get_text(1, 1, self:position_offset(1, 1, 128)) 60 + local syn = syntax.get(self.filename or "", header) 61 if self.syntax ~= syn then 62 self.syntax = syn 63 self.highlighter:reset()
+27 -28
data/core/doc/translate.lua
··· 28 end 29 30 31 - function translate.previous_word_boundary(doc, line, col) 32 - local char = doc:get_char(doc:position_offset(line, col, -1)) 33 - local inword = not is_non_word(char) 34 - repeat 35 - local line2, col2 = line, col 36 - line, col = doc:position_offset(line, col, -1) 37 - if line == line2 and col == col2 then 38 break 39 end 40 - local c = doc:get_char(doc:position_offset(line, col, -1)) 41 - until inword and is_non_word(c) or not inword and c ~= char 42 - return line, col 43 end 44 45 46 - function translate.next_word_boundary(doc, line, col) 47 - local char = doc:get_char(line, col) 48 - local inword = not is_non_word(char) 49 - repeat 50 - local line2, col2 = line, col 51 - line, col = doc:position_offset(line, col, 1) 52 - if line == line2 and col == col2 then 53 break 54 end 55 - local c = doc:get_char(line, col) 56 - until inword and is_non_word(c) or not inword and c ~= char 57 - return line, col 58 end 59 60 ··· 86 end 87 88 89 - function translate.previous_start_of_block(doc, line, col) 90 while true do 91 line = line - 1 92 if line <= 1 then 93 return 1, 1 94 end 95 - if doc.lines[line-1]:match("^%s*$") 96 - and not doc.lines[line]:match("^%s*$") then 97 return line, (doc.lines[line]:find("%S")) 98 end 99 end 100 end 101 102 103 - function translate.next_start_of_block(doc, line, col) 104 while true do 105 - line = line + 1 106 if line >= #doc.lines then 107 return #doc.lines, 1 108 end 109 - if doc.lines[line-1]:match("^%s*$") 110 - and not doc.lines[line]:match("^%s*$") then 111 - return line, (doc.lines[line]:find("%S")) 112 end 113 end 114 end 115
··· 28 end 29 30 31 + function translate.previous_word_start(doc, line, col) 32 + local prev 33 + while line > 1 or col > 1 do 34 + local l, c = doc:position_offset(line, col, -1) 35 + local char = doc:get_char(l, c) 36 + if prev and prev ~= char or not is_non_word(char) then 37 break 38 end 39 + prev, line, col = char, l, c 40 + end 41 + return translate.start_of_word(doc, line, col) 42 end 43 44 45 + function translate.next_word_end(doc, line, col) 46 + local prev 47 + local end_line, end_col = translate.end_of_doc(doc, line, col) 48 + while line < end_line or col < end_col do 49 + local char = doc:get_char(line, col) 50 + if prev and prev ~= char or not is_non_word(char) then 51 break 52 end 53 + line, col = doc:position_offset(line, col, 1) 54 + prev = char 55 + end 56 + return translate.end_of_word(doc, line, col) 57 end 58 59 ··· 85 end 86 87 88 + function translate.previous_block_start(doc, line, col) 89 while true do 90 line = line - 1 91 if line <= 1 then 92 return 1, 1 93 end 94 + if doc.lines[line-1]:find("^%s*$") 95 + and not doc.lines[line]:find("^%s*$") then 96 return line, (doc.lines[line]:find("%S")) 97 end 98 end 99 end 100 101 102 + function translate.next_block_end(doc, line, col) 103 while true do 104 if line >= #doc.lines then 105 return #doc.lines, 1 106 end 107 + if doc.lines[line+1]:find("^%s*$") 108 + and not doc.lines[line]:find("^%s*$") then 109 + return line+1, #doc.lines[line+1] 110 end 111 + line = line + 1 112 end 113 end 114
+34 -19
data/core/docview.lua
··· 192 end 193 194 195 function DocView:on_mouse_pressed(button, x, y, clicks) 196 local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks) 197 if caught then 198 return 199 end 200 - local line, col = self:resolve_screen_position(x, y) 201 - if clicks == 2 then 202 - local line1, col1 = translate.start_of_word(self.doc, line, col) 203 - local line2, col2 = translate.end_of_word(self.doc, line, col) 204 - self.doc:set_selection(line2, col2, line1, col1) 205 - elseif clicks == 3 then 206 - if line == #self.doc.lines then 207 - self.doc:insert(math.huge, math.huge, "\n") 208 end 209 - self.doc:set_selection(line + 1, 1, line, 1) 210 else 211 - local line2, col2 212 - if keymap.modkeys["shift"] then 213 - line2, col2 = select(3, self.doc:get_selection()) 214 - end 215 - self.doc:set_selection(line, col, line2, col2) 216 - self.mouse_selecting = true 217 end 218 self.blink_timer = 0 219 end ··· 229 end 230 231 if self.mouse_selecting then 232 - local _, _, line2, col2 = self.doc:get_selection() 233 - local line1, col1 = self:resolve_screen_position(x, y) 234 - self.doc:set_selection(line1, col1, line2, col2) 235 end 236 end 237 238 239 function DocView:on_mouse_released(button) 240 DocView.super.on_mouse_released(self, button) 241 - self.mouse_selecting = false 242 end 243 244
··· 192 end 193 194 195 + local function mouse_selection(doc, clicks, line1, col1, line2, col2) 196 + local swap = line2 < line1 or line2 == line1 and col2 <= col1 197 + if swap then 198 + line1, col1, line2, col2 = line2, col2, line1, col1 199 + end 200 + if clicks == 2 then 201 + line1, col1 = translate.start_of_word(doc, line1, col1) 202 + line2, col2 = translate.end_of_word(doc, line2, col2) 203 + elseif clicks == 3 then 204 + if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then 205 + doc:insert(math.huge, math.huge, "\n") 206 + end 207 + line1, col1, line2, col2 = line1, 1, line2 + 1, 1 208 + end 209 + if swap then 210 + return line2, col2, line1, col1 211 + end 212 + return line1, col1, line2, col2 213 + end 214 + 215 + 216 function DocView:on_mouse_pressed(button, x, y, clicks) 217 local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks) 218 if caught then 219 return 220 end 221 + if keymap.modkeys["shift"] then 222 + if clicks == 1 then 223 + local line1, col1 = select(3, self.doc:get_selection()) 224 + local line2, col2 = self:resolve_screen_position(x, y) 225 + self.doc:set_selection(line2, col2, line1, col1) 226 end 227 else 228 + local line, col = self:resolve_screen_position(x, y) 229 + self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col)) 230 + self.mouse_selecting = { line, col, clicks = clicks } 231 end 232 self.blink_timer = 0 233 end ··· 243 end 244 245 if self.mouse_selecting then 246 + local l1, c1 = self:resolve_screen_position(x, y) 247 + local l2, c2 = table.unpack(self.mouse_selecting) 248 + local clicks = self.mouse_selecting.clicks 249 + self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2)) 250 end 251 end 252 253 254 function DocView:on_mouse_released(button) 255 DocView.super.on_mouse_released(self, button) 256 + self.mouse_selecting = nil 257 end 258 259
+11 -17
data/core/init.lua
··· 82 CommandView = require "core.commandview" 83 Doc = require "core.doc" 84 85 - local project_dir = "." 86 local files = {} 87 for i = 2, #ARGS do 88 local info = system.get_file_info(ARGS[i]) or {} ··· 126 end 127 128 129 - math.randomseed(system.get_time() * 1000) 130 - 131 - local function uid() 132 - return string.gsub("xxxxxx", ".", function() 133 - local chars = "0123456789abcdefghijklmnopqrstuvwxyz" 134 - local n = math.random(#chars) 135 - return chars:sub(n, n) 136 - end) 137 - end 138 - 139 - local temp_file_prefix = ".temp_" .. uid() 140 141 local function delete_temp_files() 142 for _, filename in ipairs(system.list_dir(EXEDIR)) do ··· 147 end 148 149 function core.temp_filename(ext) 150 - return EXEDIR .. PATHSEP .. temp_file_prefix .. uid() .. (ext or "") 151 end 152 153 ··· 406 407 -- update window title 408 local name = core.active_view:get_name() 409 - if name ~= "---" then 410 - system.set_window_title(name .. " - lite") 411 - else 412 - system.set_window_title("lite") 413 end 414 415 -- draw
··· 82 CommandView = require "core.commandview" 83 Doc = require "core.doc" 84 85 + local project_dir = EXEDIR 86 local files = {} 87 for i = 2, #ARGS do 88 local info = system.get_file_info(ARGS[i]) or {} ··· 126 end 127 128 129 + local temp_uid = (system.get_time() * 1000) % 0xffffffff 130 + local temp_file_prefix = string.format(".lite_temp_%08x", temp_uid) 131 + local temp_file_counter = 0 132 133 local function delete_temp_files() 134 for _, filename in ipairs(system.list_dir(EXEDIR)) do ··· 139 end 140 141 function core.temp_filename(ext) 142 + temp_file_counter = temp_file_counter + 1 143 + return EXEDIR .. PATHSEP .. temp_file_prefix 144 + .. string.format("%06x", temp_file_counter) .. (ext or "") 145 end 146 147 ··· 400 401 -- update window title 402 local name = core.active_view:get_name() 403 + local title = (name ~= "---") and (name .. " - lite") or "lite" 404 + if title ~= core.window_title then 405 + system.set_window_title(title) 406 + core.window_title = title 407 end 408 409 -- draw
+15 -14
data/core/keymap.lua
··· 84 85 86 keymap.add { 87 - ["ctrl+shift+p"] = "core:command-finder", 88 - ["ctrl+p"] = "core:file-finder", 89 ["ctrl+o"] = "core:open-file", 90 ["ctrl+n"] = "core:new-doc", 91 ["alt+return"] = "core:toggle-fullscreen", ··· 132 ["shift+tab"] = "doc:unindent", 133 ["backspace"] = "doc:backspace", 134 ["shift+backspace"] = "doc:backspace", 135 - ["ctrl+backspace"] = "doc:delete-to-previous-word-boundary", 136 - ["ctrl+shift+backspace"] = "doc:delete-to-previous-word-boundary", 137 ["delete"] = "doc:delete", 138 ["shift+delete"] = "doc:delete", 139 - ["ctrl+delete"] = "doc:delete-to-next-word-boundary", 140 - ["ctrl+shift+delete"] = "doc:delete-to-next-word-boundary", 141 ["return"] = { "command:submit", "doc:newline" }, 142 ["ctrl+return"] = "doc:newline-below", 143 ["ctrl+shift+return"] = "doc:newline-above", 144 ["ctrl+j"] = "doc:join-lines", ··· 155 ["right"] = "doc:move-to-next-char", 156 ["up"] = { "command:select-previous", "doc:move-to-previous-line" }, 157 ["down"] = { "command:select-next", "doc:move-to-next-line" }, 158 - ["ctrl+left"] = "doc:move-to-previous-word-boundary", 159 - ["ctrl+right"] = "doc:move-to-next-word-boundary", 160 - ["ctrl+["] = "doc:move-to-previous-start-of-block", 161 - ["ctrl+]"] = "doc:move-to-next-start-of-block", 162 ["home"] = "doc:move-to-start-of-line", 163 ["end"] = "doc:move-to-end-of-line", 164 ["ctrl+home"] = "doc:move-to-start-of-doc", ··· 170 ["shift+right"] = "doc:select-to-next-char", 171 ["shift+up"] = "doc:select-to-previous-line", 172 ["shift+down"] = "doc:select-to-next-line", 173 - ["ctrl+shift+left"] = "doc:select-to-previous-word-boundary", 174 - ["ctrl+shift+right"] = "doc:select-to-next-word-boundary", 175 - ["ctrl+shift+["] = "doc:select-to-previous-start-of-block", 176 - ["ctrl+shift+]"] = "doc:select-to-next-start-of-block", 177 ["shift+home"] = "doc:select-to-start-of-line", 178 ["shift+end"] = "doc:select-to-end-of-line", 179 ["ctrl+shift+home"] = "doc:select-to-start-of-doc",
··· 84 85 86 keymap.add { 87 + ["ctrl+shift+p"] = "core:find-command", 88 + ["ctrl+p"] = "core:find-file", 89 ["ctrl+o"] = "core:open-file", 90 ["ctrl+n"] = "core:new-doc", 91 ["alt+return"] = "core:toggle-fullscreen", ··· 132 ["shift+tab"] = "doc:unindent", 133 ["backspace"] = "doc:backspace", 134 ["shift+backspace"] = "doc:backspace", 135 + ["ctrl+backspace"] = "doc:delete-to-previous-word-start", 136 + ["ctrl+shift+backspace"] = "doc:delete-to-previous-word-start", 137 ["delete"] = "doc:delete", 138 ["shift+delete"] = "doc:delete", 139 + ["ctrl+delete"] = "doc:delete-to-next-word-end", 140 + ["ctrl+shift+delete"] = "doc:delete-to-next-word-end", 141 ["return"] = { "command:submit", "doc:newline" }, 142 + ["keypad enter"] = { "command:submit", "doc:newline" }, 143 ["ctrl+return"] = "doc:newline-below", 144 ["ctrl+shift+return"] = "doc:newline-above", 145 ["ctrl+j"] = "doc:join-lines", ··· 156 ["right"] = "doc:move-to-next-char", 157 ["up"] = { "command:select-previous", "doc:move-to-previous-line" }, 158 ["down"] = { "command:select-next", "doc:move-to-next-line" }, 159 + ["ctrl+left"] = "doc:move-to-previous-word-start", 160 + ["ctrl+right"] = "doc:move-to-next-word-end", 161 + ["ctrl+["] = "doc:move-to-previous-block-start", 162 + ["ctrl+]"] = "doc:move-to-next-block-end", 163 ["home"] = "doc:move-to-start-of-line", 164 ["end"] = "doc:move-to-end-of-line", 165 ["ctrl+home"] = "doc:move-to-start-of-doc", ··· 171 ["shift+right"] = "doc:select-to-next-char", 172 ["shift+up"] = "doc:select-to-previous-line", 173 ["shift+down"] = "doc:select-to-next-line", 174 + ["ctrl+shift+left"] = "doc:select-to-previous-word-start", 175 + ["ctrl+shift+right"] = "doc:select-to-next-word-end", 176 + ["ctrl+shift+["] = "doc:select-to-previous-block-start", 177 + ["ctrl+shift+]"] = "doc:select-to-next-block-end", 178 ["shift+home"] = "doc:select-to-start-of-line", 179 ["shift+end"] = "doc:select-to-end-of-line", 180 ["ctrl+shift+home"] = "doc:select-to-start-of-doc",
+25 -12
data/core/rootview.lua
··· 9 10 local EmptyView = View:extend() 11 12 - function EmptyView:draw() 13 - self:draw_background(style.background) 14 - local pos = self.position 15 - local x, y, w, h = pos.x, pos.y, self.size.x, self.size.y 16 - local _, y = common.draw_text(style.big_font, style.dim, "lite", "center", x, y, w, h) 17 local lines = { 18 - { fmt = "%s to run a command", cmd = "core:command-finder" }, 19 - { fmt = "%s to open a file from the project", cmd = "core:file-finder" }, 20 } 21 - local th = style.font:get_height() 22 for _, line in ipairs(lines) do 23 local text = string.format(line.fmt, keymap.get_binding(line.cmd)) 24 - y = y + style.padding.y 25 - common.draw_text(style.font, style.dim, text, "center", x, y, w, th) 26 - y = y + th 27 end 28 end 29 30 ··· 338 color = style.text 339 end 340 core.push_clip_rect(x, y, w, h) 341 - common.draw_text(style.font, color, text, "center", x, y, w, h) 342 core.pop_clip_rect() 343 end 344
··· 9 10 local EmptyView = View:extend() 11 12 + local function draw_text(x, y, color) 13 + local th = style.big_font:get_height() 14 + local dh = th + style.padding.y * 2 15 + x = renderer.draw_text(style.big_font, "lite", x, y + (dh - th) / 2, color) 16 + x = x + style.padding.x 17 + renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color) 18 local lines = { 19 + { fmt = "%s to run a command", cmd = "core:find-command" }, 20 + { fmt = "%s to open a file from the project", cmd = "core:find-file" }, 21 } 22 + th = style.font:get_height() 23 + y = y + (dh - th * 2 - style.padding.y) / 2 24 + local w = 0 25 for _, line in ipairs(lines) do 26 local text = string.format(line.fmt, keymap.get_binding(line.cmd)) 27 + w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color)) 28 + y = y + th + style.padding.y 29 end 30 + return w, dh 31 + end 32 + 33 + function EmptyView:draw() 34 + self:draw_background(style.background) 35 + local w, h = draw_text(0, 0, { 0, 0, 0, 0 }) 36 + local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2) 37 + local y = self.position.y + (self.size.y - h) / 2 38 + draw_text(x, y, style.dim) 39 end 40 41 ··· 349 color = style.text 350 end 351 core.push_clip_rect(x, y, w, h) 352 + x, w = x + style.padding.x, w - style.padding.x * 2 353 + local align = style.font:get_width(text) > w and "left" or "center" 354 + common.draw_text(style.font, color, text, align, x, y, w, h) 355 core.pop_clip_rect() 356 end 357
+8 -3
data/core/syntax.lua
··· 11 end 12 13 14 - function syntax.get(filename) 15 for i = #syntax.items, 1, -1 do 16 local t = syntax.items[i] 17 - if common.match_pattern(filename, t.files) then 18 return t 19 end 20 end 21 - return plain_text_syntax 22 end 23 24
··· 11 end 12 13 14 + local function find(string, field) 15 for i = #syntax.items, 1, -1 do 16 local t = syntax.items[i] 17 + if common.match_pattern(string, t[field] or {}) then 18 return t 19 end 20 end 21 + end 22 + 23 + function syntax.get(filename, header) 24 + return find(filename, "files") 25 + or find(header, "headers") 26 + or plain_text_syntax 27 end 28 29
+10 -8
data/plugins/language_js.lua
··· 8 { pattern = { "/%*", "%*/" }, type = "comment" }, 9 { pattern = { '"', '"', '\\' }, type = "string" }, 10 { pattern = { "'", "'", '\\' }, type = "string" }, 11 { pattern = "0x[%da-fA-F]+", type = "number" }, 12 { pattern = "-?%d+[%d%.eE]*", type = "number" }, 13 { pattern = "-?%.?%d+", type = "number" }, ··· 16 { pattern = "[%a_][%w_]*", type = "symbol" }, 17 }, 18 symbols = { 19 - ["arguments"] = "keyword2", 20 ["async"] = "keyword", 21 ["await"] = "keyword", 22 ["break"] = "keyword", ··· 32 ["else"] = "keyword", 33 ["export"] = "keyword", 34 ["extends"] = "keyword", 35 - ["false"] = "literal", 36 ["finally"] = "keyword", 37 ["for"] = "keyword", 38 ["function"] = "keyword", ··· 40 ["if"] = "keyword", 41 ["import"] = "keyword", 42 ["in"] = "keyword", 43 - ["Infinity"] = "keyword2", 44 ["instanceof"] = "keyword", 45 ["let"] = "keyword", 46 - ["NaN"] = "keyword2", 47 ["new"] = "keyword", 48 - ["null"] = "literal", 49 ["return"] = "keyword", 50 ["set"] = "keyword", 51 ["super"] = "keyword", 52 ["switch"] = "keyword", 53 - ["this"] = "keyword2", 54 ["throw"] = "keyword", 55 - ["true"] = "literal", 56 ["try"] = "keyword", 57 ["typeof"] = "keyword", 58 - ["undefined"] = "literal", 59 ["var"] = "keyword", 60 ["void"] = "keyword", 61 ["while"] = "keyword", 62 ["with"] = "keyword", 63 ["yield"] = "keyword", 64 }, 65 }
··· 8 { pattern = { "/%*", "%*/" }, type = "comment" }, 9 { pattern = { '"', '"', '\\' }, type = "string" }, 10 { pattern = { "'", "'", '\\' }, type = "string" }, 11 + { pattern = { "`", "`", '\\' }, type = "string" }, 12 { pattern = "0x[%da-fA-F]+", type = "number" }, 13 { pattern = "-?%d+[%d%.eE]*", type = "number" }, 14 { pattern = "-?%.?%d+", type = "number" }, ··· 17 { pattern = "[%a_][%w_]*", type = "symbol" }, 18 }, 19 symbols = { 20 ["async"] = "keyword", 21 ["await"] = "keyword", 22 ["break"] = "keyword", ··· 32 ["else"] = "keyword", 33 ["export"] = "keyword", 34 ["extends"] = "keyword", 35 ["finally"] = "keyword", 36 ["for"] = "keyword", 37 ["function"] = "keyword", ··· 39 ["if"] = "keyword", 40 ["import"] = "keyword", 41 ["in"] = "keyword", 42 ["instanceof"] = "keyword", 43 ["let"] = "keyword", 44 ["new"] = "keyword", 45 ["return"] = "keyword", 46 ["set"] = "keyword", 47 + ["static"] = "keyword", 48 ["super"] = "keyword", 49 ["switch"] = "keyword", 50 ["throw"] = "keyword", 51 ["try"] = "keyword", 52 ["typeof"] = "keyword", 53 ["var"] = "keyword", 54 ["void"] = "keyword", 55 ["while"] = "keyword", 56 ["with"] = "keyword", 57 ["yield"] = "keyword", 58 + ["true"] = "literal", 59 + ["false"] = "literal", 60 + ["null"] = "literal", 61 + ["undefined"] = "literal", 62 + ["arguments"] = "keyword2", 63 + ["Infinity"] = "keyword2", 64 + ["NaN"] = "keyword2", 65 + ["this"] = "keyword2", 66 }, 67 }
+2
data/plugins/language_lua.lua
··· 2 3 syntax.add { 4 files = "%.lua$", 5 comment = "--", 6 patterns = { 7 { pattern = { '"', '"', '\\' }, type = "string" }, ··· 12 { pattern = "-?0x%x+", type = "number" }, 13 { pattern = "-?%d+[%d%.eE]*", type = "number" }, 14 { pattern = "-?%.?%d+", type = "number" }, 15 { pattern = "%.%.%.?", type = "operator" }, 16 { pattern = "[<>~=]=", type = "operator" }, 17 { pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
··· 2 3 syntax.add { 4 files = "%.lua$", 5 + headers = "^#!.*[ /]lua", 6 comment = "--", 7 patterns = { 8 { pattern = { '"', '"', '\\' }, type = "string" }, ··· 13 { pattern = "-?0x%x+", type = "number" }, 14 { pattern = "-?%d+[%d%.eE]*", type = "number" }, 15 { pattern = "-?%.?%d+", type = "number" }, 16 + { pattern = "<%a+>", type = "keyword2" }, 17 { pattern = "%.%.%.?", type = "operator" }, 18 { pattern = "[<>~=]=", type = "operator" }, 19 { pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
+2 -1
data/plugins/language_python.lua
··· 1 local syntax = require "core.syntax" 2 3 syntax.add { 4 - files = "%.py$", 5 comment = "#", 6 patterns = { 7 { pattern = { "#", "\n" }, type = "comment" },
··· 1 local syntax = require "core.syntax" 2 3 syntax.add { 4 + files = { "%.py$", "%.pyw$" }, 5 + headers = "^#!.*[ /]python", 6 comment = "#", 7 patterns = { 8 { pattern = { "#", "\n" }, type = "comment" },
+1
data/plugins/language_xml.lua
··· 2 3 syntax.add { 4 files = { "%.xml$", "%.html?$" }, 5 patterns = { 6 { pattern = { "<!%-%-", "%-%->" }, type = "comment" }, 7 { pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
··· 2 3 syntax.add { 4 files = { "%.xml$", "%.html?$" }, 5 + headers = "<%?xml", 6 patterns = { 7 { pattern = { "<!%-%-", "%-%->" }, type = "comment" }, 8 { pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
+7
data/plugins/trimwhitespace.lua
··· 4 5 6 local function trim_trailing_whitespace(doc) 7 for i = 1, #doc.lines do 8 local old_text = doc:get_text(i, 1, i, math.huge) 9 local new_text = old_text:gsub("%s*$", "") 10 if old_text ~= new_text then 11 doc:insert(i, 1, new_text) 12 doc:remove(i, #new_text + 1, i, math.huge)
··· 4 5 6 local function trim_trailing_whitespace(doc) 7 + local cline, ccol = doc:get_selection() 8 for i = 1, #doc.lines do 9 local old_text = doc:get_text(i, 1, i, math.huge) 10 local new_text = old_text:gsub("%s*$", "") 11 + 12 + -- don't remove whitespace which would cause the caret to reposition 13 + if cline == i and ccol > #new_text then 14 + new_text = old_text:sub(1, ccol - 1) 15 + end 16 + 17 if old_text ~= new_text then 18 doc:insert(i, 1, new_text) 19 doc:remove(i, #new_text + 1, i, math.huge)
+1 -1
data/user/colors/summer.lua
··· 5 style.background2 = { common.color "#f2f2f2" } 6 style.background3 = { common.color "#f2f2f2" } 7 style.text = { common.color "#404040" } 8 - style.caret = { common.color "#181818" } 9 style.accent = { common.color "#fc1785" } 10 style.dim = { common.color "#b0b0b0" } 11 style.divider = { common.color "#e8e8e8" }
··· 5 style.background2 = { common.color "#f2f2f2" } 6 style.background3 = { common.color "#f2f2f2" } 7 style.text = { common.color "#404040" } 8 + style.caret = { common.color "#fc1785" } 9 style.accent = { common.color "#fc1785" } 10 style.dim = { common.color "#b0b0b0" } 11 style.divider = { common.color "#e8e8e8" }
+4 -1
data/user/init.lua
··· 5 local config = require "core.config" 6 local style = require "core.style" 7 8 -- light theme: 9 - -- require "user.colors.summer" 10 11 -- key binding: 12 -- keymap.add { ["ctrl+escape"] = "core:quit" }
··· 5 local config = require "core.config" 6 local style = require "core.style" 7 8 + config.tab_type = "hard" 9 + config.indent_size = 8 10 + 11 -- light theme: 12 + require "user.colors.fall" 13 14 -- key binding: 15 -- keymap.add { ["ctrl+escape"] = "core:quit" }
+41
doc/original-readme.md
···
··· 1 + # lite 2 + ![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png) 3 + 4 + A lightweight text editor written in Lua 5 + 6 + * **[Get lite](https://github.com/rxi/lite/releases/latest)** โ€” Download 7 + for Windows and Linux 8 + * **[Get started](doc/usage.md)** โ€” A quick overview on how to get started 9 + * **[Get plugins](https://github.com/rxi/lite-plugins)** โ€” Add additional 10 + functionality 11 + * **[Get color themes](https://github.com/rxi/lite-colors)** โ€” Add additional colors 12 + themes 13 + 14 + ## Overview 15 + lite is a lightweight text editor written mostly in Lua โ€” it aims to provide 16 + something practical, pretty, *small* and fast, implemented as simply as 17 + possible; easy to modify and extend, or to use without doing either. 18 + 19 + ## Customization 20 + Additional functionality can be added through plugins which are available from 21 + the [plugins repository](https://github.com/rxi/lite-plugins); additional color 22 + themes can be found in the [colors repository](https://github.com/rxi/lite-colors). 23 + The editor can be customized by making changes to the 24 + [user module](data/user/init.lua). 25 + 26 + ## Building 27 + You can build the project yourself on Linux using the `build.sh` script 28 + or on Windows using the `build.bat` script *([MinGW](https://nuwen.net/mingw.html) is required)*. 29 + Note that the project does not need to be rebuilt if you are only making changes 30 + to the Lua portion of the code. 31 + 32 + ## Contributing 33 + Any additional functionality that can be added through a plugin should be done 34 + so as a plugin, after which a pull request to the 35 + [plugins repository](https://github.com/rxi/lite-plugins) can be made. In hopes 36 + of remaining lightweight, pull requests adding additional functionality to the 37 + core will likely not be merged. Bug reports and bug fixes are welcome. 38 + 39 + ## License 40 + This project is free software; you can redistribute it and/or modify it under 41 + the terms of the MIT license. See [LICENSE](LICENSE) for details.
+146
doc/usage.md
···
··· 1 + # lite 2 + 3 + ![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png) 4 + 5 + ## Overview 6 + lite is a lightweight text editor written mostly in Lua โ€” it aims to provide 7 + something practical, pretty, *small* and fast, implemented as simply as 8 + possible; easy to modify and extend, or to use without doing either. 9 + 10 + 11 + ## Getting Started 12 + When lite is started it's typically opened with a *project directory* โ€” this 13 + is the directory where your project's code and other data resides. The project 14 + directory is set once when lite is started and, for the duration of the 15 + session, cannot be changed. 16 + 17 + To open lite with a specific project directory the directory name can be passed 18 + as a command-line argument *(`.` can be passed to use the current directory)* or 19 + the directory can be dragged onto either the lite executable or a running 20 + instance of lite. 21 + 22 + The main way of opening files in lite is through the `core:find-file` command 23 + โ€” this provides a fuzzy finder over all of the project's files and can be 24 + opened using the **`ctrl+p`** shortcut by default. 25 + 26 + Commands can be run using keyboard shortcuts, or by using the `core:find-command` 27 + command bound to **`ctrl+shift+p`** by default. For example, pressing 28 + `ctrl+shift+p` and typing `newdoc` then pressing `return` would open a new 29 + document. The current keyboard shortcut for a command can be seen to the right 30 + of the command name on the command finder, thus to find the shortcut for a command 31 + `ctrl+shift+p` can be pressed and the command name typed. 32 + 33 + 34 + ## User Module 35 + lite can be configured through use of the user module. The user module can be 36 + used for changing options in the config module, adding additional key bindings, 37 + loading custom color themes, modifying the style or changing any other part of 38 + lite to your personal preference. 39 + 40 + The user module is loaded by lite when the application starts, after the plugins 41 + have been loaded. 42 + 43 + The user module can be modified by running the `core:open-user-module` command 44 + or otherwise directly opening the `data/user/init.lua` file. 45 + 46 + 47 + ## Project Module 48 + The project module is an optional module which is loaded from the current 49 + project's directory when lite is started. Project modules can be useful for 50 + things like adding custom commands for project-specific build systems, or 51 + loading project-specific plugins. 52 + 53 + The project module is loaded by lite when the application starts, after both the 54 + plugins and user module have been loaded. 55 + 56 + The project module can be edited by running the `core:open-project-module` 57 + command โ€” if the module does not exist for the current project when the 58 + command is run it will be created. 59 + 60 + 61 + ## Commands 62 + Commands in lite are used both through the command finder (`ctrl+shift+p`) and 63 + by lite's keyboard shortcut system. Commands consist of 3 components: 64 + * **Name** โ€” The command name in the form of `namespace:action-name`, for 65 + example: `doc:select-all` 66 + * **Predicate** โ€” A function that returns true if the command can be ran, for 67 + example, for any document commands the predicate checks whether the active 68 + view is a document 69 + * **Function** โ€” The function which performs the command itself 70 + 71 + Commands can be added using the `command.add` function provided by the 72 + `core.command` module: 73 + ```lua 74 + local core = require "core" 75 + local command = require "core.command" 76 + 77 + command.add("core.docview", { 78 + ["doc:save"] = function() 79 + core.active_view.doc:save() 80 + core.log("Saved '%s', core.active_view.doc.filename) 81 + end 82 + }) 83 + ``` 84 + 85 + Commands can be performed programatically (eg. from another command or by your 86 + user module) by calling the `command.perform` function after requiring the 87 + `command` module: 88 + ```lua 89 + local command = require "core.command" 90 + command.perform "core:quit" 91 + ``` 92 + 93 + 94 + ## Keymap 95 + All keyboard shortcuts in lite are handled by the `core.keymap` module. A key 96 + binding in lite maps a "stroke" (eg. `ctrl+q`) to one or more commands (eg. 97 + `core:quit`). When the shortcut is pressed lite will iterate each command 98 + assigned to that key and run the *predicate function* for that command โ€” if the 99 + predicate passes it stops iterating and runs the command. 100 + 101 + An example of where this used is the default binding of the `tab` key: 102 + ``` lua 103 + ["tab"] = { "command:complete", "doc:indent" }, 104 + ``` 105 + When tab is pressed the `command:complete` command is attempted which will only 106 + succeed if the command-input at the bottom of the window is active. Otherwise 107 + the `doc:indent` command is attempted which will only succeed if we have a 108 + document as our active view. 109 + 110 + A new mapping can be added by your user module as follows: 111 + ```lua 112 + local keymap = require "core.keymap" 113 + keymap.add { ["ctrl+q"] = "core:quit" } 114 + ``` 115 + 116 + 117 + ## Plugins 118 + Plugins in lite are normal lua modules and are treated as such โ€” no 119 + complicated plugin manager is provided, and, once a plugin is loaded, it is never 120 + expected be to have to unload itself. 121 + 122 + To install a plugin simply drop it in the `data/plugins` directory โ€” installed 123 + plugins will be automatically loaded when lite starts. To uninstall a plugin the 124 + plugin file can be deleted โ€” any plugin (including those included with lite's 125 + default installation) can be deleted to remove its functionality. 126 + 127 + If you want to load a plugin only under a certain circumstance (for example, 128 + only on a given project) the plugin can be placed somewhere other than the 129 + `data/plugins` directory so that it is not automatically loaded. The plugin can 130 + then be loaded manually as needed by using the `require` function. 131 + 132 + Plugins can be downloaded from the [plugins repository](https://github.com/rxi/lite-plugins). 133 + 134 + 135 + ## Color Themes 136 + Colors themes in lite are lua modules which overwrite the color fields of lite's 137 + `core.style` module. Color themes should be placed in the `data/user/colors` 138 + directory. 139 + 140 + A color theme can be set by requiring it in your user module: 141 + ```lua 142 + require "user.colors.winter" 143 + ``` 144 + 145 + Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors). 146 +
+53
license
···
··· 1 + === emmeline/lite: MIT License === 2 + 3 + Copyright (c) 2025 emmeline 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy of 6 + this software and associated documentation files (the "Software"), to deal in 7 + the Software without restriction, including without limitation the rights to 8 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 + of the Software, and to permit persons to whom the Software is furnished to do 10 + so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE. 22 + 23 + === rxi/lite: MIT License === 24 + 25 + Copyright (c) 2020 rxi 26 + 27 + Permission is hereby granted, free of charge, to any person obtaining a copy of 28 + this software and associated documentation files (the "Software"), to deal in 29 + the Software without restriction, including without limitation the rights to 30 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 31 + of the Software, and to permit persons to whom the Software is furnished to do 32 + so, subject to the following conditions: 33 + 34 + The above copyright notice and this permission notice shall be included in all 35 + copies or substantial portions of the Software. 36 + 37 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 43 + SOFTWARE. 44 + 45 + === lua 5.4.8: MIT License === 46 + 47 + Copyright ยฉ 1994โ€“2025 Lua.org, PUC-Rio. 48 + 49 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 50 + 51 + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 52 + 53 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+31
readme
···
··· 1 + lite (emmeline's fork) 2 + ====================== 3 + 4 + A fork of the [lite](https://github.com/rxi/lite) editor by rxi. 5 + 6 + A lightweight editor written in Lua. 7 + 8 + Usage: <doc/usage.md> 9 + Plugins: <https://github.com/rxi/lite-plugins> 10 + Themes: <https://github.com/rxi/lite-colors> 11 + 12 + Build 13 + ----- 14 + 15 + $ git clone https://tangled.org/emmeline.girlkisser.top/lite 16 + $ cd lite 17 + $ ./build.sh 18 + $ ln -s $(pwd)/lite ~/.local/bin/ 19 + $ lite . 20 + 21 + Changes 22 + ------- 23 + 24 + - [x] Reformat to my code style :p 25 + - [x] Update to SDL 3 26 + - [ ] Use LuaJIT 27 + 28 + License 29 + ------- 30 + 31 + MIT License
+9 -7
src/api/api.c
··· 6 7 8 static const luaL_Reg libs[] = { 9 - { "system", luaopen_system }, 10 - { "renderer", luaopen_renderer }, 11 - { NULL, NULL } 12 }; 13 14 - void api_load_libs(lua_State *L) { 15 - for (int i = 0; libs[i].name; i++) { 16 - luaL_requiref(L, libs[i].name, libs[i].func, 1); 17 - } 18 }
··· 6 7 8 static const luaL_Reg libs[] = { 9 + { "system", luaopen_system }, 10 + { "renderer", luaopen_renderer }, 11 + { NULL, NULL } 12 }; 13 14 + void api_load_libs(lua_State *L) 15 + { 16 + for (int i = 0 ; libs[i].name ; i++) 17 + { 18 + luaL_requiref(L, libs[i].name, libs[i].func, 1); 19 + } 20 }
+78 -70
src/api/renderer.c
··· 3 #include "rencache.h" 4 5 6 - static RenColor checkcolor(lua_State *L, int idx, int def) { 7 - RenColor color; 8 - if (lua_isnoneornil(L, idx)) { 9 - return (RenColor) { def, def, def, 255 }; 10 - } 11 - lua_rawgeti(L, idx, 1); 12 - lua_rawgeti(L, idx, 2); 13 - lua_rawgeti(L, idx, 3); 14 - lua_rawgeti(L, idx, 4); 15 - color.r = luaL_checknumber(L, -4); 16 - color.g = luaL_checknumber(L, -3); 17 - color.b = luaL_checknumber(L, -2); 18 - color.a = luaL_optnumber(L, -1, 255); 19 - lua_pop(L, 4); 20 - return color; 21 } 22 23 24 - static int f_show_debug(lua_State *L) { 25 - luaL_checkany(L, 1); 26 - rencache_show_debug(lua_toboolean(L, 1)); 27 - return 0; 28 } 29 30 31 - static int f_get_size(lua_State *L) { 32 - int w, h; 33 - ren_get_size(&w, &h); 34 - lua_pushnumber(L, w); 35 - lua_pushnumber(L, h); 36 - return 2; 37 } 38 39 40 - static int f_begin_frame(lua_State *L) { 41 - rencache_begin_frame(); 42 - return 0; 43 } 44 45 46 - static int f_end_frame(lua_State *L) { 47 - rencache_end_frame(); 48 - return 0; 49 } 50 51 52 - static int f_set_clip_rect(lua_State *L) { 53 - RenRect rect; 54 - rect.x = luaL_checknumber(L, 1); 55 - rect.y = luaL_checknumber(L, 2); 56 - rect.width = luaL_checknumber(L, 3); 57 - rect.height = luaL_checknumber(L, 4); 58 - rencache_set_clip_rect(rect); 59 - return 0; 60 } 61 62 63 - static int f_draw_rect(lua_State *L) { 64 - RenRect rect; 65 - rect.x = luaL_checknumber(L, 1); 66 - rect.y = luaL_checknumber(L, 2); 67 - rect.width = luaL_checknumber(L, 3); 68 - rect.height = luaL_checknumber(L, 4); 69 - RenColor color = checkcolor(L, 5, 255); 70 - rencache_draw_rect(rect, color); 71 - return 0; 72 } 73 74 75 - static int f_draw_text(lua_State *L) { 76 - RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); 77 - const char *text = luaL_checkstring(L, 2); 78 - int x = luaL_checknumber(L, 3); 79 - int y = luaL_checknumber(L, 4); 80 - RenColor color = checkcolor(L, 5, 255); 81 - x = rencache_draw_text(*font, text, x, y, color); 82 - lua_pushnumber(L, x); 83 - return 1; 84 } 85 86 87 static const luaL_Reg lib[] = { 88 - { "show_debug", f_show_debug }, 89 - { "get_size", f_get_size }, 90 - { "begin_frame", f_begin_frame }, 91 - { "end_frame", f_end_frame }, 92 - { "set_clip_rect", f_set_clip_rect }, 93 - { "draw_rect", f_draw_rect }, 94 - { "draw_text", f_draw_text }, 95 - { NULL, NULL } 96 }; 97 98 99 int luaopen_renderer_font(lua_State *L); 100 101 - int luaopen_renderer(lua_State *L) { 102 - luaL_newlib(L, lib); 103 - luaopen_renderer_font(L); 104 - lua_setfield(L, -2, "font"); 105 - return 1; 106 }
··· 3 #include "rencache.h" 4 5 6 + static RenColor checkcolor(lua_State *L, int idx, int def) 7 + { 8 + RenColor color; 9 + if (lua_isnoneornil(L, idx)) 10 + return (RenColor){ def, def, def, 255 }; 11 + lua_rawgeti(L, idx, 1); 12 + lua_rawgeti(L, idx, 2); 13 + lua_rawgeti(L, idx, 3); 14 + lua_rawgeti(L, idx, 4); 15 + color.r = luaL_checknumber(L, -4); 16 + color.g = luaL_checknumber(L, -3); 17 + color.b = luaL_checknumber(L, -2); 18 + color.a = luaL_optnumber(L, -1, 255); 19 + lua_pop(L, 4); 20 + return color; 21 } 22 23 24 + static int f_show_debug(lua_State *L) 25 + { 26 + luaL_checkany(L, 1); 27 + rencache_show_debug(lua_toboolean(L, 1)); 28 + return 0; 29 } 30 31 32 + static int f_get_size(lua_State *L) 33 + { 34 + int w, h; 35 + ren_get_size(&w, &h); 36 + lua_pushnumber(L, w); 37 + lua_pushnumber(L, h); 38 + return 2; 39 } 40 41 42 + static int f_begin_frame(lua_State *L) 43 + { 44 + rencache_begin_frame(); 45 + return 0; 46 } 47 48 49 + static int f_end_frame(lua_State *L) 50 + { 51 + rencache_end_frame(); 52 + return 0; 53 } 54 55 56 + static int f_set_clip_rect(lua_State *L) 57 + { 58 + RenRect rect; 59 + rect.x = luaL_checknumber(L, 1); 60 + rect.y = luaL_checknumber(L, 2); 61 + rect.width = luaL_checknumber(L, 3); 62 + rect.height = luaL_checknumber(L, 4); 63 + rencache_set_clip_rect(rect); 64 + return 0; 65 } 66 67 68 + static int f_draw_rect(lua_State *L) 69 + { 70 + RenRect rect; 71 + rect.x = luaL_checknumber(L, 1); 72 + rect.y = luaL_checknumber(L, 2); 73 + rect.width = luaL_checknumber(L, 3); 74 + rect.height = luaL_checknumber(L, 4); 75 + RenColor color = checkcolor(L, 5, 255); 76 + rencache_draw_rect(rect, color); 77 + return 0; 78 } 79 80 81 + static int f_draw_text(lua_State *L) 82 + { 83 + RenFont **font = luaL_checkudata(L, 1, API_TYPE_FONT); 84 + const char *text = luaL_checkstring(L, 2); 85 + int x = luaL_checknumber(L, 3); 86 + int y = luaL_checknumber(L, 4); 87 + RenColor color = checkcolor(L, 5, 255); 88 + x = rencache_draw_text(*font, text, x, y, color); 89 + lua_pushnumber(L, x); 90 + return 1; 91 } 92 93 94 static const luaL_Reg lib[] = { 95 + { "show_debug", f_show_debug }, 96 + { "get_size", f_get_size }, 97 + { "begin_frame", f_begin_frame }, 98 + { "end_frame", f_end_frame }, 99 + { "set_clip_rect", f_set_clip_rect }, 100 + { "draw_rect", f_draw_rect }, 101 + { "draw_text", f_draw_text }, 102 + { NULL, NULL } 103 }; 104 105 106 int luaopen_renderer_font(lua_State *L); 107 108 + int luaopen_renderer(lua_State *L) 109 + { 110 + luaL_newlib(L, lib); 111 + luaopen_renderer_font(L); 112 + lua_setfield(L, -2, "font"); 113 + return 1; 114 }
+46 -38
src/api/renderer_font.c
··· 3 #include "rencache.h" 4 5 6 - static int f_load(lua_State *L) { 7 - const char *filename = luaL_checkstring(L, 1); 8 - float size = luaL_checknumber(L, 2); 9 - RenFont **self = lua_newuserdata(L, sizeof(*self)); 10 - luaL_setmetatable(L, API_TYPE_FONT); 11 - *self = ren_load_font(filename, size); 12 - if (!*self) { luaL_error(L, "failed to load font"); } 13 - return 1; 14 } 15 16 17 - static int f_set_tab_width(lua_State *L) { 18 - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 19 - int n = luaL_checknumber(L, 2); 20 - ren_set_font_tab_width(*self, n); 21 - return 0; 22 } 23 24 25 - static int f_gc(lua_State *L) { 26 - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 27 - if (*self) { rencache_free_font(*self); } 28 - return 0; 29 } 30 31 32 - static int f_get_width(lua_State *L) { 33 - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 34 - const char *text = luaL_checkstring(L, 2); 35 - lua_pushnumber(L, ren_get_font_width(*self, text) ); 36 - return 1; 37 } 38 39 40 - static int f_get_height(lua_State *L) { 41 - RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 42 - lua_pushnumber(L, ren_get_font_height(*self) ); 43 - return 1; 44 } 45 46 47 static const luaL_Reg lib[] = { 48 - { "__gc", f_gc }, 49 - { "load", f_load }, 50 - { "set_tab_width", f_set_tab_width }, 51 - { "get_width", f_get_width }, 52 - { "get_height", f_get_height }, 53 - { NULL, NULL } 54 }; 55 56 - int luaopen_renderer_font(lua_State *L) { 57 - luaL_newmetatable(L, API_TYPE_FONT); 58 - luaL_setfuncs(L, lib, 0); 59 - lua_pushvalue(L, -1); 60 - lua_setfield(L, -2, "__index"); 61 - return 1; 62 }
··· 3 #include "rencache.h" 4 5 6 + static int f_load(lua_State *L) 7 + { 8 + const char *filename = luaL_checkstring(L, 1); 9 + float size = luaL_checknumber(L, 2); 10 + RenFont **self = lua_newuserdata(L, sizeof(*self)); 11 + luaL_setmetatable(L, API_TYPE_FONT); 12 + *self = ren_load_font(filename, size); 13 + if (!*self) 14 + luaL_error(L, "failed to load font"); 15 + return 1; 16 } 17 18 19 + static int f_set_tab_width(lua_State *L) 20 + { 21 + RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 22 + int n = luaL_checknumber(L, 2); 23 + ren_set_font_tab_width(*self, n); 24 + return 0; 25 } 26 27 28 + static int f_gc(lua_State *L) 29 + { 30 + RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 31 + if (*self) 32 + rencache_free_font(*self); 33 + return 0; 34 } 35 36 37 + static int f_get_width(lua_State *L) 38 + { 39 + RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 40 + const char *text = luaL_checkstring(L, 2); 41 + lua_pushnumber(L, ren_get_font_width(*self, text) ); 42 + return 1; 43 } 44 45 46 + static int f_get_height(lua_State *L) 47 + { 48 + RenFont **self = luaL_checkudata(L, 1, API_TYPE_FONT); 49 + lua_pushnumber(L, ren_get_font_height(*self) ); 50 + return 1; 51 } 52 53 54 static const luaL_Reg lib[] = { 55 + { "__gc", f_gc }, 56 + { "load", f_load }, 57 + { "set_tab_width", f_set_tab_width }, 58 + { "get_width", f_get_width }, 59 + { "get_height", f_get_height }, 60 + { NULL, NULL } 61 }; 62 63 + int luaopen_renderer_font(lua_State *L) 64 + { 65 + luaL_newmetatable(L, API_TYPE_FONT); 66 + luaL_setfuncs(L, lib, 0); 67 + lua_pushvalue(L, -1); 68 + lua_setfield(L, -2, "__index"); 69 + return 1; 70 }
+333 -292
src/api/system.c
··· 1 - #include <SDL2/SDL.h> 2 #include <stdbool.h> 3 #include <ctype.h> 4 #include <dirent.h> 5 #include <unistd.h> ··· 8 #include "api.h" 9 #include "rencache.h" 10 #ifdef _WIN32 11 - #include <windows.h> 12 #endif 13 14 extern SDL_Window *window; 15 16 17 - static const char* button_name(int button) { 18 - switch (button) { 19 - case 1 : return "left"; 20 - case 2 : return "middle"; 21 - case 3 : return "right"; 22 - default : return "?"; 23 - } 24 } 25 26 27 - static char* key_name(char *dst, int sym) { 28 - strcpy(dst, SDL_GetKeyName(sym)); 29 - char *p = dst; 30 - while (*p) { 31 - *p = tolower(*p); 32 - p++; 33 - } 34 - return dst; 35 } 36 37 38 - static int f_poll_event(lua_State *L) { 39 - char buf[16]; 40 - int mx, my, wx, wy; 41 - SDL_Event e; 42 43 top: 44 - if ( !SDL_PollEvent(&e) ) { 45 - return 0; 46 - } 47 48 - switch (e.type) { 49 - case SDL_QUIT: 50 - lua_pushstring(L, "quit"); 51 - return 1; 52 53 - case SDL_WINDOWEVENT: 54 - if (e.window.event == SDL_WINDOWEVENT_RESIZED) { 55 - lua_pushstring(L, "resized"); 56 - lua_pushnumber(L, e.window.data1); 57 - lua_pushnumber(L, e.window.data2); 58 - return 3; 59 - } else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) { 60 - rencache_invalidate(); 61 - lua_pushstring(L, "exposed"); 62 - return 1; 63 - } 64 - /* on some systems, when alt-tabbing to the window SDL will queue up 65 - ** several KEYDOWN events for the `tab` key; we flush all keydown 66 - ** events on focus so these are discarded */ 67 - if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { 68 - SDL_FlushEvent(SDL_KEYDOWN); 69 - } 70 - goto top; 71 72 - case SDL_DROPFILE: 73 - SDL_GetGlobalMouseState(&mx, &my); 74 - SDL_GetWindowPosition(window, &wx, &wy); 75 - lua_pushstring(L, "filedropped"); 76 - lua_pushstring(L, e.drop.file); 77 - lua_pushnumber(L, mx - wx); 78 - lua_pushnumber(L, my - wy); 79 - SDL_free(e.drop.file); 80 - return 4; 81 82 - case SDL_KEYDOWN: 83 - lua_pushstring(L, "keypressed"); 84 - lua_pushstring(L, key_name(buf, e.key.keysym.sym)); 85 - return 2; 86 87 - case SDL_KEYUP: 88 - lua_pushstring(L, "keyreleased"); 89 - lua_pushstring(L, key_name(buf, e.key.keysym.sym)); 90 - return 2; 91 92 - case SDL_TEXTINPUT: 93 - lua_pushstring(L, "textinput"); 94 - lua_pushstring(L, e.text.text); 95 - return 2; 96 97 - case SDL_MOUSEBUTTONDOWN: 98 - if (e.button.button == 1) { SDL_CaptureMouse(1); } 99 - lua_pushstring(L, "mousepressed"); 100 - lua_pushstring(L, button_name(e.button.button)); 101 - lua_pushnumber(L, e.button.x); 102 - lua_pushnumber(L, e.button.y); 103 - lua_pushnumber(L, e.button.clicks); 104 - return 5; 105 106 - case SDL_MOUSEBUTTONUP: 107 - if (e.button.button == 1) { SDL_CaptureMouse(0); } 108 - lua_pushstring(L, "mousereleased"); 109 - lua_pushstring(L, button_name(e.button.button)); 110 - lua_pushnumber(L, e.button.x); 111 - lua_pushnumber(L, e.button.y); 112 - return 4; 113 114 - case SDL_MOUSEMOTION: 115 - lua_pushstring(L, "mousemoved"); 116 - lua_pushnumber(L, e.motion.x); 117 - lua_pushnumber(L, e.motion.y); 118 - lua_pushnumber(L, e.motion.xrel); 119 - lua_pushnumber(L, e.motion.yrel); 120 - return 5; 121 122 - case SDL_MOUSEWHEEL: 123 - lua_pushstring(L, "mousewheel"); 124 - lua_pushnumber(L, e.wheel.y); 125 - return 2; 126 127 - default: 128 - goto top; 129 - } 130 131 - return 0; 132 } 133 134 135 - static int f_wait_event(lua_State *L) { 136 - double n = luaL_checknumber(L, 1); 137 - lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000)); 138 - return 1; 139 } 140 141 142 - static SDL_Cursor* cursor_cache[SDL_SYSTEM_CURSOR_HAND + 1]; 143 144 static const char *cursor_opts[] = { 145 - "arrow", 146 - "ibeam", 147 - "sizeh", 148 - "sizev", 149 - "hand", 150 - NULL 151 }; 152 153 static const int cursor_enums[] = { 154 - SDL_SYSTEM_CURSOR_ARROW, 155 - SDL_SYSTEM_CURSOR_IBEAM, 156 - SDL_SYSTEM_CURSOR_SIZEWE, 157 - SDL_SYSTEM_CURSOR_SIZENS, 158 - SDL_SYSTEM_CURSOR_HAND 159 }; 160 161 - static int f_set_cursor(lua_State *L) { 162 - int opt = luaL_checkoption(L, 1, "arrow", cursor_opts); 163 - int n = cursor_enums[opt]; 164 - SDL_Cursor *cursor = cursor_cache[n]; 165 - if (!cursor) { 166 - cursor = SDL_CreateSystemCursor(n); 167 - cursor_cache[n] = cursor; 168 - } 169 - SDL_SetCursor(cursor); 170 - return 0; 171 } 172 173 174 - static int f_set_window_title(lua_State *L) { 175 - const char *title = luaL_checkstring(L, 1); 176 - SDL_SetWindowTitle(window, title); 177 - return 0; 178 } 179 180 181 static const char *window_opts[] = { "normal", "maximized", "fullscreen", 0 }; 182 enum { WIN_NORMAL, WIN_MAXIMIZED, WIN_FULLSCREEN }; 183 184 - static int f_set_window_mode(lua_State *L) { 185 - int n = luaL_checkoption(L, 1, "normal", window_opts); 186 - SDL_SetWindowFullscreen(window, 187 - n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); 188 - if (n == WIN_NORMAL) { SDL_RestoreWindow(window); } 189 - if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window); } 190 - return 0; 191 } 192 193 194 - static int f_window_has_focus(lua_State *L) { 195 - unsigned flags = SDL_GetWindowFlags(window); 196 - lua_pushboolean(L, flags & SDL_WINDOW_INPUT_FOCUS); 197 - return 1; 198 } 199 200 201 - static int f_show_confirm_dialog(lua_State *L) { 202 - const char *title = luaL_checkstring(L, 1); 203 - const char *msg = luaL_checkstring(L, 2); 204 205 - #if _WIN32 206 - int id = MessageBox(0, msg, title, MB_YESNO | MB_ICONWARNING); 207 - lua_pushboolean(L, id == IDYES); 208 209 - #else 210 - SDL_MessageBoxButtonData buttons[] = { 211 - { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes" }, 212 - { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "No" }, 213 - }; 214 - SDL_MessageBoxData data = { 215 - .title = title, 216 - .message = msg, 217 - .numbuttons = 2, 218 - .buttons = buttons, 219 - }; 220 - int buttonid; 221 - SDL_ShowMessageBox(&data, &buttonid); 222 - lua_pushboolean(L, buttonid == 1); 223 - #endif 224 - return 1; 225 } 226 227 228 - static int f_chdir(lua_State *L) { 229 - const char *path = luaL_checkstring(L, 1); 230 - int err = chdir(path); 231 - if (err) { luaL_error(L, "chdir() failed"); } 232 - return 0; 233 } 234 235 236 - static int f_list_dir(lua_State *L) { 237 - const char *path = luaL_checkstring(L, 1); 238 239 - DIR *dir = opendir(path); 240 - if (!dir) { 241 - lua_pushnil(L); 242 - lua_pushstring(L, strerror(errno)); 243 - return 2; 244 - } 245 246 - lua_newtable(L); 247 - int i = 1; 248 - struct dirent *entry; 249 - while ( (entry = readdir(dir)) ) { 250 - if (strcmp(entry->d_name, "." ) == 0) { continue; } 251 - if (strcmp(entry->d_name, "..") == 0) { continue; } 252 - lua_pushstring(L, entry->d_name); 253 - lua_rawseti(L, -2, i); 254 - i++; 255 - } 256 257 - closedir(dir); 258 - return 1; 259 } 260 261 262 #ifdef _WIN32 263 - #include <windows.h> 264 - #define realpath(x, y) _fullpath(y, x, MAX_PATH) 265 #endif 266 267 - static int f_absolute_path(lua_State *L) { 268 - const char *path = luaL_checkstring(L, 1); 269 - char *res = realpath(path, NULL); 270 - if (!res) { return 0; } 271 - lua_pushstring(L, res); 272 - free(res); 273 - return 1; 274 } 275 276 277 - static int f_get_file_info(lua_State *L) { 278 - const char *path = luaL_checkstring(L, 1); 279 280 - struct stat s; 281 - int err = stat(path, &s); 282 - if (err < 0) { 283 - lua_pushnil(L); 284 - lua_pushstring(L, strerror(errno)); 285 - return 2; 286 - } 287 288 - lua_newtable(L); 289 - lua_pushnumber(L, s.st_mtime); 290 - lua_setfield(L, -2, "modified"); 291 292 - lua_pushnumber(L, s.st_size); 293 - lua_setfield(L, -2, "size"); 294 295 - if (S_ISREG(s.st_mode)) { 296 - lua_pushstring(L, "file"); 297 - } else if (S_ISDIR(s.st_mode)) { 298 - lua_pushstring(L, "dir"); 299 - } else { 300 - lua_pushnil(L); 301 - } 302 - lua_setfield(L, -2, "type"); 303 304 - return 1; 305 } 306 307 308 - static int f_get_clipboard(lua_State *L) { 309 - char *text = SDL_GetClipboardText(); 310 - if (!text) { return 0; } 311 - lua_pushstring(L, text); 312 - SDL_free(text); 313 - return 1; 314 } 315 316 317 - static int f_set_clipboard(lua_State *L) { 318 - const char *text = luaL_checkstring(L, 1); 319 - SDL_SetClipboardText(text); 320 - return 0; 321 } 322 323 324 - static int f_get_time(lua_State *L) { 325 - double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); 326 - lua_pushnumber(L, n); 327 - return 1; 328 } 329 330 331 - static int f_sleep(lua_State *L) { 332 - double n = luaL_checknumber(L, 1); 333 - SDL_Delay(n * 1000); 334 - return 0; 335 } 336 337 338 - static int f_exec(lua_State *L) { 339 - size_t len; 340 - const char *cmd = luaL_checklstring(L, 1, &len); 341 - char *buf = malloc(len + 32); 342 - if (!buf) { luaL_error(L, "buffer allocation failed"); } 343 - #if _WIN32 344 - sprintf(buf, "cmd /c \"%s\"", cmd); 345 - WinExec(buf, SW_HIDE); 346 - #else 347 - sprintf(buf, "%s &", cmd); 348 - int res = system(buf); 349 - (void) res; 350 - #endif 351 - free(buf); 352 - return 0; 353 } 354 355 356 - static int f_fuzzy_match(lua_State *L) { 357 - const char *str = luaL_checkstring(L, 1); 358 - const char *ptn = luaL_checkstring(L, 2); 359 - int score = 0; 360 - int run = 0; 361 362 - while (*str && *ptn) { 363 - while (*str == ' ') { str++; } 364 - while (*ptn == ' ') { ptn++; } 365 - if (tolower(*str) == tolower(*ptn)) { 366 - score += run * 10 - (*str != *ptn); 367 - run++; 368 - ptn++; 369 - } else { 370 - score -= 10; 371 - run = 0; 372 - } 373 - str++; 374 - } 375 - if (*ptn) { return 0; } 376 377 - lua_pushnumber(L, score - (int) strlen(str)); 378 - return 1; 379 } 380 381 382 static const luaL_Reg lib[] = { 383 - { "poll_event", f_poll_event }, 384 - { "wait_event", f_wait_event }, 385 - { "set_cursor", f_set_cursor }, 386 - { "set_window_title", f_set_window_title }, 387 - { "set_window_mode", f_set_window_mode }, 388 - { "window_has_focus", f_window_has_focus }, 389 - { "show_confirm_dialog", f_show_confirm_dialog }, 390 - { "chdir", f_chdir }, 391 - { "list_dir", f_list_dir }, 392 - { "absolute_path", f_absolute_path }, 393 - { "get_file_info", f_get_file_info }, 394 - { "get_clipboard", f_get_clipboard }, 395 - { "set_clipboard", f_set_clipboard }, 396 - { "get_time", f_get_time }, 397 - { "sleep", f_sleep }, 398 - { "exec", f_exec }, 399 - { "fuzzy_match", f_fuzzy_match }, 400 - { NULL, NULL } 401 }; 402 403 404 - int luaopen_system(lua_State *L) { 405 - luaL_newlib(L, lib); 406 - return 1; 407 }
··· 1 + #include <SDL3/SDL.h> 2 #include <stdbool.h> 3 + #include <stdlib.h> 4 #include <ctype.h> 5 #include <dirent.h> 6 #include <unistd.h> ··· 9 #include "api.h" 10 #include "rencache.h" 11 #ifdef _WIN32 12 + # include <windows.h> 13 #endif 14 15 extern SDL_Window *window; 16 17 18 + static const char* button_name(int button) 19 + { 20 + switch (button) 21 + { 22 + case 1 : return "left"; 23 + case 2 : return "middle"; 24 + case 3 : return "right"; 25 + default : return "?"; 26 + } 27 } 28 29 30 + static char* key_name(char *dst, int sym) 31 + { 32 + strcpy(dst, SDL_GetKeyName(sym)); 33 + char *p = dst; 34 + while (*p) 35 + { 36 + *p = tolower(*p); 37 + p++; 38 + } 39 + return dst; 40 } 41 42 43 + static int f_poll_event(lua_State *L) 44 + { 45 + char buf[16]; 46 + float mx, my; 47 + int wx, wy; 48 + SDL_Event e; 49 50 top: 51 + if (!SDL_PollEvent(&e)) 52 + return 0; 53 + 54 + switch (e.type) 55 + { 56 + case SDL_EVENT_QUIT : 57 + lua_pushstring(L, "quit"); 58 + return 1; 59 + 60 + case SDL_EVENT_WINDOW_RESIZED: 61 + lua_pushstring(L, "resized"); 62 + lua_pushnumber(L, e.window.data1); 63 + lua_pushnumber(L, e.window.data2); 64 + return 3; 65 66 + case SDL_EVENT_WINDOW_EXPOSED: 67 + rencache_invalidate(); 68 + lua_pushstring(L, "exposed"); 69 + return 1; 70 71 + case SDL_EVENT_WINDOW_FOCUS_GAINED: 72 + /* on some systems, when alt-tabbing to the window SDL will queue up 73 + ** several KEYDOWN events for the `tab` key; we flush all keydown 74 + ** events on focus so these are discarded */ 75 + SDL_FlushEvent(SDL_EVENT_KEY_DOWN); 76 + goto top; 77 78 + case SDL_EVENT_DROP_FILE : 79 + SDL_GetGlobalMouseState(&mx, &my); 80 + SDL_GetWindowPosition(window, &wx, &wy); 81 + lua_pushstring(L, "filedropped"); 82 + lua_pushstring(L, e.drop.data); 83 + lua_pushnumber(L, mx - wx); 84 + lua_pushnumber(L, my - wy); 85 + return 4; 86 87 + case SDL_EVENT_KEY_DOWN : 88 + lua_pushstring(L, "keypressed"); 89 + lua_pushstring(L, key_name(buf, e.key.key)); 90 + return 2; 91 92 + case SDL_EVENT_KEY_UP : 93 + lua_pushstring(L, "keyreleased"); 94 + lua_pushstring(L, key_name(buf, e.key.key)); 95 + return 2; 96 97 + case SDL_EVENT_TEXT_INPUT : 98 + lua_pushstring(L, "textinput"); 99 + lua_pushstring(L, e.text.text); 100 + return 2; 101 102 + case SDL_EVENT_MOUSE_BUTTON_DOWN : 103 + if (e.button.button == 1) 104 + SDL_CaptureMouse(1); 105 + lua_pushstring(L, "mousepressed"); 106 + lua_pushstring(L, button_name(e.button.button)); 107 + lua_pushnumber(L, e.button.x); 108 + lua_pushnumber(L, e.button.y); 109 + lua_pushnumber(L, e.button.clicks); 110 + return 5; 111 112 + case SDL_EVENT_MOUSE_BUTTON_UP : 113 + if (e.button.button == 1) 114 + SDL_CaptureMouse(0); 115 + lua_pushstring(L, "mousereleased"); 116 + lua_pushstring(L, button_name(e.button.button)); 117 + lua_pushnumber(L, e.button.x); 118 + lua_pushnumber(L, e.button.y); 119 + return 4; 120 121 + case SDL_EVENT_MOUSE_MOTION : 122 + lua_pushstring(L, "mousemoved"); 123 + lua_pushnumber(L, e.motion.x); 124 + lua_pushnumber(L, e.motion.y); 125 + lua_pushnumber(L, e.motion.xrel); 126 + lua_pushnumber(L, e.motion.yrel); 127 + return 5; 128 129 + case SDL_EVENT_MOUSE_WHEEL : 130 + lua_pushstring(L, "mousewheel"); 131 + lua_pushnumber(L, e.wheel.y); 132 + return 2; 133 134 + default: 135 + goto top; 136 + } 137 138 + return 0; 139 } 140 141 142 + static int f_wait_event(lua_State *L) 143 + { 144 + double n = luaL_checknumber(L, 1); 145 + lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000)); 146 + return 1; 147 } 148 149 150 + static SDL_Cursor* cursor_cache[SDL_SYSTEM_CURSOR_POINTER + 1]; 151 152 static const char *cursor_opts[] = { 153 + "arrow", 154 + "ibeam", 155 + "sizeh", 156 + "sizev", 157 + "hand", 158 + NULL 159 }; 160 161 static const int cursor_enums[] = { 162 + SDL_SYSTEM_CURSOR_DEFAULT, 163 + SDL_SYSTEM_CURSOR_TEXT, 164 + SDL_SYSTEM_CURSOR_EW_RESIZE, 165 + SDL_SYSTEM_CURSOR_NS_RESIZE, 166 + SDL_SYSTEM_CURSOR_POINTER 167 }; 168 169 + static int f_set_cursor(lua_State *L) 170 + { 171 + int opt = luaL_checkoption(L, 1, "arrow", cursor_opts); 172 + int n = cursor_enums[opt]; 173 + SDL_Cursor *cursor = cursor_cache[n]; 174 + if (!cursor) 175 + { 176 + cursor = SDL_CreateSystemCursor(n); 177 + cursor_cache[n] = cursor; 178 + } 179 + SDL_SetCursor(cursor); 180 + return 0; 181 } 182 183 184 + static int f_set_window_title(lua_State *L) 185 + { 186 + const char *title = luaL_checkstring(L, 1); 187 + SDL_SetWindowTitle(window, title); 188 + return 0; 189 } 190 191 192 static const char *window_opts[] = { "normal", "maximized", "fullscreen", 0 }; 193 enum { WIN_NORMAL, WIN_MAXIMIZED, WIN_FULLSCREEN }; 194 195 + static int f_set_window_mode(lua_State *L) 196 + { 197 + int n = luaL_checkoption(L, 1, "normal", window_opts); 198 + SDL_SetWindowFullscreen(window, n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN : 0); 199 + if (n == WIN_NORMAL) 200 + SDL_RestoreWindow(window); 201 + if (n == WIN_MAXIMIZED) 202 + SDL_MaximizeWindow(window); 203 + return 0; 204 } 205 206 207 + static int f_window_has_focus(lua_State *L) 208 + { 209 + unsigned flags = SDL_GetWindowFlags(window); 210 + lua_pushboolean(L, flags & SDL_WINDOW_INPUT_FOCUS); 211 + return 1; 212 } 213 214 215 + static int f_show_confirm_dialog(lua_State *L) 216 + { 217 + const char *title = luaL_checkstring(L, 1); 218 + const char *msg = luaL_checkstring(L, 2); 219 220 + # if _WIN32 221 + int id = MessageBox(0, msg, title, MB_YESNO | MB_ICONWARNING); 222 + lua_pushboolean(L, id == IDYES); 223 + # else 224 + SDL_MessageBoxButtonData buttons[] = { 225 + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes" }, 226 + { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "No" }, 227 + }; 228 + SDL_MessageBoxData data = { 229 + .title = title, 230 + .message = msg, 231 + .numbuttons = 2, 232 + .buttons = buttons, 233 + }; 234 + int buttonid; 235 + SDL_ShowMessageBox(&data, &buttonid); 236 + lua_pushboolean(L, buttonid == 1); 237 + # endif 238 239 + return 1; 240 } 241 242 243 + static int f_chdir(lua_State *L) 244 + { 245 + const char *path = luaL_checkstring(L, 1); 246 + int err = chdir(path); 247 + if (err) 248 + luaL_error(L, "chdir() failed"); 249 + return 0; 250 } 251 252 253 + static int f_list_dir(lua_State *L) 254 + { 255 + const char *path = luaL_checkstring(L, 1); 256 257 + DIR *dir = opendir(path); 258 + if (!dir) 259 + { 260 + lua_pushnil(L); 261 + lua_pushstring(L, strerror(errno)); 262 + return 2; 263 + } 264 265 + lua_newtable(L); 266 + int i = 1; 267 + struct dirent *entry; 268 + while ((entry = readdir(dir))) 269 + { 270 + if (strcmp(entry->d_name, "." ) == 0 || strcmp(entry->d_name, "..") == 0) 271 + continue; 272 + lua_pushstring(L, entry->d_name); 273 + lua_rawseti(L, -2, i); 274 + i++; 275 + } 276 277 + closedir(dir); 278 + return 1; 279 } 280 281 282 #ifdef _WIN32 283 + # include <windows.h> 284 + # define realpath(x, y) _fullpath(y, x, MAX_PATH) 285 #endif 286 287 + static int f_absolute_path(lua_State *L) 288 + { 289 + const char *path = luaL_checkstring(L, 1); 290 + char *res = realpath(path, NULL); 291 + if (!res) 292 + return 0; 293 + lua_pushstring(L, res); 294 + free(res); 295 + return 1; 296 } 297 298 299 + static int f_get_file_info(lua_State *L) 300 + { 301 + const char *path = luaL_checkstring(L, 1); 302 303 + struct stat s; 304 + int err = stat(path, &s); 305 + if (err < 0) 306 + { 307 + lua_pushnil(L); 308 + lua_pushstring(L, strerror(errno)); 309 + return 2; 310 + } 311 312 + lua_newtable(L); 313 + lua_pushnumber(L, s.st_mtime); 314 + lua_setfield(L, -2, "modified"); 315 316 + lua_pushnumber(L, s.st_size); 317 + lua_setfield(L, -2, "size"); 318 319 + if (S_ISREG(s.st_mode)) 320 + lua_pushstring(L, "file"); 321 + else if (S_ISDIR(s.st_mode)) 322 + lua_pushstring(L, "dir"); 323 + else 324 + lua_pushnil(L); 325 + lua_setfield(L, -2, "type"); 326 327 + return 1; 328 } 329 330 331 + static int f_get_clipboard(lua_State *L) 332 + { 333 + char *text = SDL_GetClipboardText(); 334 + if (!text) 335 + return 0; 336 + lua_pushstring(L, text); 337 + SDL_free(text); 338 + return 1; 339 } 340 341 342 + static int f_set_clipboard(lua_State *L) 343 + { 344 + const char *text = luaL_checkstring(L, 1); 345 + SDL_SetClipboardText(text); 346 + return 0; 347 } 348 349 350 + static int f_get_time(lua_State *L) 351 + { 352 + double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); 353 + lua_pushnumber(L, n); 354 + return 1; 355 } 356 357 358 + static int f_sleep(lua_State *L) 359 + { 360 + double n = luaL_checknumber(L, 1); 361 + SDL_Delay(n * 1000); 362 + return 0; 363 } 364 365 366 + static int f_exec(lua_State *L) 367 + { 368 + size_t len; 369 + const char *cmd = luaL_checklstring(L, 1, &len); 370 + char *buf = malloc(len + 32); 371 + if (!buf) 372 + luaL_error(L, "buffer allocation failed"); 373 + 374 + # if _WIN32 375 + sprintf(buf, "cmd /c \"%s\"", cmd); 376 + WinExec(buf, SW_HIDE); 377 + # else 378 + sprintf(buf, "%s &", cmd); 379 + int res = system(buf); 380 + (void)res; 381 + # endif 382 + 383 + free(buf); 384 + return 0; 385 } 386 387 388 + static int f_fuzzy_match(lua_State *L) 389 + { 390 + const char *str = luaL_checkstring(L, 1); 391 + const char *ptn = luaL_checkstring(L, 2); 392 + int score = 0; 393 + int run = 0; 394 395 + while (*str && *ptn) 396 + { 397 + while (*str == ' ') 398 + str++; 399 + while (*ptn == ' ') 400 + ptn++; 401 + if (tolower(*str) == tolower(*ptn)) 402 + { 403 + score += run * 10 - (*str != *ptn); 404 + run++; 405 + ptn++; 406 + } 407 + else 408 + { 409 + score -= 10; 410 + run = 0; 411 + } 412 + str++; 413 + } 414 + if (*ptn) 415 + return 0; 416 417 + lua_pushnumber(L, score - (int) strlen(str)); 418 + return 1; 419 } 420 421 422 static const luaL_Reg lib[] = { 423 + { "poll_event", f_poll_event }, 424 + { "wait_event", f_wait_event }, 425 + { "set_cursor", f_set_cursor }, 426 + { "set_window_title", f_set_window_title }, 427 + { "set_window_mode", f_set_window_mode }, 428 + { "window_has_focus", f_window_has_focus }, 429 + { "show_confirm_dialog", f_show_confirm_dialog }, 430 + { "chdir", f_chdir }, 431 + { "list_dir", f_list_dir }, 432 + { "absolute_path", f_absolute_path }, 433 + { "get_file_info", f_get_file_info }, 434 + { "get_clipboard", f_get_clipboard }, 435 + { "set_clipboard", f_set_clipboard }, 436 + { "get_time", f_get_time }, 437 + { "sleep", f_sleep }, 438 + { "exec", f_exec }, 439 + { "fuzzy_match", f_fuzzy_match }, 440 + { NULL, NULL } 441 }; 442 443 444 + int luaopen_system(lua_State *L) 445 + { 446 + luaL_newlib(L, lib); 447 + return 1; 448 }
+262 -104
src/lib/stb/stb_truetype.h
··· 1 - // stb_truetype.h - v1.19 - public domain 2 - // authored from 2009-2016 by Sean Barrett / RAD Game Tools 3 // 4 // This library processes TrueType files: 5 // parse files ··· 32 // Daniel Ribeiro Maciel 33 // 34 // Bug/warning reports/fixes: 35 - // "Zer" on mollyrocket Fabian "ryg" Giesen 36 - // Cass Everitt Martins Mozeiko 37 - // stoiko (Haemimont Games) Cap Petschulat 38 - // Brian Hook Omar Cornut 39 - // Walter van Niftrik github:aloucks 40 // David Gow Peter LaValle 41 // David Given Sergey Popov 42 // Ivan-Assen Ivanov Giumo X. Clanjor ··· 44 // Johan Duparc Thomas Fields 45 // Hou Qiming Derek Vinyard 46 // Rob Loach Cort Stratton 47 - // Kenney Phillis Jr. github:oyvindjam 48 - // Brian Costabile github:vassvik 49 - // 50 // VERSION HISTORY 51 // 52 // 1.19 (2018-02-11) GPOS kerning, STBTT_fmod 53 // 1.18 (2018-01-29) add missing function 54 // 1.17 (2017-07-23) make more arguments const; doc fix ··· 75 // 76 // USAGE 77 // 78 - // Include this file in whatever places neeed to refer to it. In ONE C/C++ 79 // file, write: 80 // #define STB_TRUETYPE_IMPLEMENTATION 81 // before the #include of this file. This expands out the actual ··· 206 // 207 // Advancing for the next character: 208 // Call GlyphHMetrics, and compute 'current_point += SF * advance'. 209 - // 210 // 211 // ADVANCED USAGE 212 // ··· 242 // recommend it. 243 // 244 // 245 - // SOURCE STATISTICS (based on v0.6c, 2050 LOC) 246 - // 247 - // Documentation & header file 520 LOC \___ 660 LOC documentation 248 - // Sample code 140 LOC / 249 - // Truetype parsing 620 LOC ---- 620 LOC TrueType 250 - // Software rasterization 240 LOC \ . 251 - // Curve tesselation 120 LOC \__ 550 LOC Bitmap creation 252 - // Bitmap management 100 LOC / 253 - // Baked bitmap interface 70 LOC / 254 - // Font name matching & access 150 LOC ---- 150 255 - // C runtime library abstraction 60 LOC ---- 60 256 - // 257 - // 258 // PERFORMANCE MEASUREMENTS FOR 1.06: 259 // 260 // 32-bit 64-bit ··· 344 } 345 return 0; 346 } 347 - #endif 348 // 349 // Output: 350 // ··· 358 // :@@. M@M 359 // @@@o@@@@ 360 // :M@@V:@@. 361 - // 362 ////////////////////////////////////////////////////////////////////////////// 363 - // 364 // Complete program: print "Hello World!" banner, with bugs 365 // 366 #if 0 ··· 556 // 557 // It's inefficient; you might want to c&p it and optimize it. 558 559 560 561 ////////////////////////////////////////////////////////////////////////////// ··· 641 // To use with PackFontRangesGather etc., you must set it before calls 642 // call to PackFontRangesGatherRects. 643 644 STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above 645 int char_index, // character to display 646 float *xpos, float *ypos, // pointers to current position in screen pixel space ··· 653 // Calling these functions in sequence is roughly equivalent to calling 654 // stbtt_PackFontRanges(). If you more control over the packing of multiple 655 // fonts, or if you want to pack custom data into a font texture, take a look 656 - // at the source to of stbtt_PackFontRanges() and create a custom version 657 // using these functions, e.g. call GatherRects multiple times, 658 // building up a single array of rects, then call PackRects once, 659 // then call RenderIntoRects repeatedly. This may result in a ··· 669 int height; 670 int stride_in_bytes; 671 int padding; 672 unsigned int h_oversample, v_oversample; 673 unsigned char *pixels; 674 void *nodes; ··· 694 // file will only define one font and it always be at offset 0, so it will 695 // return '0' for index 0, and -1 for all other indices. 696 697 - // The following structure is defined publically so you can declare one on 698 // the stack or as a global or etc, but you should treat it as opaque. 699 struct stbtt_fontinfo 700 { ··· 704 705 int numGlyphs; // number of glyphs, needed for range checking 706 707 - int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf 708 int index_map; // a cmap mapping for our chosen character encoding 709 int indexToLocFormat; // format needed to map from glyph index to glyph 710 ··· 733 // and you want a speed-up, call this function with the character you're 734 // going to process, then use glyph-based functions instead of the 735 // codepoint-based functions. 736 737 738 ////////////////////////////////////////////////////////////////////////////// ··· 786 STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); 787 // as above, but takes one or more glyph indices for greater efficiency 788 789 790 ////////////////////////////////////////////////////////////////////////////// 791 // ··· 820 // returns # of vertices and fills *vertices with the pointer to them 821 // these are expressed in "unscaled" coordinates 822 // 823 - // The shape is a series of countours. Each one starts with 824 // a STBTT_moveto, then consists of a series of mixed 825 // STBTT_lineto and STBTT_curveto segments. A lineto 826 // draws a line from previous endpoint to its x,y; a curveto ··· 829 830 STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); 831 // frees the data allocated above 832 833 ////////////////////////////////////////////////////////////////////////////// 834 // ··· 916 STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); 917 // These functions compute a discretized SDF field for a single character, suitable for storing 918 // in a single-channel texture, sampling with bilinear filtering, and testing against 919 - // larger than some threshhold to produce scalable fonts. 920 // info -- the font 921 // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap 922 // glyph/codepoint -- the character to generate the SDF for ··· 959 // and computing from that can allow drop-out prevention). 960 // 961 // The algorithm has not been optimized at all, so expect it to be slow 962 - // if computing lots of characters or very large sizes. 963 964 965 ··· 1331 return stbtt__cff_get_index(&cff); 1332 } 1333 1334 static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) 1335 { 1336 stbtt_uint32 cmap, t; ··· 1409 info->numGlyphs = ttUSHORT(data+t+4); 1410 else 1411 info->numGlyphs = 0xffff; 1412 1413 // find a cmap encoding table we understand *now* to avoid searching 1414 // later. (todo: could make this installable) ··· 1716 if (i != 0) 1717 num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); 1718 1719 - // now start the new one 1720 start_off = !(flags & 1); 1721 if (start_off) { 1722 // if we start off with an off-curve point, then when we need to find a point on the curve ··· 1758 } 1759 } 1760 num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); 1761 - } else if (numberOfContours == -1) { 1762 // Compound shapes. 1763 int more = 1; 1764 stbtt_uint8 *comp = data + g + 10; ··· 1769 int comp_num_verts = 0, i; 1770 stbtt_vertex *comp_verts = 0, *tmp = 0; 1771 float mtx[6] = {1,0,0,1,0,0}, m, n; 1772 - 1773 flags = ttSHORT(comp); comp+=2; 1774 gidx = ttSHORT(comp); comp+=2; 1775 ··· 1799 mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; 1800 mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; 1801 } 1802 - 1803 // Find transformation scales. 1804 m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); 1805 n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); ··· 1835 // More components ? 1836 more = flags & (1<<5); 1837 } 1838 - } else if (numberOfContours < 0) { 1839 - // @TODO other compound variations? 1840 - STBTT_assert(0); 1841 } else { 1842 // numberOfCounters == 0, do nothing 1843 } ··· 2266 } 2267 } 2268 2269 static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) 2270 { 2271 stbtt_uint8 *data = info->data + info->kern; ··· 2463 if (valueFormat2 != 0) return 0; 2464 2465 STBTT_assert(coverageIndex < pairSetCount); 2466 2467 needle=glyph2; 2468 r=pairValueCount-1; ··· 2540 2541 if (info->gpos) 2542 xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); 2543 - 2544 - if (info->kern) 2545 xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); 2546 2547 return xAdvance; ··· 2602 STBTT_free(v, info->userdata); 2603 } 2604 2605 ////////////////////////////////////////////////////////////////////////////// 2606 // 2607 // antialiasing software rasterizer ··· 2727 float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); 2728 STBTT_assert(z != NULL); 2729 if (!z) return z; 2730 - 2731 // round dx down to avoid overshooting 2732 if (dxdy < 0) 2733 z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); ··· 2805 } 2806 } 2807 } 2808 - 2809 e = e->next; 2810 } 2811 } ··· 3160 if (e->y0 != e->y1) { 3161 stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); 3162 if (z != NULL) { 3163 - STBTT_assert(z->ey >= scan_y_top); 3164 // insert at front 3165 z->next = active; 3166 active = z; ··· 3229 3230 static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) 3231 { 3232 - /* threshhold for transitioning to insertion sort */ 3233 while (n > 12) { 3234 stbtt__edge t; 3235 int c01,c12,c,m,i,j; ··· 3364 points[n].y = y; 3365 } 3366 3367 - // tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching 3368 static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) 3369 { 3370 // midpoint ··· 3527 { 3528 int ix0,iy0,ix1,iy1; 3529 stbtt__bitmap gbm; 3530 - stbtt_vertex *vertices; 3531 int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); 3532 3533 if (scale_x == 0) scale_x = scale_y; ··· 3550 if (height) *height = gbm.h; 3551 if (xoff ) *xoff = ix0; 3552 if (yoff ) *yoff = iy0; 3553 - 3554 if (gbm.w && gbm.h) { 3555 gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); 3556 if (gbm.pixels) { ··· 3561 } 3562 STBTT_free(vertices, info->userdata); 3563 return gbm.pixels; 3564 - } 3565 3566 STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) 3567 { ··· 3573 int ix0,iy0; 3574 stbtt_vertex *vertices; 3575 int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); 3576 - stbtt__bitmap gbm; 3577 3578 stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); 3579 gbm.pixels = output; ··· 3595 STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) 3596 { 3597 return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); 3598 - } 3599 3600 STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) 3601 { ··· 3610 STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) 3611 { 3612 return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); 3613 - } 3614 3615 STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) 3616 { ··· 3735 con->y = 0; 3736 con->bottom_y = 0; 3737 STBTT__NOTUSED(nodes); 3738 - STBTT__NOTUSED(num_nodes); 3739 } 3740 3741 static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) ··· 3789 spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; 3790 spc->h_oversample = 1; 3791 spc->v_oversample = 1; 3792 3793 stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); 3794 ··· 3814 spc->v_oversample = v_oversample; 3815 } 3816 3817 #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) 3818 3819 static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) ··· 3956 STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) 3957 { 3958 int i,j,k; 3959 3960 k=0; 3961 for (i=0; i < num_ranges; ++i) { ··· 3967 int x0,y0,x1,y1; 3968 int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; 3969 int glyph = stbtt_FindGlyphIndex(info, codepoint); 3970 - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, 3971 - scale * spc->h_oversample, 3972 - scale * spc->v_oversample, 3973 - 0,0, 3974 - &x0,&y0,&x1,&y1); 3975 - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); 3976 - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); 3977 ++k; 3978 } 3979 } ··· 4007 // rects array must be big enough to accommodate all characters in the given ranges 4008 STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) 4009 { 4010 - int i,j,k, return_value = 1; 4011 4012 // save current values 4013 int old_h_over = spc->h_oversample; ··· 4026 sub_y = stbtt__oversample_shift(spc->v_oversample); 4027 for (j=0; j < ranges[i].num_chars; ++j) { 4028 stbrp_rect *r = &rects[k]; 4029 - if (r->was_packed) { 4030 stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; 4031 int advance, lsb, x0,y0,x1,y1; 4032 int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; ··· 4072 bc->yoff = (float) y0 * recip_v + sub_y; 4073 bc->xoff2 = (x0 + r->w) * recip_h + sub_x; 4074 bc->yoff2 = (y0 + r->h) * recip_v + sub_y; 4075 } else { 4076 return_value = 0; // if any fail, report failure 4077 } ··· 4110 n = 0; 4111 for (i=0; i < num_ranges; ++i) 4112 n += ranges[i].num_chars; 4113 - 4114 rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); 4115 if (rects == NULL) 4116 return 0; ··· 4121 n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); 4122 4123 stbtt_PackFontRangesPackRects(spc, rects, n); 4124 - 4125 return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); 4126 4127 STBTT_free(rects, spc->user_allocator_context); ··· 4138 range.chardata_for_range = chardata_for_range; 4139 range.font_size = font_size; 4140 return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); 4141 } 4142 4143 STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) ··· 4269 int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; 4270 if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { 4271 float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; 4272 - if (x_inter < x) 4273 winding += (y0 < y1) ? 1 : -1; 4274 } 4275 } ··· 4295 y1 = (int)verts[i ].y; 4296 if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { 4297 float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; 4298 - if (x_inter < x) 4299 winding += (y0 < y1) ? 1 : -1; 4300 } 4301 } else { ··· 4307 if (hits[1][0] < 0) 4308 winding += (hits[1][1] < 0 ? -1 : 1); 4309 } 4310 - } 4311 } 4312 } 4313 return winding; ··· 4360 int w,h; 4361 unsigned char *data; 4362 4363 - // if one scale is 0, use same scale for both 4364 - if (scale_x == 0) scale_x = scale_y; 4365 - if (scale_y == 0) { 4366 - if (scale_x == 0) return NULL; // if both scales are 0, return NULL 4367 - scale_y = scale_x; 4368 - } 4369 4370 stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); 4371 ··· 4388 4389 // invert for y-downwards bitmaps 4390 scale_y = -scale_y; 4391 - 4392 { 4393 int x,y,i,j; 4394 float *precompute; ··· 4537 STBTT_free(verts, info->userdata); 4538 } 4539 return data; 4540 - } 4541 4542 STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) 4543 { ··· 4555 // 4556 4557 // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string 4558 - static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) 4559 { 4560 stbtt_int32 i=0; 4561 ··· 4594 return i; 4595 } 4596 4597 - static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) 4598 { 4599 return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); 4600 } ··· 4723 4724 STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) 4725 { 4726 - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); 4727 } 4728 4729 STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) ··· 4816 ------------------------------------------------------------------------------ 4817 ALTERNATIVE A - MIT License 4818 Copyright (c) 2017 Sean Barrett 4819 - Permission is hereby granted, free of charge, to any person obtaining a copy of 4820 - this software and associated documentation files (the "Software"), to deal in 4821 - the Software without restriction, including without limitation the rights to 4822 - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 4823 - of the Software, and to permit persons to whom the Software is furnished to do 4824 so, subject to the following conditions: 4825 - The above copyright notice and this permission notice shall be included in all 4826 copies or substantial portions of the Software. 4827 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 4828 - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4829 - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 4830 - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 4831 - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 4832 - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 4833 SOFTWARE. 4834 ------------------------------------------------------------------------------ 4835 ALTERNATIVE B - Public Domain (www.unlicense.org) 4836 This is free and unencumbered software released into the public domain. 4837 - Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 4838 - software, either in source code form or as a compiled binary, for any purpose, 4839 commercial or non-commercial, and by any means. 4840 - In jurisdictions that recognize copyright laws, the author or authors of this 4841 - software dedicate any and all copyright interest in the software to the public 4842 - domain. We make this dedication for the benefit of the public at large and to 4843 - the detriment of our heirs and successors. We intend this dedication to be an 4844 - overt act of relinquishment in perpetuity of all present and future rights to 4845 this software under copyright law. 4846 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 4847 - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4848 - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 4849 - AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 4850 - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 4851 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 4852 ------------------------------------------------------------------------------ 4853 */
··· 1 + // stb_truetype.h - v1.24 - public domain 2 + // authored from 2009-2020 by Sean Barrett / RAD Game Tools 3 + // 4 + // ======================================================================= 5 + // 6 + // NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES 7 + // 8 + // This library does no range checking of the offsets found in the file, 9 + // meaning an attacker can use it to read arbitrary memory. 10 + // 11 + // ======================================================================= 12 // 13 // This library processes TrueType files: 14 // parse files ··· 41 // Daniel Ribeiro Maciel 42 // 43 // Bug/warning reports/fixes: 44 + // "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe 45 + // Cass Everitt Martins Mozeiko github:aloucks 46 + // stoiko (Haemimont Games) Cap Petschulat github:oyvindjam 47 + // Brian Hook Omar Cornut github:vassvik 48 + // Walter van Niftrik Ryan Griege 49 // David Gow Peter LaValle 50 // David Given Sergey Popov 51 // Ivan-Assen Ivanov Giumo X. Clanjor ··· 53 // Johan Duparc Thomas Fields 54 // Hou Qiming Derek Vinyard 55 // Rob Loach Cort Stratton 56 + // Kenney Phillis Jr. Brian Costabile 57 + // Ken Voskuil (kaesve) 58 + // 59 // VERSION HISTORY 60 // 61 + // 1.24 (2020-02-05) fix warning 62 + // 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) 63 + // 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined 64 + // 1.21 (2019-02-25) fix warning 65 + // 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() 66 // 1.19 (2018-02-11) GPOS kerning, STBTT_fmod 67 // 1.18 (2018-01-29) add missing function 68 // 1.17 (2017-07-23) make more arguments const; doc fix ··· 89 // 90 // USAGE 91 // 92 + // Include this file in whatever places need to refer to it. In ONE C/C++ 93 // file, write: 94 // #define STB_TRUETYPE_IMPLEMENTATION 95 // before the #include of this file. This expands out the actual ··· 220 // 221 // Advancing for the next character: 222 // Call GlyphHMetrics, and compute 'current_point += SF * advance'. 223 + // 224 // 225 // ADVANCED USAGE 226 // ··· 256 // recommend it. 257 // 258 // 259 // PERFORMANCE MEASUREMENTS FOR 1.06: 260 // 261 // 32-bit 64-bit ··· 345 } 346 return 0; 347 } 348 + #endif 349 // 350 // Output: 351 // ··· 359 // :@@. M@M 360 // @@@o@@@@ 361 // :M@@V:@@. 362 + // 363 ////////////////////////////////////////////////////////////////////////////// 364 + // 365 // Complete program: print "Hello World!" banner, with bugs 366 // 367 #if 0 ··· 557 // 558 // It's inefficient; you might want to c&p it and optimize it. 559 560 + STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); 561 + // Query the font vertical metrics without having to create a font first. 562 563 564 ////////////////////////////////////////////////////////////////////////////// ··· 644 // To use with PackFontRangesGather etc., you must set it before calls 645 // call to PackFontRangesGatherRects. 646 647 + STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); 648 + // If skip != 0, this tells stb_truetype to skip any codepoints for which 649 + // there is no corresponding glyph. If skip=0, which is the default, then 650 + // codepoints without a glyph recived the font's "missing character" glyph, 651 + // typically an empty box by convention. 652 + 653 STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above 654 int char_index, // character to display 655 float *xpos, float *ypos, // pointers to current position in screen pixel space ··· 662 // Calling these functions in sequence is roughly equivalent to calling 663 // stbtt_PackFontRanges(). If you more control over the packing of multiple 664 // fonts, or if you want to pack custom data into a font texture, take a look 665 + // at the source to of stbtt_PackFontRanges() and create a custom version 666 // using these functions, e.g. call GatherRects multiple times, 667 // building up a single array of rects, then call PackRects once, 668 // then call RenderIntoRects repeatedly. This may result in a ··· 678 int height; 679 int stride_in_bytes; 680 int padding; 681 + int skip_missing; 682 unsigned int h_oversample, v_oversample; 683 unsigned char *pixels; 684 void *nodes; ··· 704 // file will only define one font and it always be at offset 0, so it will 705 // return '0' for index 0, and -1 for all other indices. 706 707 + // The following structure is defined publicly so you can declare one on 708 // the stack or as a global or etc, but you should treat it as opaque. 709 struct stbtt_fontinfo 710 { ··· 714 715 int numGlyphs; // number of glyphs, needed for range checking 716 717 + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf 718 int index_map; // a cmap mapping for our chosen character encoding 719 int indexToLocFormat; // format needed to map from glyph index to glyph 720 ··· 743 // and you want a speed-up, call this function with the character you're 744 // going to process, then use glyph-based functions instead of the 745 // codepoint-based functions. 746 + // Returns 0 if the character codepoint is not defined in the font. 747 748 749 ////////////////////////////////////////////////////////////////////////////// ··· 797 STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); 798 // as above, but takes one or more glyph indices for greater efficiency 799 800 + typedef struct stbtt_kerningentry 801 + { 802 + int glyph1; // use stbtt_FindGlyphIndex 803 + int glyph2; 804 + int advance; 805 + } stbtt_kerningentry; 806 + 807 + STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); 808 + STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); 809 + // Retrieves a complete list of all of the kerning pairs provided by the font 810 + // stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. 811 + // The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) 812 813 ////////////////////////////////////////////////////////////////////////////// 814 // ··· 843 // returns # of vertices and fills *vertices with the pointer to them 844 // these are expressed in "unscaled" coordinates 845 // 846 + // The shape is a series of contours. Each one starts with 847 // a STBTT_moveto, then consists of a series of mixed 848 // STBTT_lineto and STBTT_curveto segments. A lineto 849 // draws a line from previous endpoint to its x,y; a curveto ··· 852 853 STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); 854 // frees the data allocated above 855 + 856 + STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); 857 + STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); 858 + // fills svg with the character's SVG data. 859 + // returns data size or 0 if SVG not found. 860 861 ////////////////////////////////////////////////////////////////////////////// 862 // ··· 944 STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); 945 // These functions compute a discretized SDF field for a single character, suitable for storing 946 // in a single-channel texture, sampling with bilinear filtering, and testing against 947 + // larger than some threshold to produce scalable fonts. 948 // info -- the font 949 // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap 950 // glyph/codepoint -- the character to generate the SDF for ··· 987 // and computing from that can allow drop-out prevention). 988 // 989 // The algorithm has not been optimized at all, so expect it to be slow 990 + // if computing lots of characters or very large sizes. 991 992 993 ··· 1359 return stbtt__cff_get_index(&cff); 1360 } 1361 1362 + // since most people won't use this, find this table the first time it's needed 1363 + static int stbtt__get_svg(stbtt_fontinfo *info) 1364 + { 1365 + stbtt_uint32 t; 1366 + if (info->svg < 0) { 1367 + t = stbtt__find_table(info->data, info->fontstart, "SVG "); 1368 + if (t) { 1369 + stbtt_uint32 offset = ttULONG(info->data + t + 2); 1370 + info->svg = t + offset; 1371 + } else { 1372 + info->svg = 0; 1373 + } 1374 + } 1375 + return info->svg; 1376 + } 1377 + 1378 static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) 1379 { 1380 stbtt_uint32 cmap, t; ··· 1453 info->numGlyphs = ttUSHORT(data+t+4); 1454 else 1455 info->numGlyphs = 0xffff; 1456 + 1457 + info->svg = -1; 1458 1459 // find a cmap encoding table we understand *now* to avoid searching 1460 // later. (todo: could make this installable) ··· 1762 if (i != 0) 1763 num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); 1764 1765 + // now start the new one 1766 start_off = !(flags & 1); 1767 if (start_off) { 1768 // if we start off with an off-curve point, then when we need to find a point on the curve ··· 1804 } 1805 } 1806 num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); 1807 + } else if (numberOfContours < 0) { 1808 // Compound shapes. 1809 int more = 1; 1810 stbtt_uint8 *comp = data + g + 10; ··· 1815 int comp_num_verts = 0, i; 1816 stbtt_vertex *comp_verts = 0, *tmp = 0; 1817 float mtx[6] = {1,0,0,1,0,0}, m, n; 1818 + 1819 flags = ttSHORT(comp); comp+=2; 1820 gidx = ttSHORT(comp); comp+=2; 1821 ··· 1845 mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; 1846 mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; 1847 } 1848 + 1849 // Find transformation scales. 1850 m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); 1851 n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); ··· 1881 // More components ? 1882 more = flags & (1<<5); 1883 } 1884 } else { 1885 // numberOfCounters == 0, do nothing 1886 } ··· 2309 } 2310 } 2311 2312 + STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) 2313 + { 2314 + stbtt_uint8 *data = info->data + info->kern; 2315 + 2316 + // we only look at the first table. it must be 'horizontal' and format 0. 2317 + if (!info->kern) 2318 + return 0; 2319 + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 2320 + return 0; 2321 + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format 2322 + return 0; 2323 + 2324 + return ttUSHORT(data+10); 2325 + } 2326 + 2327 + STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) 2328 + { 2329 + stbtt_uint8 *data = info->data + info->kern; 2330 + int k, length; 2331 + 2332 + // we only look at the first table. it must be 'horizontal' and format 0. 2333 + if (!info->kern) 2334 + return 0; 2335 + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 2336 + return 0; 2337 + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format 2338 + return 0; 2339 + 2340 + length = ttUSHORT(data+10); 2341 + if (table_length < length) 2342 + length = table_length; 2343 + 2344 + for (k = 0; k < length; k++) 2345 + { 2346 + table[k].glyph1 = ttUSHORT(data+18+(k*6)); 2347 + table[k].glyph2 = ttUSHORT(data+20+(k*6)); 2348 + table[k].advance = ttSHORT(data+22+(k*6)); 2349 + } 2350 + 2351 + return length; 2352 + } 2353 + 2354 static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) 2355 { 2356 stbtt_uint8 *data = info->data + info->kern; ··· 2548 if (valueFormat2 != 0) return 0; 2549 2550 STBTT_assert(coverageIndex < pairSetCount); 2551 + STBTT__NOTUSED(pairSetCount); 2552 2553 needle=glyph2; 2554 r=pairValueCount-1; ··· 2626 2627 if (info->gpos) 2628 xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); 2629 + else if (info->kern) 2630 xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); 2631 2632 return xAdvance; ··· 2687 STBTT_free(v, info->userdata); 2688 } 2689 2690 + STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) 2691 + { 2692 + int i; 2693 + stbtt_uint8 *data = info->data; 2694 + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); 2695 + 2696 + int numEntries = ttUSHORT(svg_doc_list); 2697 + stbtt_uint8 *svg_docs = svg_doc_list + 2; 2698 + 2699 + for(i=0; i<numEntries; i++) { 2700 + stbtt_uint8 *svg_doc = svg_docs + (12 * i); 2701 + if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) 2702 + return svg_doc; 2703 + } 2704 + return 0; 2705 + } 2706 + 2707 + STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) 2708 + { 2709 + stbtt_uint8 *data = info->data; 2710 + stbtt_uint8 *svg_doc; 2711 + 2712 + if (info->svg == 0) 2713 + return 0; 2714 + 2715 + svg_doc = stbtt_FindSVGDoc(info, gl); 2716 + if (svg_doc != NULL) { 2717 + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); 2718 + return ttULONG(svg_doc + 8); 2719 + } else { 2720 + return 0; 2721 + } 2722 + } 2723 + 2724 + STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) 2725 + { 2726 + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); 2727 + } 2728 + 2729 ////////////////////////////////////////////////////////////////////////////// 2730 // 2731 // antialiasing software rasterizer ··· 2851 float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); 2852 STBTT_assert(z != NULL); 2853 if (!z) return z; 2854 + 2855 // round dx down to avoid overshooting 2856 if (dxdy < 0) 2857 z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); ··· 2929 } 2930 } 2931 } 2932 + 2933 e = e->next; 2934 } 2935 } ··· 3284 if (e->y0 != e->y1) { 3285 stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); 3286 if (z != NULL) { 3287 + if (j == 0 && off_y != 0) { 3288 + if (z->ey < scan_y_top) { 3289 + // this can happen due to subpixel positioning and some kind of fp rounding error i think 3290 + z->ey = scan_y_top; 3291 + } 3292 + } 3293 + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds 3294 // insert at front 3295 z->next = active; 3296 active = z; ··· 3359 3360 static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) 3361 { 3362 + /* threshold for transitioning to insertion sort */ 3363 while (n > 12) { 3364 stbtt__edge t; 3365 int c01,c12,c,m,i,j; ··· 3494 points[n].y = y; 3495 } 3496 3497 + // tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching 3498 static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) 3499 { 3500 // midpoint ··· 3657 { 3658 int ix0,iy0,ix1,iy1; 3659 stbtt__bitmap gbm; 3660 + stbtt_vertex *vertices; 3661 int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); 3662 3663 if (scale_x == 0) scale_x = scale_y; ··· 3680 if (height) *height = gbm.h; 3681 if (xoff ) *xoff = ix0; 3682 if (yoff ) *yoff = iy0; 3683 + 3684 if (gbm.w && gbm.h) { 3685 gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); 3686 if (gbm.pixels) { ··· 3691 } 3692 STBTT_free(vertices, info->userdata); 3693 return gbm.pixels; 3694 + } 3695 3696 STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) 3697 { ··· 3703 int ix0,iy0; 3704 stbtt_vertex *vertices; 3705 int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); 3706 + stbtt__bitmap gbm; 3707 3708 stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); 3709 gbm.pixels = output; ··· 3725 STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) 3726 { 3727 return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); 3728 + } 3729 3730 STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) 3731 { ··· 3740 STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) 3741 { 3742 return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); 3743 + } 3744 3745 STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) 3746 { ··· 3865 con->y = 0; 3866 con->bottom_y = 0; 3867 STBTT__NOTUSED(nodes); 3868 + STBTT__NOTUSED(num_nodes); 3869 } 3870 3871 static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) ··· 3919 spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; 3920 spc->h_oversample = 1; 3921 spc->v_oversample = 1; 3922 + spc->skip_missing = 0; 3923 3924 stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); 3925 ··· 3945 spc->v_oversample = v_oversample; 3946 } 3947 3948 + STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) 3949 + { 3950 + spc->skip_missing = skip; 3951 + } 3952 + 3953 #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) 3954 3955 static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) ··· 4092 STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) 4093 { 4094 int i,j,k; 4095 + int missing_glyph_added = 0; 4096 4097 k=0; 4098 for (i=0; i < num_ranges; ++i) { ··· 4104 int x0,y0,x1,y1; 4105 int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; 4106 int glyph = stbtt_FindGlyphIndex(info, codepoint); 4107 + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { 4108 + rects[k].w = rects[k].h = 0; 4109 + } else { 4110 + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, 4111 + scale * spc->h_oversample, 4112 + scale * spc->v_oversample, 4113 + 0,0, 4114 + &x0,&y0,&x1,&y1); 4115 + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); 4116 + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); 4117 + if (glyph == 0) 4118 + missing_glyph_added = 1; 4119 + } 4120 ++k; 4121 } 4122 } ··· 4150 // rects array must be big enough to accommodate all characters in the given ranges 4151 STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) 4152 { 4153 + int i,j,k, missing_glyph = -1, return_value = 1; 4154 4155 // save current values 4156 int old_h_over = spc->h_oversample; ··· 4169 sub_y = stbtt__oversample_shift(spc->v_oversample); 4170 for (j=0; j < ranges[i].num_chars; ++j) { 4171 stbrp_rect *r = &rects[k]; 4172 + if (r->was_packed && r->w != 0 && r->h != 0) { 4173 stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; 4174 int advance, lsb, x0,y0,x1,y1; 4175 int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; ··· 4215 bc->yoff = (float) y0 * recip_v + sub_y; 4216 bc->xoff2 = (x0 + r->w) * recip_h + sub_x; 4217 bc->yoff2 = (y0 + r->h) * recip_v + sub_y; 4218 + 4219 + if (glyph == 0) 4220 + missing_glyph = j; 4221 + } else if (spc->skip_missing) { 4222 + return_value = 0; 4223 + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { 4224 + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; 4225 } else { 4226 return_value = 0; // if any fail, report failure 4227 } ··· 4260 n = 0; 4261 for (i=0; i < num_ranges; ++i) 4262 n += ranges[i].num_chars; 4263 + 4264 rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); 4265 if (rects == NULL) 4266 return 0; ··· 4271 n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); 4272 4273 stbtt_PackFontRangesPackRects(spc, rects, n); 4274 + 4275 return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); 4276 4277 STBTT_free(rects, spc->user_allocator_context); ··· 4288 range.chardata_for_range = chardata_for_range; 4289 range.font_size = font_size; 4290 return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); 4291 + } 4292 + 4293 + STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) 4294 + { 4295 + int i_ascent, i_descent, i_lineGap; 4296 + float scale; 4297 + stbtt_fontinfo info; 4298 + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); 4299 + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); 4300 + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); 4301 + *ascent = (float) i_ascent * scale; 4302 + *descent = (float) i_descent * scale; 4303 + *lineGap = (float) i_lineGap * scale; 4304 } 4305 4306 STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) ··· 4432 int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; 4433 if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { 4434 float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; 4435 + if (x_inter < x) 4436 winding += (y0 < y1) ? 1 : -1; 4437 } 4438 } ··· 4458 y1 = (int)verts[i ].y; 4459 if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { 4460 float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; 4461 + if (x_inter < x) 4462 winding += (y0 < y1) ? 1 : -1; 4463 } 4464 } else { ··· 4470 if (hits[1][0] < 0) 4471 winding += (hits[1][1] < 0 ? -1 : 1); 4472 } 4473 + } 4474 } 4475 } 4476 return winding; ··· 4523 int w,h; 4524 unsigned char *data; 4525 4526 + if (scale == 0) return NULL; 4527 4528 stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); 4529 ··· 4546 4547 // invert for y-downwards bitmaps 4548 scale_y = -scale_y; 4549 + 4550 { 4551 int x,y,i,j; 4552 float *precompute; ··· 4695 STBTT_free(verts, info->userdata); 4696 } 4697 return data; 4698 + } 4699 4700 STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) 4701 { ··· 4713 // 4714 4715 // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string 4716 + static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) 4717 { 4718 stbtt_int32 i=0; 4719 ··· 4752 return i; 4753 } 4754 4755 + static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) 4756 { 4757 return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); 4758 } ··· 4881 4882 STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) 4883 { 4884 + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); 4885 } 4886 4887 STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) ··· 4974 ------------------------------------------------------------------------------ 4975 ALTERNATIVE A - MIT License 4976 Copyright (c) 2017 Sean Barrett 4977 + Permission is hereby granted, free of charge, to any person obtaining a copy of 4978 + this software and associated documentation files (the "Software"), to deal in 4979 + the Software without restriction, including without limitation the rights to 4980 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 4981 + of the Software, and to permit persons to whom the Software is furnished to do 4982 so, subject to the following conditions: 4983 + The above copyright notice and this permission notice shall be included in all 4984 copies or substantial portions of the Software. 4985 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 4986 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4987 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 4988 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 4989 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 4990 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 4991 SOFTWARE. 4992 ------------------------------------------------------------------------------ 4993 ALTERNATIVE B - Public Domain (www.unlicense.org) 4994 This is free and unencumbered software released into the public domain. 4995 + Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 4996 + software, either in source code form or as a compiled binary, for any purpose, 4997 commercial or non-commercial, and by any means. 4998 + In jurisdictions that recognize copyright laws, the author or authors of this 4999 + software dedicate any and all copyright interest in the software to the public 5000 + domain. We make this dedication for the benefit of the public at large and to 5001 + the detriment of our heirs and successors. We intend this dedication to be an 5002 + overt act of relinquishment in perpetuity of all present and future rights to 5003 this software under copyright law. 5004 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 5005 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 5006 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5007 + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 5008 + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 5009 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5010 ------------------------------------------------------------------------------ 5011 */
+103 -106
src/main.c
··· 1 #include <stdio.h> 2 - #include <SDL2/SDL.h> 3 #include "api/api.h" 4 #include "renderer.h" 5 6 #ifdef _WIN32 7 - #include <windows.h> 8 #elif __linux__ 9 - #include <unistd.h> 10 #elif __APPLE__ 11 - #include <mach-o/dyld.h> 12 #endif 13 14 15 SDL_Window *window; 16 17 18 - static double get_scale(void) { 19 - float dpi; 20 - SDL_GetDisplayDPI(0, NULL, &dpi, NULL); 21 - #if _WIN32 22 - return dpi / 96.0; 23 - #elif __APPLE__ 24 - return 1.0; /* dpi / 72.0; */ 25 - #else 26 - return 1.0; 27 - #endif 28 } 29 30 - 31 - static void get_exe_filename(char *buf, int sz) { 32 - #if _WIN32 33 - int len = GetModuleFileName(NULL, buf, sz - 1); 34 - buf[len] = '\0'; 35 - #elif __linux__ 36 - char path[512]; 37 - sprintf(path, "/proc/%d/exe", getpid()); 38 - int len = readlink(path, buf, sz - 1); 39 - buf[len] = '\0'; 40 - #elif __APPLE__ 41 - unsigned size = sz; 42 - _NSGetExecutablePath(buf, &size); 43 - #else 44 - strcpy(buf, "./lite"); 45 - #endif 46 } 47 48 49 - static void init_window_icon(void) { 50 - #ifndef _WIN32 51 - #include "../icon.inl" 52 - (void) icon_rgba_len; /* unused */ 53 - SDL_Surface *surf = SDL_CreateRGBSurfaceFrom( 54 - icon_rgba, 64, 64, 55 - 32, 64 * 4, 56 - 0x000000ff, 57 - 0x0000ff00, 58 - 0x00ff0000, 59 - 0xff000000); 60 - SDL_SetWindowIcon(window, surf); 61 - SDL_FreeSurface(surf); 62 - #endif 63 } 64 65 66 - int main(int argc, char **argv) { 67 - #ifdef _WIN32 68 - HINSTANCE lib = LoadLibrary("user32.dll"); 69 - int (*SetProcessDPIAware)() = (void*) GetProcAddress(lib, "SetProcessDPIAware"); 70 - SetProcessDPIAware(); 71 - #endif 72 73 - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); 74 - SDL_EnableScreenSaver(); 75 - SDL_EventState(SDL_DROPFILE, SDL_ENABLE); 76 - atexit(SDL_Quit); 77 - #if SDL_VERSION_ATLEAST(2, 0, 5) 78 - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); 79 - #endif 80 81 - SDL_DisplayMode dm; 82 - SDL_GetCurrentDisplayMode(0, &dm); 83 84 - window = SDL_CreateWindow( 85 - "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, 86 - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); 87 - init_window_icon(); 88 - ren_init(window); 89 - 90 91 - lua_State *L = luaL_newstate(); 92 - luaL_openlibs(L); 93 - api_load_libs(L); 94 95 96 - lua_newtable(L); 97 - for (int i = 0; i < argc; i++) { 98 - lua_pushstring(L, argv[i]); 99 - lua_rawseti(L, -2, i + 1); 100 - } 101 - lua_setglobal(L, "ARGS"); 102 103 - lua_pushstring(L, "1.05"); 104 - lua_setglobal(L, "VERSION"); 105 106 - lua_pushstring(L, SDL_GetPlatform()); 107 - lua_setglobal(L, "PLATFORM"); 108 109 - lua_pushnumber(L, get_scale()); 110 - lua_setglobal(L, "SCALE"); 111 112 - char exename[2048]; 113 - get_exe_filename(exename, sizeof(exename)); 114 - lua_pushstring(L, exename); 115 - lua_setglobal(L, "EXEFILE"); 116 117 118 - (void) luaL_dostring(L, 119 - "local core\n" 120 - "xpcall(function()\n" 121 - " SCALE = tonumber(os.getenv(\"LITE_SCALE\")) or SCALE\n" 122 - " PATHSEP = package.config:sub(1, 1)\n" 123 - " EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n" 124 - " package.path = EXEDIR .. '/data/?.lua;' .. package.path\n" 125 - " package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n" 126 - " core = require('core')\n" 127 - " core.init()\n" 128 - " core.run()\n" 129 - "end, function(err)\n" 130 - " print('Error: ' .. tostring(err))\n" 131 - " print(debug.traceback(nil, 2))\n" 132 - " if core and core.on_error then\n" 133 - " pcall(core.on_error, err)\n" 134 - " end\n" 135 - " os.exit(1)\n" 136 - "end)"); 137 138 139 - lua_close(L); 140 - SDL_DestroyWindow(window); 141 142 - return EXIT_SUCCESS; 143 }
··· 1 #include <stdio.h> 2 + #include <stdlib.h> 3 + #include <SDL3/SDL.h> 4 #include "api/api.h" 5 #include "renderer.h" 6 7 #ifdef _WIN32 8 + # include <windows.h> 9 #elif __linux__ 10 + # include <unistd.h> 11 #elif __APPLE__ 12 + # include <mach-o/dyld.h> 13 #endif 14 15 16 SDL_Window *window; 17 18 19 + static double get_scale(void) 20 + { 21 + return (double)SDL_min(SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()), 1.0); 22 } 23 24 + static void get_exe_filename(char *buf, int sz) 25 + { 26 + # if _WIN32 27 + int len = GetModuleFileName(NULL, buf, sz - 1); 28 + buf[len] = '\0'; 29 + # elif __linux__ 30 + char path[512]; 31 + sprintf(path, "/proc/%d/exe", getpid()); 32 + int len = readlink(path, buf, sz - 1); 33 + buf[len] = '\0'; 34 + # elif __APPLE__ 35 + unsigned size = sz; 36 + _NSGetExecutablePath(buf, &size); 37 + # else 38 + strcpy(buf, "./lite"); 39 + # endif 40 } 41 42 43 + static void init_window_icon(void) 44 + { 45 + # ifndef _WIN32 46 + # include "../icon.inl" 47 + (void) icon_rgba_len; /* unused */ 48 + SDL_Surface *surf = SDL_CreateSurfaceFrom(64, 64, SDL_PIXELFORMAT_ABGR8888, icon_rgba, 64 * 4); 49 + SDL_SetWindowIcon(window, surf); 50 + SDL_DestroySurface(surf); 51 + # endif 52 } 53 54 55 + int main(int argc, char **argv) 56 + { 57 + # ifdef _WIN32 58 + HINSTANCE lib = LoadLibrary("user32.dll"); 59 + int (*SetProcessDPIAware)() = (void *)GetProcAddress(lib, "SetProcessDPIAware"); 60 + SetProcessDPIAware(); 61 + # endif 62 63 + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); 64 + SDL_EnableScreenSaver(); 65 + SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, true); 66 + atexit(SDL_Quit); 67 68 + SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); 69 + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); 70 71 + const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay()); 72 + window = SDL_CreateWindow("", (int)(dm->w * 0.8), (int)(dm->h * 0.8), SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_HIDDEN); 73 + if (!window) 74 + { 75 + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window: %s\n", SDL_GetError()); 76 + return EXIT_FAILURE; 77 + } 78 + init_window_icon(); 79 + ren_init(window); 80 81 + if (!SDL_StartTextInput(window)) 82 + { 83 + # ifndef _WIN32 84 + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR: failed SDL_StartTextInput() - text entry may not work"); 85 + # endif 86 + } 87 88 89 + lua_State *L = luaL_newstate(); 90 + luaL_openlibs(L); 91 + api_load_libs(L); 92 93 + lua_newtable(L); 94 + for (int i = 0 ; i < argc ; i++) 95 + { 96 + lua_pushstring(L, argv[i]); 97 + lua_rawseti(L, -2, i + 1); 98 + } 99 + lua_setglobal(L, "ARGS"); 100 101 + lua_pushstring(L, "1.11"); 102 + lua_setglobal(L, "VERSION"); 103 104 + lua_pushstring(L, SDL_GetPlatform()); 105 + lua_setglobal(L, "PLATFORM"); 106 107 + lua_pushnumber(L, get_scale()); 108 + lua_setglobal(L, "SCALE"); 109 110 + char exename[2048]; 111 + get_exe_filename(exename, sizeof(exename)); 112 + lua_pushstring(L, exename); 113 + lua_setglobal(L, "EXEFILE"); 114 115 + (void) luaL_dostring(L, 116 + "local core\n" 117 + "xpcall(function()\n" 118 + " SCALE = tonumber(os.getenv(\"LITE_SCALE\")) or SCALE\n" 119 + " PATHSEP = package.config:sub(1, 1)\n" 120 + " EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n" 121 + " package.path = EXEDIR .. '/data/?.lua;' .. package.path\n" 122 + " package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n" 123 + " core = require('core')\n" 124 + " core.init()\n" 125 + " core.run()\n" 126 + "end, function(err)\n" 127 + " print('Error: ' .. tostring(err))\n" 128 + " print(debug.traceback(nil, 2))\n" 129 + " if core and core.on_error then\n" 130 + " pcall(core.on_error, err)\n" 131 + " end\n" 132 + " os.exit(1)\n" 133 + "end)"); 134 135 136 + lua_close(L); 137 + SDL_DestroyWindow(window); 138 139 + return EXIT_SUCCESS; 140 }
+265 -194
src/rencache.c
··· 11 #define CELL_SIZE 96 12 #define COMMAND_BUF_SIZE (1024 * 512) 13 14 - enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT }; 15 16 - typedef struct { 17 - int type, size; 18 - RenRect rect; 19 - RenColor color; 20 - RenFont *font; 21 - char text[0]; 22 } Command; 23 24 ··· 39 /* 32bit fnv-1a hash */ 40 #define HASH_INITIAL 2166136261 41 42 - static void hash(unsigned *h, const void *data, int size) { 43 - const unsigned char *p = data; 44 - while (size--) { 45 - *h = (*h ^ *p++) * 16777619; 46 - } 47 } 48 49 50 - static inline int cell_idx(int x, int y) { 51 - return x + y * CELLS_X; 52 } 53 54 55 - static inline bool rects_overlap(RenRect a, RenRect b) { 56 - return b.x + b.width >= a.x && b.x <= a.x + a.width 57 - && b.y + b.height >= a.y && b.y <= a.y + a.height; 58 } 59 60 61 - static RenRect intersect_rects(RenRect a, RenRect b) { 62 - int x1 = max(a.x, b.x); 63 - int y1 = max(a.y, b.y); 64 - int x2 = min(a.x + a.width, b.x + b.width); 65 - int y2 = min(a.y + a.height, b.y + b.height); 66 - return (RenRect) { x1, y1, max(0, x2 - x1), max(0, y2 - y1) }; 67 } 68 69 70 - static RenRect merge_rects(RenRect a, RenRect b) { 71 - int x1 = min(a.x, b.x); 72 - int y1 = min(a.y, b.y); 73 - int x2 = max(a.x + a.width, b.x + b.width); 74 - int y2 = max(a.y + a.height, b.y + b.height); 75 - return (RenRect) { x1, y1, x2 - x1, y2 - y1 }; 76 } 77 78 79 - static Command* push_command(int type, int size) { 80 - Command *cmd = (Command*) (command_buf + command_buf_idx); 81 - int n = command_buf_idx + size; 82 - if (n > COMMAND_BUF_SIZE) { 83 - fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n"); 84 - return NULL; 85 - } 86 - command_buf_idx = n; 87 - memset(cmd, 0, sizeof(Command)); 88 - cmd->type = type; 89 - cmd->size = size; 90 - return cmd; 91 } 92 93 94 - static bool next_command(Command **prev) { 95 - if (*prev == NULL) { 96 - *prev = (Command*) command_buf; 97 - } else { 98 - *prev = (Command*) (((char*) *prev) + (*prev)->size); 99 - } 100 - return *prev != ((Command*) (command_buf + command_buf_idx)); 101 } 102 103 104 - void rencache_show_debug(bool enable) { 105 - show_debug = enable; 106 } 107 108 109 - void rencache_free_font(RenFont *font) { 110 - Command *cmd = push_command(FREE_FONT, sizeof(Command)); 111 - if (cmd) { cmd->font = font; } 112 } 113 114 115 - void rencache_set_clip_rect(RenRect rect) { 116 - Command *cmd = push_command(SET_CLIP, sizeof(Command)); 117 - if (cmd) { cmd->rect = intersect_rects(rect, screen_rect); } 118 } 119 120 121 - void rencache_draw_rect(RenRect rect, RenColor color) { 122 - if (!rects_overlap(screen_rect, rect)) { return; } 123 - Command *cmd = push_command(DRAW_RECT, sizeof(Command)); 124 - if (cmd) { 125 - cmd->rect = rect; 126 - cmd->color = color; 127 - } 128 } 129 130 131 - int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) { 132 - RenRect rect; 133 - rect.x = x; 134 - rect.y = y; 135 - rect.width = ren_get_font_width(font, text); 136 - rect.height = ren_get_font_height(font); 137 138 - if (rects_overlap(screen_rect, rect)) { 139 - int sz = strlen(text) + 1; 140 - Command *cmd = push_command(DRAW_TEXT, sizeof(Command) + sz); 141 - if (cmd) { 142 - memcpy(cmd->text, text, sz); 143 - cmd->color = color; 144 - cmd->font = font; 145 - cmd->rect = rect; 146 - } 147 - } 148 149 - return x + rect.width; 150 } 151 152 153 - void rencache_invalidate(void) { 154 - memset(cells_prev, 0xff, sizeof(cells_buf1)); 155 } 156 157 158 - void rencache_begin_frame(void) { 159 - /* reset all cells if the screen width/height has changed */ 160 - int w, h; 161 - ren_get_size(&w, &h); 162 - if (screen_rect.width != w || h != screen_rect.height) { 163 - screen_rect.width = w; 164 - screen_rect.height = h; 165 - rencache_invalidate(); 166 - } 167 } 168 169 170 - static void update_overlapping_cells(RenRect r, unsigned h) { 171 - int x1 = r.x / CELL_SIZE; 172 - int y1 = r.y / CELL_SIZE; 173 - int x2 = (r.x + r.width) / CELL_SIZE; 174 - int y2 = (r.y + r.height) / CELL_SIZE; 175 176 - for (int y = y1; y <= y2; y++) { 177 - for (int x = x1; x <= x2; x++) { 178 - int idx = cell_idx(x, y); 179 - hash(&cells[idx], &h, sizeof(h)); 180 - } 181 - } 182 } 183 184 185 - static void push_rect(RenRect r, int *count) { 186 - /* try to merge with existing rectangle */ 187 - for (int i = *count - 1; i >= 0; i--) { 188 - RenRect *rp = &rect_buf[i]; 189 - if (rects_overlap(*rp, r)) { 190 - *rp = merge_rects(*rp, r); 191 - return; 192 - } 193 - } 194 - /* couldn't merge with previous rectangle: push */ 195 - rect_buf[(*count)++] = r; 196 } 197 198 199 - void rencache_end_frame(void) { 200 - /* update cells from commands */ 201 - Command *cmd = NULL; 202 - RenRect cr = screen_rect; 203 - while (next_command(&cmd)) { 204 - if (cmd->type == SET_CLIP) { cr = cmd->rect; } 205 - RenRect r = intersect_rects(cmd->rect, cr); 206 - if (r.width == 0 || r.height == 0) { continue; } 207 - unsigned h = HASH_INITIAL; 208 - hash(&h, cmd, cmd->size); 209 - update_overlapping_cells(r, h); 210 - } 211 212 - /* push rects for all cells changed from last frame, reset cells */ 213 - int rect_count = 0; 214 - int max_x = screen_rect.width / CELL_SIZE + 1; 215 - int max_y = screen_rect.height / CELL_SIZE + 1; 216 - for (int y = 0; y < max_y; y++) { 217 - for (int x = 0; x < max_x; x++) { 218 - /* compare previous and current cell for change */ 219 - int idx = cell_idx(x, y); 220 - if (cells[idx] != cells_prev[idx]) { 221 - push_rect((RenRect) { x, y, 1, 1 }, &rect_count); 222 - } 223 - cells_prev[idx] = HASH_INITIAL; 224 - } 225 - } 226 227 - /* expand rects from cells to pixels */ 228 - for (int i = 0; i < rect_count; i++) { 229 - RenRect *r = &rect_buf[i]; 230 - r->x *= CELL_SIZE; 231 - r->y *= CELL_SIZE; 232 - r->width *= CELL_SIZE; 233 - r->height *= CELL_SIZE; 234 - *r = intersect_rects(*r, screen_rect); 235 - } 236 237 - /* redraw updated regions */ 238 - bool has_free_commands = false; 239 - for (int i = 0; i < rect_count; i++) { 240 - /* draw */ 241 - RenRect r = rect_buf[i]; 242 - ren_set_clip_rect(r); 243 244 - cmd = NULL; 245 - while (next_command(&cmd)) { 246 - switch (cmd->type) { 247 - case FREE_FONT: 248 - has_free_commands = true; 249 - break; 250 - case SET_CLIP: 251 - ren_set_clip_rect(intersect_rects(cmd->rect, r)); 252 - break; 253 - case DRAW_RECT: 254 - ren_draw_rect(cmd->rect, cmd->color); 255 - break; 256 - case DRAW_TEXT: 257 - ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color); 258 - break; 259 - } 260 - } 261 262 - if (show_debug) { 263 - RenColor color = { rand(), rand(), rand(), 50 }; 264 - ren_draw_rect(r, color); 265 - } 266 - } 267 268 - /* update dirty rects */ 269 - if (rect_count > 0) { 270 - ren_update_rects(rect_buf, rect_count); 271 - } 272 273 - /* free fonts */ 274 - if (has_free_commands) { 275 - cmd = NULL; 276 - while (next_command(&cmd)) { 277 - if (cmd->type == FREE_FONT) { 278 - ren_free_font(cmd->font); 279 - } 280 - } 281 - } 282 283 - /* swap cell buffer and reset */ 284 - unsigned *tmp = cells; 285 - cells = cells_prev; 286 - cells_prev = tmp; 287 - command_buf_idx = 0; 288 }
··· 11 #define CELL_SIZE 96 12 #define COMMAND_BUF_SIZE (1024 * 512) 13 14 + enum 15 + { 16 + FREE_FONT, 17 + SET_CLIP, 18 + DRAW_TEXT, 19 + DRAW_RECT 20 + }; 21 22 + typedef struct 23 + { 24 + int type, size; 25 + RenRect rect; 26 + RenColor color; 27 + RenFont *font; 28 + int tab_width; 29 + char text[0]; 30 } Command; 31 32 ··· 47 /* 32bit fnv-1a hash */ 48 #define HASH_INITIAL 2166136261 49 50 + static void hash(unsigned *h, const void *data, int size) 51 + { 52 + const unsigned char *p = data; 53 + while (size--) 54 + { 55 + *h = (*h ^ *p++) * 16777619; 56 + } 57 } 58 59 60 + static inline int cell_idx(int x, int y) 61 + { 62 + return x + y * CELLS_X; 63 } 64 65 66 + static inline bool rects_overlap(RenRect a, RenRect b) 67 + { 68 + return b.x + b.width >= a.x && 69 + b.x <= a.x + a.width && 70 + b.y + b.height >= a.y && 71 + b.y <= a.y + a.height; 72 } 73 74 75 + static RenRect intersect_rects(RenRect a, RenRect b) 76 + { 77 + int x1 = max(a.x, b.x); 78 + int y1 = max(a.y, b.y); 79 + int x2 = min(a.x + a.width, b.x + b.width); 80 + int y2 = min(a.y + a.height, b.y + b.height); 81 + return (RenRect){ x1, y1, max(0, x2 - x1), max(0, y2 - y1) }; 82 } 83 84 85 + static RenRect merge_rects(RenRect a, RenRect b) 86 + { 87 + int x1 = min(a.x, b.x); 88 + int y1 = min(a.y, b.y); 89 + int x2 = max(a.x + a.width, b.x + b.width); 90 + int y2 = max(a.y + a.height, b.y + b.height); 91 + return (RenRect){ x1, y1, x2 - x1, y2 - y1 }; 92 } 93 94 95 + static Command* push_command(int type, int size) 96 + { 97 + Command *cmd = (Command *)(command_buf + command_buf_idx); 98 + int n = command_buf_idx + size; 99 + if (n > COMMAND_BUF_SIZE) 100 + { 101 + fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n"); 102 + return NULL; 103 + } 104 + command_buf_idx = n; 105 + memset(cmd, 0, sizeof(Command)); 106 + cmd->type = type; 107 + cmd->size = size; 108 + return cmd; 109 } 110 111 112 + static bool next_command(Command **prev) 113 + { 114 + if (*prev == NULL) 115 + { 116 + *prev = (Command *)command_buf; 117 + } 118 + else 119 + { 120 + *prev = (Command *)(((char *)*prev) + (*prev)->size); 121 + } 122 + 123 + return *prev != ((Command *)(command_buf + command_buf_idx)); 124 } 125 126 127 + void rencache_show_debug(bool enable) 128 + { 129 + show_debug = enable; 130 } 131 132 133 + void rencache_free_font(RenFont *font) 134 + { 135 + Command *cmd = push_command(FREE_FONT, sizeof(Command)); 136 + if (cmd) 137 + { 138 + cmd->font = font; 139 + } 140 } 141 142 143 + void rencache_set_clip_rect(RenRect rect) 144 + { 145 + Command *cmd = push_command(SET_CLIP, sizeof(Command)); 146 + if (cmd) 147 + { 148 + cmd->rect = intersect_rects(rect, screen_rect); 149 + } 150 } 151 152 153 + void rencache_draw_rect(RenRect rect, RenColor color) 154 + { 155 + if (!rects_overlap(screen_rect, rect)) 156 + { 157 + return; 158 + } 159 + Command *cmd = push_command(DRAW_RECT, sizeof(Command)); 160 + if (cmd) 161 + { 162 + cmd->rect = rect; 163 + cmd->color = color; 164 + } 165 } 166 167 168 + int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) 169 + { 170 + RenRect rect; 171 + rect.x = x; 172 + rect.y = y; 173 + rect.width = ren_get_font_width(font, text); 174 + rect.height = ren_get_font_height(font); 175 176 + if (rects_overlap(screen_rect, rect)) 177 + { 178 + int sz = strlen(text) + 1; 179 + Command *cmd = push_command(DRAW_TEXT, sizeof(Command) + sz); 180 + if (cmd) 181 + { 182 + memcpy(cmd->text, text, sz); 183 + cmd->color = color; 184 + cmd->font = font; 185 + cmd->rect = rect; 186 + cmd->tab_width = ren_get_font_tab_width(font); 187 + } 188 + } 189 190 + return x + rect.width; 191 } 192 193 194 + void rencache_invalidate(void) 195 + { 196 + memset(cells_prev, 0xff, sizeof(cells_buf1)); 197 } 198 199 200 + void rencache_begin_frame(void) 201 + { 202 + /* reset all cells if the screen width/height has changed */ 203 + int w, h; 204 + ren_get_size(&w, &h); 205 + if (screen_rect.width != w || h != screen_rect.height) 206 + { 207 + screen_rect.width = w; 208 + screen_rect.height = h; 209 + rencache_invalidate(); 210 + } 211 } 212 213 214 + static void update_overlapping_cells(RenRect r, unsigned h) 215 + { 216 + int x1 = r.x / CELL_SIZE; 217 + int y1 = r.y / CELL_SIZE; 218 + int x2 = (r.x + r.width) / CELL_SIZE; 219 + int y2 = (r.y + r.height) / CELL_SIZE; 220 221 + for (int y = y1 ; y <= y2 ; y++) 222 + { 223 + for (int x = x1 ; x <= x2 ; x++) 224 + { 225 + int idx = cell_idx(x, y); 226 + hash(&cells[idx], &h, sizeof(h)); 227 + } 228 + } 229 } 230 231 232 + static void push_rect(RenRect r, int *count) 233 + { 234 + /* try to merge with existing rectangle */ 235 + for (int i = *count - 1 ; i >= 0 ; i--) 236 + { 237 + RenRect *rp = &rect_buf[i]; 238 + if (rects_overlap(*rp, r)) 239 + { 240 + *rp = merge_rects(*rp, r); 241 + return; 242 + } 243 + } 244 + /* couldn't merge with previous rectangle: push */ 245 + rect_buf[(*count)++] = r; 246 } 247 248 249 + void rencache_end_frame(void) 250 + { 251 + /* update cells from commands */ 252 + Command *cmd = NULL; 253 + RenRect cr = screen_rect; 254 + while (next_command(&cmd)) 255 + { 256 + if (cmd->type == SET_CLIP) 257 + { 258 + cr = cmd->rect; 259 + } 260 + RenRect r = intersect_rects(cmd->rect, cr); 261 + if (r.width == 0 || r.height == 0) 262 + { 263 + continue; 264 + } 265 + unsigned h = HASH_INITIAL; 266 + hash(&h, cmd, cmd->size); 267 + update_overlapping_cells(r, h); 268 + } 269 270 + /* push rects for all cells changed from last frame, reset cells */ 271 + int rect_count = 0; 272 + int max_x = screen_rect.width / CELL_SIZE + 1; 273 + int max_y = screen_rect.height / CELL_SIZE + 1; 274 + for (int y = 0 ; y < max_y; y++) 275 + { 276 + for (int x = 0 ; x < max_x ; x++) 277 + { 278 + /* compare previous and current cell for change */ 279 + int idx = cell_idx(x, y); 280 + if (cells[idx] != cells_prev[idx]) 281 + { 282 + push_rect((RenRect) { x, y, 1, 1 }, &rect_count); 283 + } 284 + cells_prev[idx] = HASH_INITIAL; 285 + } 286 + } 287 288 + /* expand rects from cells to pixels */ 289 + for (int i = 0; i < rect_count; i++) 290 + { 291 + RenRect *r = &rect_buf[i]; 292 + r->x *= CELL_SIZE; 293 + r->y *= CELL_SIZE; 294 + r->width *= CELL_SIZE; 295 + r->height *= CELL_SIZE; 296 + *r = intersect_rects(*r, screen_rect); 297 + } 298 299 + /* redraw updated regions */ 300 + bool has_free_commands = false; 301 + for (int i = 0; i < rect_count; i++) 302 + { 303 + /* draw */ 304 + RenRect r = rect_buf[i]; 305 + ren_set_clip_rect(r); 306 307 + cmd = NULL; 308 + while (next_command(&cmd)) 309 + { 310 + switch (cmd->type) 311 + { 312 + case FREE_FONT: 313 + has_free_commands = true; 314 + break; 315 + case SET_CLIP: 316 + ren_set_clip_rect(intersect_rects(cmd->rect, r)); 317 + break; 318 + case DRAW_RECT: 319 + ren_draw_rect(cmd->rect, cmd->color); 320 + break; 321 + case DRAW_TEXT: 322 + ren_set_font_tab_width(cmd->font, cmd->tab_width); 323 + ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color); 324 + break; 325 + } 326 + } 327 328 + if (show_debug) 329 + { 330 + RenColor color = { rand(), rand(), rand(), 50 }; 331 + ren_draw_rect(r, color); 332 + } 333 + } 334 335 + /* update dirty rects */ 336 + if (rect_count > 0) 337 + { 338 + ren_update_rects(rect_buf, rect_count); 339 + } 340 341 + /* free fonts */ 342 + if (has_free_commands) 343 + { 344 + cmd = NULL; 345 + while (next_command(&cmd)) 346 + { 347 + if (cmd->type == FREE_FONT) 348 + { 349 + ren_free_font(cmd->font); 350 + } 351 + } 352 + } 353 354 + /* swap cell buffer and reset */ 355 + unsigned *tmp = cells; 356 + cells = cells_prev; 357 + cells_prev = tmp; 358 + command_buf_idx = 0; 359 }
+301 -246
src/renderer.c
··· 7 8 #define MAX_GLYPHSET 256 9 10 - struct RenImage { 11 - RenColor *pixels; 12 - int width, height; 13 }; 14 15 - typedef struct { 16 - RenImage *image; 17 - stbtt_bakedchar glyphs[256]; 18 } GlyphSet; 19 20 - struct RenFont { 21 - void *data; 22 - stbtt_fontinfo stbfont; 23 - GlyphSet *sets[MAX_GLYPHSET]; 24 - float size; 25 - int height; 26 }; 27 28 29 static SDL_Window *window; 30 - static struct { int left, top, right, bottom; } clip; 31 32 33 - static void* check_alloc(void *ptr) { 34 - if (!ptr) { 35 - fprintf(stderr, "Fatal error: memory allocation failed\n"); 36 - exit(EXIT_FAILURE); 37 - } 38 - return ptr; 39 } 40 41 42 - static const char* utf8_to_codepoint(const char *p, unsigned *dst) { 43 - unsigned res, n; 44 - switch (*p & 0xf0) { 45 - case 0xf0 : res = *p & 0x07; n = 3; break; 46 - case 0xe0 : res = *p & 0x0f; n = 2; break; 47 - case 0xd0 : 48 - case 0xc0 : res = *p & 0x1f; n = 1; break; 49 - default : res = *p; n = 0; break; 50 - } 51 - while (n--) { 52 - res = (res << 6) | (*(++p) & 0x3f); 53 - } 54 - *dst = res; 55 - return p + 1; 56 } 57 58 59 - void ren_init(SDL_Window *win) { 60 - assert(win); 61 - window = win; 62 - SDL_Surface *surf = SDL_GetWindowSurface(window); 63 - ren_set_clip_rect( (RenRect) { 0, 0, surf->w, surf->h } ); 64 } 65 66 67 - void ren_update_rects(RenRect *rects, int count) { 68 - SDL_UpdateWindowSurfaceRects(window, (SDL_Rect*) rects, count); 69 - static bool initial_frame = true; 70 - if (initial_frame) { 71 - SDL_ShowWindow(window); 72 - initial_frame = false; 73 - } 74 } 75 76 77 - void ren_set_clip_rect(RenRect rect) { 78 - clip.left = rect.x; 79 - clip.top = rect.y; 80 - clip.right = rect.x + rect.width; 81 - clip.bottom = rect.y + rect.height; 82 } 83 84 85 - void ren_get_size(int *x, int *y) { 86 - SDL_Surface *surf = SDL_GetWindowSurface(window); 87 - *x = surf->w; 88 - *y = surf->h; 89 } 90 91 92 - RenImage* ren_new_image(int width, int height) { 93 - assert(width > 0 && height > 0); 94 - RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor)); 95 - check_alloc(image); 96 - image->pixels = (void*) (image + 1); 97 - image->width = width; 98 - image->height = height; 99 - return image; 100 } 101 102 103 - void ren_free_image(RenImage *image) { 104 - free(image); 105 } 106 107 108 - static GlyphSet* load_glyphset(RenFont *font, int idx) { 109 - GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet))); 110 111 - /* init image */ 112 - int width = 128; 113 - int height = 128; 114 retry: 115 - set->image = ren_new_image(width, height); 116 117 - /* load glyphs */ 118 - float s = 119 - stbtt_ScaleForMappingEmToPixels(&font->stbfont, 1) / 120 - stbtt_ScaleForPixelHeight(&font->stbfont, 1); 121 - int res = stbtt_BakeFontBitmap( 122 - font->data, 0, font->size * s, (void*) set->image->pixels, 123 - width, height, idx * 256, 256, set->glyphs); 124 125 - /* retry with a larger image buffer if the buffer wasn't large enough */ 126 - if (res < 0) { 127 - width *= 2; 128 - height *= 2; 129 - ren_free_image(set->image); 130 - goto retry; 131 - } 132 133 - /* adjust glyph yoffsets and xadvance */ 134 - int ascent, descent, linegap; 135 - stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); 136 - float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size); 137 - int scaled_ascent = ascent * scale + 0.5; 138 - for (int i = 0; i < 256; i++) { 139 - set->glyphs[i].yoff += scaled_ascent; 140 - set->glyphs[i].xadvance = floor(set->glyphs[i].xadvance); 141 - } 142 143 - /* convert 8bit data to 32bit */ 144 - for (int i = width * height - 1; i >= 0; i--) { 145 - uint8_t n = *((uint8_t*) set->image->pixels + i); 146 - set->image->pixels[i] = (RenColor) { .r = 255, .g = 255, .b = 255, .a = n }; 147 - } 148 149 - return set; 150 } 151 152 153 - static GlyphSet* get_glyphset(RenFont *font, int codepoint) { 154 - int idx = (codepoint >> 8) % MAX_GLYPHSET; 155 - if (!font->sets[idx]) { 156 - font->sets[idx] = load_glyphset(font, idx); 157 - } 158 - return font->sets[idx]; 159 } 160 161 162 - RenFont* ren_load_font(const char *filename, float size) { 163 - RenFont *font = NULL; 164 - FILE *fp = NULL; 165 166 - /* init font */ 167 - font = check_alloc(calloc(1, sizeof(RenFont))); 168 - font->size = size; 169 170 - /* load font into buffer */ 171 - fp = fopen(filename, "rb"); 172 - if (!fp) { return NULL; } 173 - /* get size */ 174 - fseek(fp, 0, SEEK_END); int buf_size = ftell(fp); fseek(fp, 0, SEEK_SET); 175 - /* load */ 176 - font->data = check_alloc(malloc(buf_size)); 177 - int _ = fread(font->data, 1, buf_size, fp); (void) _; 178 - fclose(fp); 179 - fp = NULL; 180 181 - /* init stbfont */ 182 - int ok = stbtt_InitFont(&font->stbfont, font->data, 0); 183 - if (!ok) { goto fail; } 184 185 - /* get height and scale */ 186 - int ascent, descent, linegap; 187 - stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); 188 - float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, size); 189 - font->height = (ascent - descent + linegap) * scale + 0.5; 190 191 - /* make tab and newline glyphs invisible */ 192 - stbtt_bakedchar *g = get_glyphset(font, '\n')->glyphs; 193 - g['\t'].x1 = g['\t'].x0; 194 - g['\n'].x1 = g['\n'].x0; 195 196 - return font; 197 198 fail: 199 - if (fp) { fclose(fp); } 200 - if (font) { free(font->data); } 201 - free(font); 202 - return NULL; 203 } 204 205 206 - void ren_free_font(RenFont *font) { 207 - for (int i = 0; i < MAX_GLYPHSET; i++) { 208 - GlyphSet *set = font->sets[i]; 209 - if (set) { 210 - ren_free_image(set->image); 211 - free(set); 212 - } 213 - } 214 - free(font->data); 215 - free(font); 216 } 217 218 219 - void ren_set_font_tab_width(RenFont *font, int n) { 220 - GlyphSet *set = get_glyphset(font, '\t'); 221 - set->glyphs['\t'].xadvance = n; 222 } 223 224 225 - int ren_get_font_width(RenFont *font, const char *text) { 226 - int x = 0; 227 - const char *p = text; 228 - unsigned codepoint; 229 - while (*p) { 230 - p = utf8_to_codepoint(p, &codepoint); 231 - GlyphSet *set = get_glyphset(font, codepoint); 232 - stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; 233 - x += g->xadvance; 234 - } 235 - return x; 236 } 237 238 239 - int ren_get_font_height(RenFont *font) { 240 - return font->height; 241 } 242 243 244 - static inline RenColor blend_pixel(RenColor dst, RenColor src) { 245 - int ia = 0xff - src.a; 246 - dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8; 247 - dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8; 248 - dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8; 249 - return dst; 250 } 251 252 253 - static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color) { 254 - src.a = (src.a * color.a) >> 8; 255 - int ia = 0xff - src.a; 256 - dst.r = ((src.r * color.r * src.a) >> 16) + ((dst.r * ia) >> 8); 257 - dst.g = ((src.g * color.g * src.a) >> 16) + ((dst.g * ia) >> 8); 258 - dst.b = ((src.b * color.b * src.a) >> 16) + ((dst.b * ia) >> 8); 259 - return dst; 260 } 261 262 263 - #define rect_draw_loop(expr) \ 264 - for (int j = y1; j < y2; j++) { \ 265 - for (int i = x1; i < x2; i++) { \ 266 - *d = expr; \ 267 - d++; \ 268 - } \ 269 - d += dr; \ 270 - } 271 272 - void ren_draw_rect(RenRect rect, RenColor color) { 273 - if (color.a == 0) { return; } 274 275 - int x1 = rect.x < clip.left ? clip.left : rect.x; 276 - int y1 = rect.y < clip.top ? clip.top : rect.y; 277 - int x2 = rect.x + rect.width; 278 - int y2 = rect.y + rect.height; 279 - x2 = x2 > clip.right ? clip.right : x2; 280 - y2 = y2 > clip.bottom ? clip.bottom : y2; 281 282 - SDL_Surface *surf = SDL_GetWindowSurface(window); 283 - RenColor *d = (RenColor*) surf->pixels; 284 - d += x1 + y1 * surf->w; 285 - int dr = surf->w - (x2 - x1); 286 287 - if (color.a == 0xff) { 288 - rect_draw_loop(color); 289 - } else { 290 - rect_draw_loop(blend_pixel(*d, color)); 291 - } 292 } 293 294 295 - void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) { 296 - if (color.a == 0) { return; } 297 298 - /* clip */ 299 - int n; 300 - if ((n = clip.left - x) > 0) { sub->width -= n; sub->x += n; x += n; } 301 - if ((n = clip.top - y) > 0) { sub->height -= n; sub->y += n; y += n; } 302 - if ((n = x + sub->width - clip.right ) > 0) { sub->width -= n; } 303 - if ((n = y + sub->height - clip.bottom) > 0) { sub->height -= n; } 304 305 - if (sub->width <= 0 || sub->height <= 0) { 306 - return; 307 - } 308 309 - /* draw */ 310 - SDL_Surface *surf = SDL_GetWindowSurface(window); 311 - RenColor *s = image->pixels; 312 - RenColor *d = (RenColor*) surf->pixels; 313 - s += sub->x + sub->y * image->width; 314 - d += x + y * surf->w; 315 - int sr = image->width - sub->width; 316 - int dr = surf->w - sub->width; 317 318 - for (int j = 0; j < sub->height; j++) { 319 - for (int i = 0; i < sub->width; i++) { 320 - *d = blend_pixel2(*d, *s, color); 321 - d++; 322 - s++; 323 - } 324 - d += dr; 325 - s += sr; 326 - } 327 } 328 329 330 - int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) { 331 - RenRect rect; 332 - const char *p = text; 333 - unsigned codepoint; 334 - while (*p) { 335 - p = utf8_to_codepoint(p, &codepoint); 336 - GlyphSet *set = get_glyphset(font, codepoint); 337 - stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; 338 - rect.x = g->x0; 339 - rect.y = g->y0; 340 - rect.width = g->x1 - g->x0; 341 - rect.height = g->y1 - g->y0; 342 - ren_draw_image(set->image, &rect, x + g->xoff, y + g->yoff, color); 343 - x += g->xadvance; 344 - } 345 - return x; 346 }
··· 7 8 #define MAX_GLYPHSET 256 9 10 + struct RenImage 11 + { 12 + RenColor *pixels; 13 + int width, height; 14 }; 15 16 + typedef struct 17 + { 18 + RenImage *image; 19 + stbtt_bakedchar glyphs[256]; 20 } GlyphSet; 21 22 + struct RenFont 23 + { 24 + void *data; 25 + stbtt_fontinfo stbfont; 26 + GlyphSet *sets[MAX_GLYPHSET]; 27 + float size; 28 + int height; 29 }; 30 31 32 static SDL_Window *window; 33 + static struct 34 + { 35 + int left, top, right, bottom; 36 + } clip; 37 38 39 + static void* check_alloc(void *ptr) 40 + { 41 + if (!ptr) 42 + { 43 + fprintf(stderr, "Fatal error: memory allocation failed\n"); 44 + exit(EXIT_FAILURE); 45 + } 46 + return ptr; 47 } 48 49 50 + static const char* utf8_to_codepoint(const char *p, unsigned *dst) 51 + { 52 + unsigned res, n; 53 + switch (*p & 0xf0) 54 + { 55 + case 0xf0 : res = *p & 0x07; n = 3; break; 56 + case 0xe0 : res = *p & 0x0f; n = 2; break; 57 + case 0xd0 : 58 + case 0xc0 : res = *p & 0x1f; n = 1; break; 59 + default : res = *p; n = 0; break; 60 + } 61 + while (n--) 62 + res = (res << 6) | (*(++p) & 0x3f); 63 + *dst = res; 64 + return p + 1; 65 } 66 67 68 + void ren_init(SDL_Window *win) 69 + { 70 + assert(win); 71 + window = win; 72 + SDL_Surface *surf = SDL_GetWindowSurface(window); 73 + ren_set_clip_rect((RenRect){ 0, 0, surf->w, surf->h }); 74 } 75 76 77 + void ren_update_rects(RenRect *rects, int count) 78 + { 79 + SDL_UpdateWindowSurfaceRects(window, (SDL_Rect *)rects, count); 80 + static bool initial_frame = true; 81 + if (initial_frame) 82 + { 83 + SDL_ShowWindow(window); 84 + initial_frame = false; 85 + } 86 } 87 88 89 + void ren_set_clip_rect(RenRect rect) 90 + { 91 + clip.left = rect.x; 92 + clip.top = rect.y; 93 + clip.right = rect.x + rect.width; 94 + clip.bottom = rect.y + rect.height; 95 } 96 97 98 + void ren_get_size(int *x, int *y) 99 + { 100 + SDL_Surface *surf = SDL_GetWindowSurface(window); 101 + *x = surf->w; 102 + *y = surf->h; 103 } 104 105 106 + RenImage* ren_new_image(int width, int height) 107 + { 108 + assert(width > 0 && height > 0); 109 + RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor)); 110 + check_alloc(image); 111 + image->pixels = (void *)(image + 1); 112 + image->width = width; 113 + image->height = height; 114 + return image; 115 } 116 117 118 + void ren_free_image(RenImage *image) 119 + { 120 + free(image); 121 } 122 123 124 + static GlyphSet* load_glyphset(RenFont *font, int idx) 125 + { 126 + GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet))); 127 128 + /* init image */ 129 + int width = 128; 130 + int height = 128; 131 retry: 132 + set->image = ren_new_image(width, height); 133 134 + /* load glyphs */ 135 + float s = 136 + stbtt_ScaleForMappingEmToPixels(&font->stbfont, 1) / 137 + stbtt_ScaleForPixelHeight(&font->stbfont, 1); 138 + int res = stbtt_BakeFontBitmap( 139 + font->data, 0, font->size * s, (void *)set->image->pixels, 140 + width, height, idx * 256, 256, set->glyphs); 141 142 + /* retry with a larger image buffer if the buffer wasn't large enough */ 143 + if (res < 0) 144 + { 145 + width *= 2; 146 + height *= 2; 147 + ren_free_image(set->image); 148 + goto retry; 149 + } 150 151 + /* adjust glyph yoffsets and xadvance */ 152 + int ascent, descent, linegap; 153 + stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); 154 + float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size); 155 + int scaled_ascent = ascent * scale + 0.5; 156 + for (int i = 0; i < 256; i++) 157 + { 158 + set->glyphs[i].yoff += scaled_ascent; 159 + set->glyphs[i].xadvance = floor(set->glyphs[i].xadvance); 160 + } 161 162 + /* convert 8bit data to 32bit */ 163 + for (int i = width * height - 1; i >= 0; i--) 164 + { 165 + uint8_t n = *((uint8_t *)set->image->pixels + i); 166 + set->image->pixels[i] = (RenColor) { .r = 255, .g = 255, .b = 255, .a = n }; 167 + } 168 169 + return set; 170 } 171 172 173 + static GlyphSet* get_glyphset(RenFont *font, int codepoint) 174 + { 175 + int idx = (codepoint >> 8) % MAX_GLYPHSET; 176 + if (!font->sets[idx]) 177 + font->sets[idx] = load_glyphset(font, idx); 178 + return font->sets[idx]; 179 } 180 181 182 + RenFont* ren_load_font(const char *filename, float size) 183 + { 184 + RenFont *font = NULL; 185 + FILE *fp = NULL; 186 187 + /* init font */ 188 + font = check_alloc(calloc(1, sizeof(RenFont))); 189 + font->size = size; 190 191 + /* load font into buffer */ 192 + fp = fopen(filename, "rb"); 193 + if (!fp) 194 + return NULL; 195 + /* get size */ 196 + fseek(fp, 0, SEEK_END); 197 + int buf_size = ftell(fp); 198 + fseek(fp, 0, SEEK_SET); 199 + /* load */ 200 + font->data = check_alloc(malloc(buf_size)); 201 + (void)fread(font->data, 1, buf_size, fp); 202 + fclose(fp); 203 + fp = NULL; 204 205 + /* init stbfont */ 206 + int ok = stbtt_InitFont(&font->stbfont, font->data, 0); 207 + if (!ok) 208 + goto fail; 209 210 + /* get height and scale */ 211 + int ascent, descent, linegap; 212 + stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap); 213 + float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, size); 214 + font->height = (ascent - descent + linegap) * scale + 0.5; 215 216 + /* make tab and newline glyphs invisible */ 217 + stbtt_bakedchar *g = get_glyphset(font, '\n')->glyphs; 218 + g['\t'].x1 = g['\t'].x0; 219 + g['\n'].x1 = g['\n'].x0; 220 221 + return font; 222 223 fail: 224 + if (fp) 225 + fclose(fp); 226 + if (font) 227 + free(font->data); 228 + free(font); 229 + return NULL; 230 + } 231 + 232 + 233 + void ren_free_font(RenFont *font) 234 + { 235 + for (int i = 0 ; i < MAX_GLYPHSET ; i++) 236 + { 237 + GlyphSet *set = font->sets[i]; 238 + if (set) 239 + { 240 + ren_free_image(set->image); 241 + free(set); 242 + } 243 + } 244 + free(font->data); 245 + free(font); 246 } 247 248 249 + void ren_set_font_tab_width(RenFont *font, int n) 250 + { 251 + GlyphSet *set = get_glyphset(font, '\t'); 252 + set->glyphs['\t'].xadvance = n; 253 } 254 255 256 + int ren_get_font_tab_width(RenFont *font) 257 + { 258 + GlyphSet *set = get_glyphset(font, '\t'); 259 + return set->glyphs['\t'].xadvance; 260 } 261 262 263 + int ren_get_font_width(RenFont *font, const char *text) 264 + { 265 + int x = 0; 266 + const char *p = text; 267 + unsigned codepoint; 268 + while (*p) 269 + { 270 + p = utf8_to_codepoint(p, &codepoint); 271 + GlyphSet *set = get_glyphset(font, codepoint); 272 + stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; 273 + x += g->xadvance; 274 + } 275 + return x; 276 } 277 278 279 + int ren_get_font_height(RenFont *font) 280 + { 281 + return font->height; 282 } 283 284 285 + static inline RenColor blend_pixel(RenColor dst, RenColor src) 286 + { 287 + int ia = 0xff - src.a; 288 + dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8; 289 + dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8; 290 + dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8; 291 + return dst; 292 } 293 294 295 + static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color) 296 + { 297 + src.a = (src.a * color.a) >> 8; 298 + int ia = 0xff - src.a; 299 + dst.r = ((src.r * color.r * src.a) >> 16) + ((dst.r * ia) >> 8); 300 + dst.g = ((src.g * color.g * src.a) >> 16) + ((dst.g * ia) >> 8); 301 + dst.b = ((src.b * color.b * src.a) >> 16) + ((dst.b * ia) >> 8); 302 + return dst; 303 } 304 305 306 + #define rect_draw_loop(expr) \ 307 + for (int j = y1 ; j < y2 ; j++) \ 308 + { \ 309 + for (int i = x1 ; i < x2 ; i++) \ 310 + { \ 311 + *d = expr; \ 312 + d++; \ 313 + } \ 314 + d += dr; \ 315 + } 316 317 + void ren_draw_rect(RenRect rect, RenColor color) 318 + { 319 + if (color.a == 0) 320 + return; 321 322 + int x1 = rect.x < clip.left ? clip.left : rect.x; 323 + int y1 = rect.y < clip.top ? clip.top : rect.y; 324 + int x2 = rect.x + rect.width; 325 + int y2 = rect.y + rect.height; 326 + x2 = x2 > clip.right ? clip.right : x2; 327 + y2 = y2 > clip.bottom ? clip.bottom : y2; 328 329 + SDL_Surface *surf = SDL_GetWindowSurface(window); 330 + RenColor *d = (RenColor *)surf->pixels; 331 + d += x1 + y1 * surf->w; 332 + int dr = surf->w - (x2 - x1); 333 334 + if (color.a == 0xff) 335 + { 336 + rect_draw_loop(color); 337 + } 338 + else 339 + { 340 + rect_draw_loop(blend_pixel(*d, color)); 341 + } 342 } 343 344 345 + void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) 346 + { 347 + if (color.a == 0) 348 + return; 349 350 + /* clip */ 351 + int n; 352 + if ((n = clip.left - x) > 0) { sub->width -= n; sub->x += n; x += n; } 353 + if ((n = clip.top - y) > 0) { sub->height -= n; sub->y += n; y += n; } 354 + if ((n = x + sub->width - clip.right ) > 0) { sub->width -= n; } 355 + if ((n = y + sub->height - clip.bottom) > 0) { sub->height -= n; } 356 357 + if (sub->width <= 0 || sub->height <= 0) 358 + return; 359 360 + /* draw */ 361 + SDL_Surface *surf = SDL_GetWindowSurface(window); 362 + RenColor *s = image->pixels; 363 + RenColor *d = (RenColor *)surf->pixels; 364 + s += sub->x + sub->y * image->width; 365 + d += x + y * surf->w; 366 + int sr = image->width - sub->width; 367 + int dr = surf->w - sub->width; 368 369 + for (int j = 0; j < sub->height; j++) 370 + { 371 + for (int i = 0; i < sub->width; i++) 372 + { 373 + *d = blend_pixel2(*d, *s, color); 374 + d++; 375 + s++; 376 + } 377 + d += dr; 378 + s += sr; 379 + } 380 } 381 382 383 + int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) 384 + { 385 + RenRect rect; 386 + const char *p = text; 387 + unsigned codepoint; 388 + while (*p) 389 + { 390 + p = utf8_to_codepoint(p, &codepoint); 391 + GlyphSet *set = get_glyphset(font, codepoint); 392 + stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; 393 + rect.x = g->x0; 394 + rect.y = g->y0; 395 + rect.width = g->x1 - g->x0; 396 + rect.height = g->y1 - g->y0; 397 + ren_draw_image(set->image, &rect, x + g->xoff, y + g->yoff, color); 398 + x += g->xadvance; 399 + } 400 + return x; 401 }
+12 -3
src/renderer.h
··· 1 #ifndef RENDERER_H 2 #define RENDERER_H 3 4 - #include <SDL2/SDL.h> 5 #include <stdint.h> 6 7 typedef struct RenImage RenImage; 8 typedef struct RenFont RenFont; 9 10 - typedef struct { uint8_t b, g, r, a; } RenColor; 11 - typedef struct { int x, y, width, height; } RenRect; 12 13 14 void ren_init(SDL_Window *win); ··· 22 RenFont* ren_load_font(const char *filename, float size); 23 void ren_free_font(RenFont *font); 24 void ren_set_font_tab_width(RenFont *font, int n); 25 int ren_get_font_width(RenFont *font, const char *text); 26 int ren_get_font_height(RenFont *font); 27
··· 1 #ifndef RENDERER_H 2 #define RENDERER_H 3 4 + #include <SDL3/SDL.h> 5 + #include <stdlib.h> 6 #include <stdint.h> 7 8 typedef struct RenImage RenImage; 9 typedef struct RenFont RenFont; 10 11 + typedef struct 12 + { 13 + uint8_t b, g, r, a; 14 + } RenColor; 15 + 16 + typedef struct 17 + { 18 + int x, y, width, height; 19 + } RenRect; 20 21 22 void ren_init(SDL_Window *win); ··· 30 RenFont* ren_load_font(const char *filename, float size); 31 void ren_free_font(RenFont *font); 32 void ren_set_font_tab_width(RenFont *font, int n); 33 + int ren_get_font_tab_width(RenFont *font); 34 int ren_get_font_width(RenFont *font, const char *text); 35 int ren_get_font_height(RenFont *font); 36