Merge branch 'master' into keymap-improvements
This commit is contained in:
commit
285ea4f495
|
@ -0,0 +1,35 @@
|
|||
"Category: CI":
|
||||
- .github/workflows/*
|
||||
|
||||
"Category: Meta":
|
||||
- ./*
|
||||
- .github/*
|
||||
- .github/ISSUE_TEMPLATE/*
|
||||
- .github/PULL_REQUEST_TEMPLATE/*
|
||||
- .gitignore
|
||||
|
||||
"Category: Build System":
|
||||
- meson.build
|
||||
- meson_options.txt
|
||||
- subprojects/*
|
||||
|
||||
"Category: Documentation":
|
||||
- docs/**/*
|
||||
|
||||
"Category: Resources":
|
||||
- resources/**/*
|
||||
|
||||
"Category: Themes":
|
||||
- data/colors/*
|
||||
|
||||
"Category: Lua Core":
|
||||
- data/core/**/*
|
||||
|
||||
"Category: Fonts":
|
||||
- data/fonts/*
|
||||
|
||||
"Category: Plugins":
|
||||
- data/plugins/*
|
||||
|
||||
"Category: C Core":
|
||||
- src/**/*
|
|
@ -0,0 +1,16 @@
|
|||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply Type Label
|
||||
uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: "" # works around actions/labeler#104
|
|
@ -3,7 +3,6 @@ build*/
|
|||
lhelper/
|
||||
submodules/
|
||||
subprojects/lua/
|
||||
subprojects/libagg/
|
||||
subprojects/reproc/
|
||||
/appimage*
|
||||
.ccls-cache
|
||||
|
|
53
README.md
53
README.md
|
@ -77,6 +77,57 @@ DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
|
|||
Please note that the package is relocatable to any prefix and the option prefix
|
||||
affects only the place where the application is actually installed.
|
||||
|
||||
## Installing Prebuilt
|
||||
|
||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
||||
|
||||
### Linux
|
||||
|
||||
Unzip the file and `cd` into the `lite-xl` directory:
|
||||
|
||||
```sh
|
||||
tar -xzf <file>
|
||||
cd lite-xl
|
||||
```
|
||||
|
||||
To run lite-xl without installing:
|
||||
```sh
|
||||
cd bin
|
||||
./lite-xl
|
||||
```
|
||||
|
||||
To install lite-xl copy files over into appropriate directories:
|
||||
|
||||
```sh
|
||||
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
||||
cp -r share $HOME/.local
|
||||
```
|
||||
|
||||
If `$HOME/.local/bin` is not in PATH:
|
||||
|
||||
```sh
|
||||
echo -e 'export PATH=$PATH:$HOME/.local/bin' >> $HOME/.bashrc
|
||||
```
|
||||
|
||||
To get the icon to show up in app launcher:
|
||||
|
||||
```sh
|
||||
xdg-desktop-menu forceupdate
|
||||
```
|
||||
|
||||
You may need to logout and login again to see icon in app launcher.
|
||||
|
||||
To uninstall just run:
|
||||
|
||||
```sh
|
||||
rm -f $HOME/.local/bin/lite-xl
|
||||
rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
|
||||
$HOME/.local/share/applications/org.lite_xl.lite_xl.desktop \
|
||||
$HOME/.local/share/metainfo/org.lite_xl.lite_xl.appdata.xml \
|
||||
$HOME/.local/share/lite-xl
|
||||
```
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Any additional functionality that can be added through a plugin should be done
|
||||
|
@ -100,7 +151,7 @@ See the [licenses] file for details on licenses used by the required dependencie
|
|||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||
[lite]: https://github.com/rxi/lite
|
||||
[website]: https://lite-xl.github.io
|
||||
[build]: https://lite-xl.github.io/en/build
|
||||
[build]: https://lite-xl.github.io/en/documentation/build/
|
||||
[Get Lite XL]: https://github.com/franko/lite-xl/releases/latest
|
||||
[Get plugins]: https://github.com/franko/lite-plugins
|
||||
[Get color themes]: https://github.com/rxi/lite-colors
|
||||
|
|
44
changelog.md
44
changelog.md
|
@ -1,5 +1,49 @@
|
|||
This files document the changes done in Lite XL for each release.
|
||||
|
||||
### 2.0.2
|
||||
|
||||
Fix problem project directory when starting the application from Launcher on macOS.
|
||||
|
||||
Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content.
|
||||
|
||||
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence.
|
||||
The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`.
|
||||
|
||||
Added a command to create a multi-cursor with all the occurrences of the current selection.
|
||||
Activated with the shortcut `ctrl+shift+l`.
|
||||
|
||||
Fix problem when trying to close an unsaved new document.
|
||||
|
||||
No longer shows an error for the `-psn` argument passed to the application on macOS.
|
||||
|
||||
Fix `treeview:open-in-system` command on Windows.
|
||||
|
||||
Fix rename command to update name of document if opened.
|
||||
|
||||
Improve the find and replace dialog so that previously used expressions can be recalled
|
||||
using "up" and "down" keys.
|
||||
|
||||
Build package script rewrite with many improvements.
|
||||
|
||||
Use bigger fonts by default.
|
||||
|
||||
Other minor improvements and fixes.
|
||||
|
||||
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101.
|
||||
|
||||
### 2.0.1
|
||||
|
||||
Fix a few bugs and we mandate the mod-version 2 for plugins.
|
||||
This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
|
||||
|
||||
Here some details about the bug fixes:
|
||||
|
||||
- fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents
|
||||
- add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts
|
||||
- fix focus problem with NagView when switching project directory
|
||||
- fix error that prevented the verification of plugins versions
|
||||
- fix error on X11 that caused a bug window event on exit
|
||||
|
||||
### 2.0
|
||||
|
||||
The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured;
|
||||
|
|
|
@ -148,7 +148,9 @@ command.add(nil, {
|
|||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Change Project Folder", function(text, item)
|
||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||
if text == core.project_dir then return end
|
||||
|
@ -162,6 +164,10 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:open-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Open Project", function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
local path_stat = system.get_file_info(text)
|
||||
|
|
|
@ -168,16 +168,6 @@ local commands = {
|
|||
doc():set_selection(line, col)
|
||||
end,
|
||||
|
||||
|
||||
["doc:indent"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:select-lines"] = function()
|
||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
|
|
|
@ -12,6 +12,7 @@ local last_finds, last_view, last_fn, last_text, last_sel
|
|||
|
||||
local case_sensitive = config.find_case_sensitive or false
|
||||
local find_regex = config.find_regex or false
|
||||
local found_expression
|
||||
|
||||
local function doc()
|
||||
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
|
||||
|
@ -34,24 +35,37 @@ local function update_preview(sel, search_fn, text)
|
|||
if ok and line1 and text ~= "" then
|
||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
return true
|
||||
found_expression = true
|
||||
else
|
||||
last_view.doc:set_selection(unpack(sel))
|
||||
return false
|
||||
found_expression = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function insert_unique(t, v)
|
||||
local n = #t
|
||||
for i = 1, n do
|
||||
if t[i] == v then return end
|
||||
end
|
||||
t[n + 1] = v
|
||||
end
|
||||
|
||||
|
||||
local function find(label, search_fn)
|
||||
last_view, last_sel, last_finds = core.active_view,
|
||||
{ core.active_view.doc:get_selection() }, {}
|
||||
local text, found = last_view.doc:get_text(unpack(last_sel)), false
|
||||
local text = last_view.doc:get_text(unpack(last_sel))
|
||||
found_expression = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
|
||||
core.command_view:enter(label, function(text)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(label, function(text, item)
|
||||
insert_unique(core.previous_find, text)
|
||||
core.status_view:remove_tooltip()
|
||||
if found then
|
||||
if found_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
|
@ -59,8 +73,9 @@ local function find(label, search_fn)
|
|||
last_view:scroll_to_make_visible(unpack(last_sel))
|
||||
end
|
||||
end, function(text)
|
||||
found = update_preview(last_sel, search_fn, text)
|
||||
update_preview(last_sel, search_fn, text)
|
||||
last_fn, last_text = search_fn, text
|
||||
return core.previous_find
|
||||
end, function(explicit)
|
||||
core.status_view:remove_tooltip()
|
||||
if explicit then
|
||||
|
@ -75,19 +90,24 @@ local function replace(kind, default, fn)
|
|||
core.command_view:set_text(default, true)
|
||||
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||
insert_unique(core.previous_find, old)
|
||||
core.command_view:set_text(old, true)
|
||||
|
||||
local s = string.format("Replace %s %q With", kind, old)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(s, function(new)
|
||||
core.status_view:remove_tooltip()
|
||||
insert_unique(core.previous_replace, new)
|
||||
local n = doc():replace(function(text)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||
end, function() end, function()
|
||||
end, function() return core.previous_replace end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end, function() end, function()
|
||||
end, function() return core.previous_find end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end
|
||||
|
@ -108,6 +128,20 @@ local function has_unique_selection()
|
|||
return text ~= nil
|
||||
end
|
||||
|
||||
local function is_in_selection(line, col, l1, c1, l2, c2)
|
||||
if line < l1 or line > l2 then return false end
|
||||
if line == l1 and col <= c1 then return false end
|
||||
if line == l2 and col > c2 then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_in_any_selection(line, col)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
||||
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function select_next(all)
|
||||
local il1, ic1 = doc():get_selection(true)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
|
@ -115,15 +149,27 @@ local function select_next(all)
|
|||
repeat
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l1 == il1 and c1 == ic1 then break end
|
||||
if l2 then doc():add_selection(l2, c2, l1, c1) end
|
||||
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
||||
doc():add_selection(l2, c2, l1, c1)
|
||||
if not all then
|
||||
core.active_view:scroll_to_make_visible(l2, c2)
|
||||
return
|
||||
end
|
||||
end
|
||||
until not all or not l2
|
||||
break
|
||||
if all then break end
|
||||
end
|
||||
end
|
||||
|
||||
command.add(has_unique_selection, {
|
||||
["find-replace:select-next"] = function() select_next(false) end,
|
||||
["find-replace:select-all"] = function() select_next(true) end
|
||||
["find-replace:select-next"] = function()
|
||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||
end,
|
||||
["find-replace:select-add-next"] = function() select_next(false) end,
|
||||
["find-replace:select-add-all"] = function() select_next(true) end
|
||||
})
|
||||
|
||||
command.add("core.docview", {
|
||||
|
@ -135,7 +181,9 @@ command.add("core.docview", {
|
|||
end,
|
||||
|
||||
["find-replace:replace"] = function()
|
||||
replace("Text", doc():get_text(doc():get_selection(true)), function(text, old, new)
|
||||
local l1, c1, l2, c2 = doc():get_selection()
|
||||
local selected_text = doc():get_text(l1, c1, l2, c2)
|
||||
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
||||
if not find_regex then
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end
|
||||
|
|
|
@ -34,6 +34,7 @@ function CommandView:new()
|
|||
self.suggestion_idx = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.show_suggestions = true
|
||||
self.last_change_id = 0
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
|
@ -45,6 +46,11 @@ function CommandView:new()
|
|||
end
|
||||
|
||||
|
||||
function CommandView:set_hidden_suggestions()
|
||||
self.show_suggestions = false
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_name()
|
||||
return View.get_name(self)
|
||||
end
|
||||
|
@ -83,10 +89,29 @@ end
|
|||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
if self.show_suggestions then
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
else
|
||||
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
||||
local text = self:get_text()
|
||||
if text == current_suggestion then
|
||||
local n = self.suggestion_idx + dir
|
||||
if n == 0 and self.save_suggestion then
|
||||
self:set_text(self.save_suggestion)
|
||||
else
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
end
|
||||
else
|
||||
self.save_suggestion = text
|
||||
self:complete()
|
||||
end
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
self.state.suggest(self:get_text())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -134,6 +159,8 @@ function CommandView:exit(submitted, inexplicit)
|
|||
self.doc:reset()
|
||||
self.suggestions = {}
|
||||
if not submitted then cancel(not inexplicit) end
|
||||
self.show_suggestions = true
|
||||
self.save_suggestion = nil
|
||||
end
|
||||
|
||||
|
||||
|
@ -187,7 +214,7 @@ function CommandView:update()
|
|||
|
||||
-- update suggestions box height
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dest = math.min(#self.suggestions, max_suggestions) * lh
|
||||
local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
||||
self:move_towards("suggestions_height", dest)
|
||||
|
||||
-- update suggestion cursor offset
|
||||
|
@ -256,7 +283,9 @@ end
|
|||
|
||||
function CommandView:draw()
|
||||
CommandView.super.draw(self)
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
if self.show_suggestions then
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,11 @@ function common.lerp(a, b, t)
|
|||
end
|
||||
|
||||
|
||||
function common.distance(x1, y1, x2, y2)
|
||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||
end
|
||||
|
||||
|
||||
function common.color(str)
|
||||
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
if r then
|
||||
|
@ -276,6 +281,7 @@ end
|
|||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if not filename then return end
|
||||
if PATHSEP == '\\' then
|
||||
filename = filename:gsub('[/\\]', '\\')
|
||||
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||
|
@ -290,7 +296,8 @@ function common.normalize_path(filename)
|
|||
table.insert(accu, part)
|
||||
end
|
||||
end
|
||||
return table.concat(accu, PATHSEP)
|
||||
local npath = table.concat(accu, PATHSEP)
|
||||
return npath == "" and PATHSEP or npath
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ config.fps = 60
|
|||
config.max_log_items = 80
|
||||
config.message_timeout = 5
|
||||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.scroll_past_end = true
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = "^%."
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
|
@ -23,6 +24,7 @@ config.max_project_files = 2000
|
|||
config.transitions = true
|
||||
config.animation_rate = 1.0
|
||||
config.blink_period = 0.8
|
||||
config.disable_blink = false
|
||||
config.draw_whitespace = false
|
||||
config.borderless = false
|
||||
config.tab_close_button = true
|
||||
|
@ -33,5 +35,6 @@ config.plugins = {}
|
|||
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
config.plugins.drawwhitespace = false
|
||||
|
||||
return config
|
||||
|
|
|
@ -24,7 +24,7 @@ function Highlighter:new(doc)
|
|||
for i = self.first_invalid_line, max do
|
||||
local state = (i > 1) and self.lines[i - 1].state
|
||||
local line = self.lines[i]
|
||||
if not (line and line.init_state == state) then
|
||||
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
||||
self.lines[i] = self:tokenize_line(i, state)
|
||||
end
|
||||
end
|
||||
|
@ -44,12 +44,25 @@ function Highlighter:reset()
|
|||
self.max_wanted_line = 0
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:invalidate(idx)
|
||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||
end
|
||||
|
||||
function Highlighter:insert_notify(line, n)
|
||||
self:invalidate(line)
|
||||
for i = 1, n do
|
||||
table.insert(self.lines, line, nil)
|
||||
end
|
||||
end
|
||||
|
||||
function Highlighter:remove_notify(line, n)
|
||||
self:invalidate(line)
|
||||
for i = 1, n do
|
||||
table.remove(self.lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:tokenize_line(idx, state)
|
||||
local res = {}
|
||||
|
|
|
@ -102,7 +102,11 @@ end
|
|||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id() or self.new_file
|
||||
if self.new_file then
|
||||
return #self.lines > 1 or #self.lines[1] > 1
|
||||
else
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -348,7 +352,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
|||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line)
|
||||
self.highlighter:insert_notify(line, #lines - 1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
@ -375,7 +379,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|||
end
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line1)
|
||||
self.highlighter:remove_notify(line1, line2 - line1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ end
|
|||
|
||||
|
||||
function DocView:get_scrollable_size()
|
||||
if not config.scroll_past_end then
|
||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||
end
|
||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||
end
|
||||
|
||||
|
@ -150,14 +153,14 @@ function DocView:get_col_x_offset(line, col)
|
|||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
if column == col then
|
||||
return xoffset / font:subpixel_scale()
|
||||
return xoffset
|
||||
end
|
||||
xoffset = xoffset + font:get_width_subpixel(char)
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
column = column + #char
|
||||
end
|
||||
end
|
||||
|
||||
return xoffset / default_font:subpixel_scale()
|
||||
return xoffset
|
||||
end
|
||||
|
||||
|
||||
|
@ -166,14 +169,12 @@ function DocView:get_x_offset_col(line, x)
|
|||
|
||||
local xoffset, last_i, i = 0, 1, 1
|
||||
local default_font = self:get_font()
|
||||
local subpixel_scale = default_font:subpixel_scale()
|
||||
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = font:get_width_subpixel(char)
|
||||
if xoffset >= subpixel_scale * x then
|
||||
return (xoffset - x_subpixel > w / 2) and last_i or i
|
||||
local w = font:get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
|
@ -339,16 +340,11 @@ end
|
|||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local default_font = self:get_font()
|
||||
local subpixel_scale = default_font:subpixel_scale()
|
||||
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
local color = style.syntax[type]
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
if config.draw_whitespace then
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
||||
else
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
|
||||
end
|
||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -358,6 +354,18 @@ function DocView:draw_caret(x, y)
|
|||
end
|
||||
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
-- draw highlight if any selection ends on this line
|
||||
local draw_highlight = false
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == idx then
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
-- draw selection if it overlaps this line
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
if idx >= line1 and idx <= line2 then
|
||||
|
@ -372,15 +380,6 @@ function DocView:draw_line_body(idx, x, y)
|
|||
end
|
||||
end
|
||||
end
|
||||
local draw_highlight = nil
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
-- draw line highlight if caret is on this line
|
||||
if draw_highlight ~= false and config.highlight_current_line
|
||||
and line1 == idx and core.active_view == self then
|
||||
draw_highlight = (line1 == line2 and col1 == col2)
|
||||
end
|
||||
end
|
||||
if draw_highlight then self:draw_line_highlight(x + self.scroll.x, y) end
|
||||
|
||||
-- draw line's text
|
||||
self:draw_line_text(idx, x, y)
|
||||
|
@ -408,10 +407,12 @@ function DocView:draw_overlay()
|
|||
local T = config.blink_period
|
||||
for _, line, col in self.doc:get_selections() do
|
||||
if line >= minline and line <= maxline
|
||||
and (core.blink_timer - core.blink_start) % T < T / 2
|
||||
and system.window_has_focus() then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,7 @@ local core = {}
|
|||
|
||||
local function load_session()
|
||||
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
||||
if ok and t then
|
||||
return t.recents, t.window, t.window_mode
|
||||
end
|
||||
return {}
|
||||
return ok and t or {}
|
||||
end
|
||||
|
||||
|
||||
|
@ -30,6 +27,8 @@ local function save_session()
|
|||
fp:write("return {recents=", common.serialize(core.recent_projects),
|
||||
", window=", common.serialize(table.pack(system.get_window_size())),
|
||||
", window_mode=", common.serialize(system.get_window_mode()),
|
||||
", previous_find=", common.serialize(core.previous_find),
|
||||
", previous_replace=", common.serialize(core.previous_replace),
|
||||
"}\n")
|
||||
fp:close()
|
||||
end
|
||||
|
@ -80,6 +79,9 @@ function core.open_folder_project(dir_path_abs)
|
|||
if core.set_project_dir(dir_path_abs, core.on_quit_project) then
|
||||
core.root_view:close_all_docviews()
|
||||
update_recents_project("add", dir_path_abs)
|
||||
if not core.load_project_module() then
|
||||
command.perform("core:open-log")
|
||||
end
|
||||
core.on_enter_project(dir_path_abs)
|
||||
end
|
||||
end
|
||||
|
@ -320,8 +322,8 @@ local style = require "core.style"
|
|||
------------------------------- Fonts ----------------------------------------
|
||||
|
||||
-- customize fonts:
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 14 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE)
|
||||
--
|
||||
-- font names used by lite:
|
||||
-- style.font : user interface
|
||||
|
@ -394,15 +396,6 @@ function core.remove_project_directory(path)
|
|||
return false
|
||||
end
|
||||
|
||||
|
||||
local function whitespace_replacements()
|
||||
local r = renderer.replacements.new()
|
||||
r:add(" ", "·")
|
||||
r:add("\t", "»")
|
||||
return r
|
||||
end
|
||||
|
||||
|
||||
local function reload_on_user_module_save()
|
||||
-- auto-realod style when user's module is saved by overriding Doc:Save()
|
||||
local doc_save = Doc.save
|
||||
|
@ -435,13 +428,15 @@ function core.init()
|
|||
end
|
||||
|
||||
do
|
||||
local recent_projects, window_position, window_mode = load_session()
|
||||
if window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(window_position))
|
||||
elseif window_mode == "maximized" then
|
||||
local session = load_session()
|
||||
if session.window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(session.window))
|
||||
elseif session.window_mode == "maximized" then
|
||||
system.set_window_mode("maximized")
|
||||
end
|
||||
core.recent_projects = recent_projects or {}
|
||||
core.recent_projects = session.recents or {}
|
||||
core.previous_find = session.previous_find or {}
|
||||
core.previous_replace = session.previous_replace or {}
|
||||
end
|
||||
|
||||
local project_dir = core.recent_projects[1] or "."
|
||||
|
@ -461,7 +456,10 @@ function core.init()
|
|||
project_dir = arg_filename
|
||||
project_dir_explicit = true
|
||||
else
|
||||
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
||||
-- on macOS we can get an argument like "-psn_0_52353" that we just ignore.
|
||||
if not ARGS[i]:match("^-psn") then
|
||||
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -495,7 +493,6 @@ function core.init()
|
|||
core.visited_files = {}
|
||||
core.restart_request = false
|
||||
core.quit_request = false
|
||||
core.replacements = whitespace_replacements()
|
||||
|
||||
core.root_view = RootView()
|
||||
core.command_view = CommandView()
|
||||
|
@ -678,16 +675,18 @@ function core.load_plugins()
|
|||
userdir = {dir = USERDIR, plugins = {}},
|
||||
datadir = {dir = DATADIR, plugins = {}},
|
||||
}
|
||||
local files = {}
|
||||
local files, ordered = {}, {}
|
||||
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||
local plugin_dir = root_dir .. "/plugins"
|
||||
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
|
||||
if not files[filename] then table.insert(ordered, filename) end
|
||||
files[filename] = plugin_dir -- user plugins will always replace system plugins
|
||||
end
|
||||
end
|
||||
table.sort(ordered)
|
||||
|
||||
for filename, plugin_dir in pairs(files) do
|
||||
local basename = filename:match("(.-)%.lua$") or filename
|
||||
for _, filename in ipairs(ordered) do
|
||||
local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename
|
||||
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||
if is_lua_file then
|
||||
if not version_match then
|
||||
|
|
|
@ -66,9 +66,10 @@ local function keymap_macos(keymap)
|
|||
["cmd+shift+return"] = "doc:newline-above",
|
||||
["cmd+j"] = "doc:join-lines",
|
||||
["cmd+a"] = "doc:select-all",
|
||||
["cmd+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["cmd+f3"] = "find-replace:select-next",
|
||||
["cmd+l"] = "doc:select-lines",
|
||||
["cmd+shift+l"] = { "find-replace:select-all", "doc:select-word" },
|
||||
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["cmd+/"] = "doc:toggle-line-comments",
|
||||
["option+up"] = "doc:move-lines-up",
|
||||
["option+down"] = "doc:move-lines-down",
|
||||
|
|
|
@ -5,9 +5,10 @@ keymap.modkeys = {}
|
|||
keymap.map = {}
|
||||
keymap.reverse_map = {}
|
||||
|
||||
local macos = PLATFORM == "Mac OS X"
|
||||
|
||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||
local modkeys_os = require("core.modkeys-" .. (MACOS and "macos" or "generic"))
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
|
||||
local modkey_map = modkeys_os.map
|
||||
local modkeys = modkeys_os.keys
|
||||
|
||||
|
@ -37,7 +38,7 @@ end
|
|||
|
||||
function keymap.add(map, overwrite)
|
||||
for stroke, commands in pairs(map) do
|
||||
if MACOS then
|
||||
if macos then
|
||||
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
|
||||
end
|
||||
if type(commands) == "string" then
|
||||
|
@ -119,7 +120,7 @@ function keymap.on_key_released(k)
|
|||
end
|
||||
|
||||
|
||||
if MACOS then
|
||||
if macos then
|
||||
local keymap_macos = require("core.keymap-macos")
|
||||
keymap_macos(keymap)
|
||||
return keymap
|
||||
|
@ -193,9 +194,10 @@ keymap.add_direct {
|
|||
["ctrl+shift+return"] = "doc:newline-above",
|
||||
["ctrl+j"] = "doc:join-lines",
|
||||
["ctrl+a"] = "doc:select-all",
|
||||
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["ctrl+f3"] = "find-replace:select-next",
|
||||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+shift+l"] = { "find-replace:select-all", "doc:select-word" },
|
||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
|
|
|
@ -23,7 +23,7 @@ end
|
|||
-- Moves to the end of the identified character.
|
||||
local function end_character(str, index)
|
||||
local byte = string.byte(str, index + 1)
|
||||
while byte >= 128 and byte < 192 do
|
||||
while byte and byte >= 128 and byte < 192 do
|
||||
index = index + 1
|
||||
byte = string.byte(str, index + 1)
|
||||
end
|
||||
|
|
|
@ -142,7 +142,14 @@ function Node:remove_view(root, view)
|
|||
local parent = self:get_parent_node(root)
|
||||
local is_a = (parent.a == self)
|
||||
local other = parent[is_a and "b" or "a"]
|
||||
if other:get_locked_size() then
|
||||
local locked_size_x, locked_size_y = other:get_locked_size()
|
||||
local locked_size
|
||||
if parent.type == "hsplit" then
|
||||
locked_size = locked_size_x
|
||||
else
|
||||
locked_size = locked_size_y
|
||||
end
|
||||
if self.is_primary_node or locked_size then
|
||||
self.views = {}
|
||||
self:add_view(EmptyView())
|
||||
else
|
||||
|
@ -273,7 +280,10 @@ end
|
|||
|
||||
function Node:should_show_tabs()
|
||||
if self.locked then return false end
|
||||
if #self.views > 1 then return true
|
||||
local dn = core.root_view.dragged_node
|
||||
if #self.views > 1
|
||||
or (dn and dn.dragging) then -- show tabs while dragging
|
||||
return true
|
||||
elseif config.always_show_tabs then
|
||||
return not self.views[1]:is(EmptyView)
|
||||
end
|
||||
|
@ -506,6 +516,53 @@ function Node:update()
|
|||
end
|
||||
end
|
||||
|
||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if standalone then
|
||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||
end
|
||||
if is_active then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = is_close_hovered and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if is_hovered then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function Node:draw_tabs()
|
||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||
|
@ -530,47 +587,9 @@ function Node:draw_tabs()
|
|||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||
local view = self.views[i]
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
local text = view:get_name()
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if view == self.active_view then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((view == self.active_view or i == self.hovered_tab) and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = self.hovered_close == i and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if i == self.hovered_tab then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
self:draw_tab(view:get_name(), view == self.active_view,
|
||||
i == self.hovered_tab, i == self.hovered_close,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
|
@ -696,6 +715,62 @@ function Node:resize(axis, value)
|
|||
end
|
||||
|
||||
|
||||
function Node:get_split_type(mouse_x, mouse_y)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
|
||||
local local_mouse_x = mouse_x - x
|
||||
local local_mouse_y = mouse_y - y
|
||||
|
||||
if local_mouse_y < 0 then
|
||||
return "tab"
|
||||
else
|
||||
local left_pct = local_mouse_x * 100 / w
|
||||
local top_pct = local_mouse_y * 100 / h
|
||||
if left_pct <= 30 then
|
||||
return "left"
|
||||
elseif left_pct >= 70 then
|
||||
return "right"
|
||||
elseif top_pct <= 30 then
|
||||
return "up"
|
||||
elseif top_pct >= 70 then
|
||||
return "down"
|
||||
end
|
||||
return "middle"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
||||
local tab_index = self:get_tab_overlapping_point(x, y)
|
||||
if not tab_index then
|
||||
local first_tab_x = self:get_tab_rect(1)
|
||||
if x < first_tab_x then
|
||||
-- mouse before first visible tab
|
||||
tab_index = self.tab_offset or 1
|
||||
else
|
||||
-- mouse after last visible tab
|
||||
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
||||
end
|
||||
end
|
||||
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
||||
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
||||
-- use next tab
|
||||
tab_x = tab_x + tab_w
|
||||
tab_index = tab_index + 1
|
||||
end
|
||||
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
||||
-- the tab we are moving is counted in tab_index
|
||||
tab_index = tab_index - 1
|
||||
tab_x = tab_x - tab_w
|
||||
end
|
||||
return tab_index, tab_x, tab_y, tab_w, tab_h
|
||||
end
|
||||
|
||||
|
||||
local RootView = View:extend()
|
||||
|
||||
function RootView:new()
|
||||
|
@ -703,6 +778,14 @@ function RootView:new()
|
|||
self.root_node = Node()
|
||||
self.deferred_draws = {}
|
||||
self.mouse = { x = 0, y = 0 }
|
||||
self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay,
|
||||
color = { table.unpack(style.drag_overlay) } }
|
||||
self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay_tab,
|
||||
color = { table.unpack(style.drag_overlay_tab) } }
|
||||
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
end
|
||||
|
||||
|
||||
|
@ -786,10 +869,12 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
|||
if button == "middle" or node.hovered_close == idx then
|
||||
node:close_view(self.root_node, node.views[idx])
|
||||
else
|
||||
self.dragged_node = { node, idx }
|
||||
if button == "left" then
|
||||
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
|
||||
end
|
||||
node:set_active_view(node.views[idx])
|
||||
end
|
||||
else
|
||||
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
||||
core.set_active_view(node.active_view)
|
||||
if not self.on_view_mouse_pressed(button, x, y, clicks) then
|
||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
|
@ -798,14 +883,73 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(...)
|
||||
function RootView:get_overlay_base_color(overlay)
|
||||
if overlay == self.drag_overlay then
|
||||
return style.drag_overlay
|
||||
else
|
||||
return style.drag_overlay_tab
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_show_overlay(overlay, status)
|
||||
overlay.visible = status
|
||||
if status then -- reset colors
|
||||
-- reload base_color
|
||||
overlay.base_color = self:get_overlay_base_color(overlay)
|
||||
overlay.color[1] = overlay.base_color[1]
|
||||
overlay.color[2] = overlay.base_color[2]
|
||||
overlay.color[3] = overlay.base_color[3]
|
||||
overlay.color[4] = overlay.base_color[4]
|
||||
overlay.opacity = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(button, x, y, ...)
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
end
|
||||
if self.dragged_node then
|
||||
self.dragged_node = nil
|
||||
if button == "left" then
|
||||
if self.dragged_node.dragging then
|
||||
local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
local dragged_node = self.dragged_node.node
|
||||
|
||||
if node and not node.locked
|
||||
-- don't do anything if dragging onto own node, with only one view
|
||||
and (node ~= dragged_node or #node.views > 1) then
|
||||
local split_type = node:get_split_type(self.mouse.x, self.mouse.y)
|
||||
local view = dragged_node.views[self.dragged_node.idx]
|
||||
|
||||
if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting
|
||||
local new_node = node:split(split_type)
|
||||
self.root_node:get_node_for_view(view):remove_view(self.root_node, view)
|
||||
new_node:add_view(view)
|
||||
elseif split_type == "middle" and node ~= dragged_node then -- move to other node
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
elseif split_type == "tab" then -- move besides other tabs
|
||||
local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx)
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view, tab_index)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
end
|
||||
self.root_node:update_layout()
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
self.dragged_node = nil
|
||||
end
|
||||
else -- avoid sending on_mouse_released events when dragging tabs
|
||||
self.root_node:on_mouse_released(button, x, y, ...)
|
||||
end
|
||||
self.root_node:on_mouse_released(...)
|
||||
end
|
||||
|
||||
|
||||
|
@ -841,6 +985,19 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
end
|
||||
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
|
||||
local dn = self.dragged_node
|
||||
if dn and not dn.dragging then
|
||||
-- start dragging only after enough movement
|
||||
dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05
|
||||
if dn.dragging then
|
||||
core.request_cursor("hand")
|
||||
end
|
||||
end
|
||||
|
||||
-- avoid sending on_mouse_moved events when dragging tabs
|
||||
if dn then return end
|
||||
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
@ -855,24 +1012,6 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
elseif node then
|
||||
core.request_cursor(node.active_view.cursor)
|
||||
end
|
||||
if node and self.dragged_node and (self.dragged_node[1] ~= node or (tab_index and self.dragged_node[2] ~= tab_index))
|
||||
and node.type == "leaf" and #node.views > 0 and node.views[1]:is(DocView) then
|
||||
local tab = self.dragged_node[1].views[self.dragged_node[2]]
|
||||
if self.dragged_node[1] ~= node then
|
||||
for i, v in ipairs(node.views) do if v.doc == tab.doc then tab = nil break end end
|
||||
if tab then
|
||||
self.dragged_node[1]:remove_view(self.root_node, tab)
|
||||
node:add_view(tab, tab_index)
|
||||
self.root_node:update_layout()
|
||||
self.dragged_node = { node, tab_index or #node.views }
|
||||
core.redraw = true
|
||||
end
|
||||
else
|
||||
table.remove(self.dragged_node[1].views, self.dragged_node[2])
|
||||
table.insert(node.views, tab_index, tab)
|
||||
self.dragged_node = { node, tab_index }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -893,10 +1032,110 @@ function RootView:on_focus_lost(...)
|
|||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
function RootView:interpolate_drag_overlay(overlay)
|
||||
self:move_towards(overlay, "x", overlay.to.x)
|
||||
self:move_towards(overlay, "y", overlay.to.y)
|
||||
self:move_towards(overlay, "w", overlay.to.w)
|
||||
self:move_towards(overlay, "h", overlay.to.h)
|
||||
|
||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
||||
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
||||
end
|
||||
|
||||
|
||||
function RootView:update()
|
||||
copy_position_and_size(self.root_node, self)
|
||||
self.root_node:update()
|
||||
self.root_node:update_layout()
|
||||
|
||||
self:update_drag_overlay()
|
||||
self:interpolate_drag_overlay(self.drag_overlay)
|
||||
self:interpolate_drag_overlay(self.drag_overlay_tab)
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_drag_overlay(overlay, x, y, w, h, immediate)
|
||||
overlay.to.x = x
|
||||
overlay.to.y = y
|
||||
overlay.to.w = w
|
||||
overlay.to.h = h
|
||||
if immediate then
|
||||
overlay.x = x
|
||||
overlay.y = y
|
||||
overlay.w = w
|
||||
overlay.h = h
|
||||
end
|
||||
if not overlay.visible then
|
||||
self:set_show_overlay(overlay, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_split_sizes(split_type, x, y, w, h)
|
||||
if split_type == "left" then
|
||||
w = w * .5
|
||||
elseif split_type == "right" then
|
||||
x = x + w * .5
|
||||
w = w * .5
|
||||
elseif split_type == "up" then
|
||||
h = h * .5
|
||||
elseif split_type == "down" then
|
||||
y = y + h * .5
|
||||
h = h * .5
|
||||
end
|
||||
return x, y, w, h
|
||||
end
|
||||
|
||||
|
||||
function RootView:update_drag_overlay()
|
||||
if not (self.dragged_node and self.dragged_node.dragging) then return end
|
||||
local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
if over and not over.locked then
|
||||
local _, _, _, tab_h = over:get_scroll_button_rect(1)
|
||||
local x, y = over.position.x, over.position.y
|
||||
local w, h = over.size.x, over.size.y
|
||||
local split_type = over:get_split_type(self.mouse.x, self.mouse.y)
|
||||
|
||||
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
|
||||
self:set_drag_overlay(self.drag_overlay_tab,
|
||||
tab_x + (tab_index and 0 or tab_w), tab_y,
|
||||
style.caret_width, tab_h,
|
||||
-- avoid showing tab overlay moving between nodes
|
||||
over ~= self.drag_overlay_tab.last_over)
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self.drag_overlay_tab.last_over = over
|
||||
else
|
||||
if (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
x, y, w, h = get_split_sizes(split_type, x, y, w, h)
|
||||
end
|
||||
self:set_drag_overlay(self.drag_overlay, x, y, w, h)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
else
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_grabbed_tab()
|
||||
local dn = self.dragged_node
|
||||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||
local x = self.mouse.x - w / 2
|
||||
local y = self.mouse.y - h / 2
|
||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_drag_overlay(ov)
|
||||
if ov.opacity > 0 then
|
||||
renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -906,6 +1145,12 @@ function RootView:draw()
|
|||
local t = table.remove(self.deferred_draws)
|
||||
t.fn(table.unpack(t))
|
||||
end
|
||||
|
||||
self:draw_drag_overlay(self.drag_overlay)
|
||||
self:draw_drag_overlay(self.drag_overlay_tab)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
self:draw_grabbed_tab()
|
||||
end
|
||||
if core.cursor_change_req then
|
||||
system.set_cursor(core.cursor_change_req)
|
||||
core.cursor_change_req = nil
|
||||
|
|
|
@ -19,3 +19,12 @@ package.path = DATADIR .. '/?.lua;' .. package.path
|
|||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||
|
||||
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
||||
package.native_plugins = {}
|
||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
||||
local path = package.searchpath(modname, package.cpath)
|
||||
if not path then return nil end
|
||||
return system.load_native_plugin, path
|
||||
end }
|
||||
|
|
|
@ -6,6 +6,7 @@ local style = require "core.style"
|
|||
local DocView = require "core.docview"
|
||||
local LogView = require "core.logview"
|
||||
local View = require "core.view"
|
||||
local Object = require "core.object"
|
||||
|
||||
|
||||
local StatusView = View:extend()
|
||||
|
@ -70,7 +71,7 @@ local function draw_items(self, items, x, y, draw_fn)
|
|||
local color = style.text
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if type(item) == "userdata" then
|
||||
if Object.is(item, renderer.font) then
|
||||
font = item
|
||||
elseif type(item) == "table" then
|
||||
color = item
|
||||
|
|
|
@ -21,11 +21,11 @@ style.tab_width = common.round(170 * SCALE)
|
|||
--
|
||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||
style.big_font = style.font:copy(40 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(20 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
|
||||
style.big_font = style.font:copy(46 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
||||
|
||||
style.background = { common.color "#2e2e32" }
|
||||
style.background2 = { common.color "#252529" }
|
||||
|
@ -44,6 +44,8 @@ style.scrollbar2 = { common.color "#4b4b52" }
|
|||
style.nagbar = { common.color "#FF0000" }
|
||||
style.nagbar_text = { common.color "#FFFFFF" }
|
||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||
|
||||
style.syntax = {}
|
||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||
|
|
|
@ -10,7 +10,7 @@ local RootView = require "core.rootview"
|
|||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.plugins.autocomplete = {
|
||||
config.plugins.autocomplete = {
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
-- The max amount of visible items
|
||||
|
@ -502,6 +502,11 @@ command.add(predicate, {
|
|||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||
end,
|
||||
|
||||
["autocomplete:cycle"] = function()
|
||||
local newidx = suggestions_idx + 1
|
||||
suggestions_idx = newidx > #suggestions and 1 or newidx
|
||||
end,
|
||||
|
||||
["autocomplete:cancel"] = function()
|
||||
reset_suggestions()
|
||||
end,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local common = require "core.common"
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local font = (self:get_font() or style.syntax_fonts["comment"])
|
||||
local color = style.syntax.comment
|
||||
local ty, tx = y + self:get_line_text_y_offset()
|
||||
local text, offset, s, e = self.doc.lines[idx], 1
|
||||
while true do
|
||||
s, e = text:find(" +", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
||||
offset = e + 1
|
||||
end
|
||||
offset = 1
|
||||
while true do
|
||||
s, e = text:find("\t", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, "»", tx, ty, color)
|
||||
offset = e + 1
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
||||
|
||||
|
|
@ -1,22 +1,41 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
|
||||
|
||||
syntax.add {
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
||||
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
},
|
||||
symbols = { },
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
|
|
|
@ -7,8 +7,7 @@ local draw_overlay = DocView.draw_overlay
|
|||
|
||||
function DocView:draw_overlay(...)
|
||||
local ns = ("n"):rep(config.line_limit)
|
||||
local ss = self:get_font():subpixel_scale()
|
||||
local offset = self:get_font():get_width_subpixel(ns) / ss
|
||||
local offset = self:get_font():get_width(ns)
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
|
|
|
@ -13,9 +13,6 @@ config.plugins.scale = {
|
|||
use_mousewheel = true
|
||||
}
|
||||
|
||||
local MINIMUM_SCALE = 0.25;
|
||||
|
||||
local scale_level = 0
|
||||
local scale_steps = 0.05
|
||||
|
||||
local current_scale = SCALE
|
||||
|
@ -36,9 +33,6 @@ local function set_scale(scale)
|
|||
local s = scale / current_scale
|
||||
current_scale = scale
|
||||
|
||||
-- we set scale_level in case this was called by user
|
||||
scale_level = (scale - default_scale) / scale_steps
|
||||
|
||||
if config.plugins.scale.mode == "ui" then
|
||||
SCALE = scale
|
||||
|
||||
|
@ -50,10 +44,14 @@ local function set_scale(scale)
|
|||
style.tab_width = style.tab_width * s
|
||||
|
||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||
renderer.font.set_size(style[name], s * style[name]:get_size())
|
||||
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
|
||||
end
|
||||
else
|
||||
renderer.font.set_size(style.code_font, s * style.code_font:get_size())
|
||||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
-- restore scroll positions
|
||||
|
@ -81,18 +79,15 @@ function RootView:on_mouse_wheel(d, ...)
|
|||
end
|
||||
|
||||
local function res_scale()
|
||||
scale_level = 0
|
||||
set_scale(default_scale)
|
||||
end
|
||||
|
||||
local function inc_scale()
|
||||
scale_level = scale_level + 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
set_scale(current_scale + scale_steps)
|
||||
end
|
||||
|
||||
local function dec_scale()
|
||||
scale_level = scale_level - 1
|
||||
set_scale(math.max(default_scale + scale_level * scale_steps), MINIMUM_SCALE)
|
||||
local function dec_scale()
|
||||
set_scale(current_scale - scale_steps)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
else
|
||||
core.try(function()
|
||||
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
|
||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
end
|
||||
|
@ -453,6 +453,7 @@ command.add(function() return view.hovered_item ~= nil end, {
|
|||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,9 @@ renderer.color = {}
|
|||
---@class renderer.fontoptions
|
||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
||||
-- @field public bold boolean
|
||||
-- @field public italic boolean
|
||||
-- @field public underline boolean
|
||||
renderer.fontoptions = {}
|
||||
|
||||
---
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,198 +0,0 @@
|
|||
//----------------------------------------------------------------------------
|
||||
// Anti-Grain Geometry - Version 2.4
|
||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
||||
//
|
||||
// Permission to copy, use, modify, sell and distribute this software
|
||||
// is granted provided this copyright notice appears in all copies.
|
||||
// This software is provided "as is" without express or implied
|
||||
// warranty, and with no claim as to its suitability for any purpose.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
// Contact: mcseem@antigrain.com
|
||||
// mcseemagg@yahoo.com
|
||||
// http://www.antigrain.com
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// See implementation agg_font_freetype.cpp
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#ifndef AGG_FONT_FREETYPE_INCLUDED
|
||||
#define AGG_FONT_FREETYPE_INCLUDED
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
|
||||
#include "agg_scanline_storage_aa.h"
|
||||
#include "agg_scanline_storage_bin.h"
|
||||
#include "agg_scanline_u.h"
|
||||
#include "agg_scanline_bin.h"
|
||||
#include "agg_path_storage_integer.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
#include "agg_conv_curve.h"
|
||||
#include "agg_font_cache_manager.h"
|
||||
#include "agg_trans_affine.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
|
||||
|
||||
//-----------------------------------------------font_engine_freetype_base
|
||||
class font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
//--------------------------------------------------------------------
|
||||
typedef serialized_scanlines_adaptor_aa<int8u> gray8_adaptor_type;
|
||||
typedef serialized_scanlines_adaptor_bin mono_adaptor_type;
|
||||
typedef scanline_storage_aa8 scanlines_aa_type;
|
||||
typedef scanline_storage_bin scanlines_bin_type;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
~font_engine_freetype_base();
|
||||
font_engine_freetype_base(bool flag32, unsigned max_faces = 32);
|
||||
|
||||
// Set font parameters
|
||||
//--------------------------------------------------------------------
|
||||
void resolution(unsigned dpi);
|
||||
bool load_font(const char* font_name, unsigned face_index, glyph_rendering ren_type,
|
||||
const char* font_mem = 0, const long font_mem_size = 0);
|
||||
bool attach(const char* file_name);
|
||||
bool char_map(FT_Encoding map);
|
||||
bool height(double h);
|
||||
bool width(double w);
|
||||
void hinting(bool h);
|
||||
void flip_y(bool f);
|
||||
void transform(const trans_affine& affine);
|
||||
|
||||
// Set Gamma
|
||||
//--------------------------------------------------------------------
|
||||
template<class GammaF> void gamma(const GammaF& f)
|
||||
{
|
||||
m_rasterizer.gamma(f);
|
||||
}
|
||||
|
||||
// Accessors
|
||||
//--------------------------------------------------------------------
|
||||
int last_error() const { return m_last_error; }
|
||||
unsigned resolution() const { return m_resolution; }
|
||||
const char* name() const { return m_name; }
|
||||
unsigned num_faces() const;
|
||||
FT_Encoding char_map() const { return m_char_map; }
|
||||
double height() const { return double(m_height) / 64.0; }
|
||||
double width() const { return double(m_width) / 64.0; }
|
||||
double ascender() const;
|
||||
double descender() const;
|
||||
int face_height() const;
|
||||
int face_units_em()const;
|
||||
bool hinting() const { return m_hinting; }
|
||||
bool flip_y() const { return m_flip_y; }
|
||||
|
||||
|
||||
// Interface mandatory to implement for font_cache_manager
|
||||
//--------------------------------------------------------------------
|
||||
const char* font_signature() const { return m_signature; }
|
||||
int change_stamp() const { return m_change_stamp; }
|
||||
|
||||
bool prepare_glyph(unsigned glyph_code);
|
||||
unsigned glyph_index() const { return m_glyph_index; }
|
||||
unsigned data_size() const { return m_data_size; }
|
||||
glyph_data_type data_type() const { return m_data_type; }
|
||||
const rect_i& bounds() const { return m_bounds; }
|
||||
double advance_x() const { return m_advance_x; }
|
||||
double advance_y() const { return m_advance_y; }
|
||||
void write_glyph_to(int8u* data) const;
|
||||
bool add_kerning(unsigned first, unsigned second,
|
||||
double* x, double* y);
|
||||
|
||||
private:
|
||||
font_engine_freetype_base(const font_engine_freetype_base&);
|
||||
const font_engine_freetype_base& operator = (const font_engine_freetype_base&);
|
||||
|
||||
void update_char_size();
|
||||
void update_signature();
|
||||
int find_face(const char* face_name) const;
|
||||
|
||||
bool m_flag32;
|
||||
int m_change_stamp;
|
||||
int m_last_error;
|
||||
char* m_name;
|
||||
unsigned m_name_len;
|
||||
unsigned m_face_index;
|
||||
FT_Encoding m_char_map;
|
||||
char* m_signature;
|
||||
unsigned m_height;
|
||||
unsigned m_width;
|
||||
bool m_hinting;
|
||||
bool m_flip_y;
|
||||
bool m_library_initialized;
|
||||
FT_Library m_library; // handle to library
|
||||
FT_Face* m_faces; // A pool of font faces
|
||||
char** m_face_names;
|
||||
unsigned m_num_faces;
|
||||
unsigned m_max_faces;
|
||||
FT_Face m_cur_face; // handle to the current face object
|
||||
int m_resolution;
|
||||
glyph_rendering m_glyph_rendering;
|
||||
unsigned m_glyph_index;
|
||||
unsigned m_data_size;
|
||||
glyph_data_type m_data_type;
|
||||
rect_i m_bounds;
|
||||
double m_advance_x;
|
||||
double m_advance_y;
|
||||
trans_affine m_affine;
|
||||
|
||||
path_storage_integer<int16, 6> m_path16;
|
||||
path_storage_integer<int32, 6> m_path32;
|
||||
conv_curve<path_storage_integer<int16, 6> > m_curves16;
|
||||
conv_curve<path_storage_integer<int32, 6> > m_curves32;
|
||||
scanline_u8 m_scanline_aa;
|
||||
scanline_bin m_scanline_bin;
|
||||
scanlines_aa_type m_scanlines_aa;
|
||||
scanlines_bin_type m_scanlines_bin;
|
||||
rasterizer_scanline_aa<> m_rasterizer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------font_engine_freetype_int16
|
||||
// This class uses values of type int16 (10.6 format) for the vector cache.
|
||||
// The vector cache is compact, but when rendering glyphs of height
|
||||
// more that 200 there integer overflow can occur.
|
||||
//
|
||||
class font_engine_freetype_int16 : public font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
typedef serialized_integer_path_adaptor<int16, 6> path_adaptor_type;
|
||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
||||
|
||||
font_engine_freetype_int16(unsigned max_faces = 32) :
|
||||
font_engine_freetype_base(false, max_faces) {}
|
||||
};
|
||||
|
||||
//------------------------------------------------font_engine_freetype_int32
|
||||
// This class uses values of type int32 (26.6 format) for the vector cache.
|
||||
// The vector cache is twice larger than in font_engine_freetype_int16,
|
||||
// but it allows you to render glyphs of very large sizes.
|
||||
//
|
||||
class font_engine_freetype_int32 : public font_engine_freetype_base
|
||||
{
|
||||
public:
|
||||
typedef serialized_integer_path_adaptor<int32, 6> path_adaptor_type;
|
||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
||||
|
||||
font_engine_freetype_int32(unsigned max_faces = 32) :
|
||||
font_engine_freetype_base(true, max_faces) {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||
// Adapted by Francesco Abbate for GSL Shell
|
||||
// Original code's copyright below.
|
||||
//----------------------------------------------------------------------------
|
||||
// Anti-Grain Geometry - Version 2.4
|
||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
||||
//
|
||||
// Permission to copy, use, modify, sell and distribute this software
|
||||
// is granted provided this copyright notice appears in all copies.
|
||||
// This software is provided "as is" without express or implied
|
||||
// warranty, and with no claim as to its suitability for any purpose.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
// Contact: mcseem@antigrain.com
|
||||
// mcseemagg@yahoo.com
|
||||
// http://www.antigrain.com
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#ifndef AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
||||
#define AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "agg_basics.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
|
||||
//=====================================================lcd_distribution_lut
|
||||
class lcd_distribution_lut
|
||||
{
|
||||
public:
|
||||
lcd_distribution_lut(double prim, double second, double tert)
|
||||
{
|
||||
double norm = 1.0 / (prim + second*2 + tert*2);
|
||||
prim *= norm;
|
||||
second *= norm;
|
||||
tert *= norm;
|
||||
for(unsigned i = 0; i < 256; i++)
|
||||
{
|
||||
unsigned b = (i << 8);
|
||||
unsigned s = round(second * b);
|
||||
unsigned t = round(tert * b);
|
||||
unsigned p = b - (2*s + 2*t);
|
||||
|
||||
m_data[3*i + 1] = s; /* secondary */
|
||||
m_data[3*i + 2] = t; /* tertiary */
|
||||
m_data[3*i ] = p; /* primary */
|
||||
}
|
||||
}
|
||||
|
||||
unsigned convolution(const int8u* covers, int i0, int i_min, int i_max) const
|
||||
{
|
||||
unsigned sum = 0;
|
||||
int k_min = (i0 >= i_min + 2 ? -2 : i_min - i0);
|
||||
int k_max = (i0 <= i_max - 2 ? 2 : i_max - i0);
|
||||
for (int k = k_min; k <= k_max; k++)
|
||||
{
|
||||
/* select the primary, secondary or tertiary channel */
|
||||
int channel = std::abs(k) % 3;
|
||||
int8u c = covers[i0 + k];
|
||||
sum += m_data[3*c + channel];
|
||||
}
|
||||
|
||||
return (sum + 128) >> 8;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned short m_data[256*3];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,93 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include "agg_basics.h"
|
||||
#include "agg_rendering_buffer.h"
|
||||
|
||||
namespace agg
|
||||
{
|
||||
// This is a special purpose color type that only has the alpha channel.
|
||||
// It can be thought as a gray color but with the intensity always on.
|
||||
// It is actually used to store coverage information.
|
||||
struct alpha8
|
||||
{
|
||||
typedef int8u value_type;
|
||||
typedef int32u calc_type;
|
||||
typedef int32 long_type;
|
||||
enum base_scale_e
|
||||
{
|
||||
base_shift = 8,
|
||||
base_scale = 1 << base_shift,
|
||||
base_mask = base_scale - 1
|
||||
};
|
||||
|
||||
value_type a;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
alpha8(unsigned a_=base_mask) :
|
||||
a(int8u(a_)) {}
|
||||
};
|
||||
|
||||
// Pixer format to store coverage information.
|
||||
class pixfmt_alpha8
|
||||
{
|
||||
public:
|
||||
typedef alpha8 color_type;
|
||||
typedef int8u value_type;
|
||||
typedef int32u calc_type;
|
||||
typedef agg::rendering_buffer::row_data row_data;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
pixfmt_alpha8(rendering_buffer& rb): m_rbuf(&rb)
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
unsigned width() const {
|
||||
return m_rbuf->width();
|
||||
}
|
||||
unsigned height() const {
|
||||
return m_rbuf->height();
|
||||
}
|
||||
|
||||
// This method should never be called when using the scanline_u8.
|
||||
// The use of scanline_p8 should be avoided because if does not works
|
||||
// properly for rendering fonts because single hspan are split in many
|
||||
// hline/hspan elements and pixel whitening happens.
|
||||
void blend_hline(int x, int y, unsigned len,
|
||||
const color_type& c, int8u cover)
|
||||
{ }
|
||||
|
||||
void copy_hline(int x, int y, unsigned len, const color_type& c)
|
||||
{
|
||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
||||
do
|
||||
{
|
||||
*p = c.a;
|
||||
p++;
|
||||
}
|
||||
while(--len);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
void blend_solid_hspan(int x, int y,
|
||||
unsigned len,
|
||||
const color_type& c,
|
||||
const int8u* covers)
|
||||
{
|
||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
||||
do
|
||||
{
|
||||
calc_type alpha = (calc_type(c.a) * (calc_type(*covers) + 1)) >> 8;
|
||||
*p = alpha;
|
||||
p++;
|
||||
++covers;
|
||||
}
|
||||
while(--len);
|
||||
}
|
||||
|
||||
private:
|
||||
rendering_buffer* m_rbuf;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,445 +0,0 @@
|
|||
#include "font_renderer.h"
|
||||
|
||||
#include "agg_lcd_distribution_lut.h"
|
||||
#include "agg_pixfmt_rgb.h"
|
||||
#include "agg_pixfmt_rgba.h"
|
||||
|
||||
#include "font_renderer_alpha.h"
|
||||
|
||||
// Important: when a subpixel scale is used the width below will be the width in logical pixel.
|
||||
// As each logical pixel contains 3 subpixels it means that the 'pixels' pointer
|
||||
// will hold enough space for '3 * width' uint8_t values.
|
||||
struct FR_Bitmap {
|
||||
agg::int8u *pixels;
|
||||
int width, height;
|
||||
};
|
||||
|
||||
class FR_Renderer {
|
||||
public:
|
||||
// Conventional LUT values: (1./3., 2./9., 1./9.)
|
||||
// The values below are fine tuned as in the Elementary Plot library.
|
||||
|
||||
FR_Renderer(bool hinting, bool kerning, bool subpixel, bool prescale_x) :
|
||||
m_renderer(hinting, kerning, subpixel, prescale_x),
|
||||
m_lcd_lut(0.448, 0.184, 0.092),
|
||||
m_subpixel(subpixel)
|
||||
{ }
|
||||
|
||||
font_renderer_alpha& renderer_alpha() { return m_renderer; }
|
||||
agg::lcd_distribution_lut& lcd_distribution_lut() { return m_lcd_lut; }
|
||||
int subpixel_scale() const { return (m_subpixel ? 3 : 1); }
|
||||
|
||||
private:
|
||||
font_renderer_alpha m_renderer;
|
||||
agg::lcd_distribution_lut m_lcd_lut;
|
||||
int m_subpixel;
|
||||
};
|
||||
|
||||
FR_Renderer *FR_Renderer_New(unsigned int flags) {
|
||||
bool hinting = ((flags & FR_HINTING) != 0);
|
||||
bool kerning = ((flags & FR_KERNING) != 0);
|
||||
bool subpixel = ((flags & FR_SUBPIXEL) != 0);
|
||||
bool prescale_x = ((flags & FR_PRESCALE_X) != 0);
|
||||
return new FR_Renderer(hinting, kerning, subpixel, prescale_x);
|
||||
}
|
||||
|
||||
FR_Bitmap* FR_Bitmap_New(FR_Renderer *font_renderer, int width, int height) {
|
||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
||||
FR_Bitmap *image = (FR_Bitmap *) malloc(sizeof(FR_Bitmap) + width * height * subpixel_scale);
|
||||
if (!image) { return NULL; }
|
||||
image->pixels = (agg::int8u *) (image + 1);
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
return image;
|
||||
}
|
||||
|
||||
void FR_Bitmap_Free(FR_Bitmap *image) {
|
||||
free(image);
|
||||
}
|
||||
|
||||
void FR_Renderer_Free(FR_Renderer *font_renderer) {
|
||||
delete font_renderer;
|
||||
}
|
||||
|
||||
int FR_Subpixel_Scale(FR_Renderer *font_renderer) {
|
||||
return font_renderer->subpixel_scale();
|
||||
}
|
||||
|
||||
int FR_Load_Font(FR_Renderer *font_renderer, const char *filename) {
|
||||
bool success = font_renderer->renderer_alpha().load_font(filename);
|
||||
return (success ? 0 : 1);
|
||||
}
|
||||
|
||||
int FR_Get_Font_Height(FR_Renderer *font_renderer, float size) {
|
||||
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
|
||||
double ascender, descender;
|
||||
renderer_alpha.get_font_vmetrics(ascender, descender);
|
||||
int face_height = renderer_alpha.get_face_height();
|
||||
float scale = renderer_alpha.scale_for_em_to_pixels(size);
|
||||
return int((ascender - descender) * face_height * scale + 0.5);
|
||||
}
|
||||
|
||||
static void glyph_trim_rect(agg::rendering_buffer& ren_buf, FR_Bitmap_Glyph_Metrics& gli, int subpixel_scale) {
|
||||
const int height = ren_buf.height();
|
||||
int x0 = gli.x0 * subpixel_scale, x1 = gli.x1 * subpixel_scale;
|
||||
int y0 = gli.y0, y1 = gli.y1;
|
||||
for (int y = gli.y0; y < gli.y1; y++) {
|
||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
||||
unsigned int row_bitsum = 0;
|
||||
for (int x = x0; x < x1; x++) {
|
||||
row_bitsum |= row[x];
|
||||
}
|
||||
if (row_bitsum == 0) {
|
||||
y0++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int y = gli.y1 - 1; y >= y0; y--) {
|
||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
||||
unsigned int row_bitsum = 0;
|
||||
for (int x = x0; x < x1; x++) {
|
||||
row_bitsum |= row[x];
|
||||
}
|
||||
if (row_bitsum == 0) {
|
||||
y1--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int x = gli.x0 * subpixel_scale; x < gli.x1 * subpixel_scale; x += subpixel_scale) {
|
||||
unsigned int xaccu = 0;
|
||||
for (int y = y0; y < y1; y++) {
|
||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
||||
for (int i = 0; i < subpixel_scale; i++) {
|
||||
xaccu |= row[x + i];
|
||||
}
|
||||
}
|
||||
if (xaccu == 0) {
|
||||
x0 += subpixel_scale;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int x = (gli.x1 - 1) * subpixel_scale; x >= x0; x -= subpixel_scale) {
|
||||
unsigned int xaccu = 0;
|
||||
for (int y = y0; y < y1; y++) {
|
||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
||||
for (int i = 0; i < subpixel_scale; i++) {
|
||||
xaccu |= row[x + i];
|
||||
}
|
||||
}
|
||||
if (xaccu == 0) {
|
||||
x1 -= subpixel_scale;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
gli.xoff += (x0 / subpixel_scale) - gli.x0;
|
||||
gli.yoff += (y0 - gli.y0);
|
||||
gli.x0 = x0 / subpixel_scale;
|
||||
gli.y0 = y0;
|
||||
gli.x1 = x1 / subpixel_scale;
|
||||
gli.y1 = y1;
|
||||
}
|
||||
|
||||
static void glyph_lut_convolution(agg::rendering_buffer ren_buf, agg::lcd_distribution_lut& lcd_lut, agg::int8u *covers_buf, FR_Bitmap_Glyph_Metrics& gli) {
|
||||
const int subpixel = 3;
|
||||
const int x0 = gli.x0, y0 = gli.y0, x1 = gli.x1, y1 = gli.y1;
|
||||
const int len = (x1 - x0) * subpixel;
|
||||
const int height = ren_buf.height();
|
||||
for (int y = y0; y < y1; y++) {
|
||||
agg::int8u *covers = ren_buf.row_ptr(height - 1 - y) + x0 * subpixel;
|
||||
memcpy(covers_buf, covers, len);
|
||||
for (int x = x0 - 1; x < x1 + 1; x++) {
|
||||
for (int i = 0; i < subpixel; i++) {
|
||||
const int cx = (x - x0) * subpixel + i;
|
||||
covers[cx] = lcd_lut.convolution(covers_buf, cx, 0, len - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
gli.x0 -= 1;
|
||||
gli.x1 += 1;
|
||||
gli.xoff -= 1;
|
||||
}
|
||||
|
||||
// The two functions below are needed because in C and C++ integer division
|
||||
// is rounded toward zero.
|
||||
|
||||
// euclidean division rounded toward positive infinite
|
||||
static int div_pos(int n, int p) {
|
||||
return n >= 0 ? (n + p - 1) / p : (n / p);
|
||||
}
|
||||
|
||||
// euclidean division rounded toward negative infinite
|
||||
static int div_neg(int n, int p) {
|
||||
return n >= 0 ? (n / p) : ((n - p + 1) / p);
|
||||
}
|
||||
|
||||
FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height,
|
||||
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyphs)
|
||||
{
|
||||
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
|
||||
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
|
||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
||||
|
||||
double ascender, descender;
|
||||
renderer_alpha.get_font_vmetrics(ascender, descender);
|
||||
const int ascender_px = int(ascender * font_height);
|
||||
const int pad_y = 1;
|
||||
|
||||
// When using subpixel font rendering it is needed to leave a padding pixel on the left and on the right.
|
||||
// Since each pixel is composed by n subpixel we set below x_start to subpixel_scale instead than zero.
|
||||
// In addition we need one more pixel on the left because of subpixel positioning so
|
||||
// it adds up to 2 * subpixel_scale.
|
||||
// Note about the coordinates: they are AGG-like so x is positive toward the right and
|
||||
// y is positive in the upper direction.
|
||||
const int x_start = 2 * subpixel_scale;
|
||||
const agg::alpha8 text_color(0xff);
|
||||
#ifdef FONT_RENDERER_HEIGHT_HACK
|
||||
const int font_height_reduced = (font_height * 86) / 100;
|
||||
#else
|
||||
const int font_height_reduced = font_height;
|
||||
#endif
|
||||
renderer_alpha.set_font_height(font_height_reduced);
|
||||
|
||||
int *index = (int *) malloc(num_chars * sizeof(int));
|
||||
agg::rect_i *bounds = (agg::rect_i *) malloc(num_chars * sizeof(agg::rect_i));
|
||||
if (!index || !bounds) {
|
||||
free(index);
|
||||
free(bounds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int x_size_sum = 0, glyph_count = 0;
|
||||
for (int i = 0; i < num_chars; i++) {
|
||||
int codepoint = first_char + i;
|
||||
index[i] = i;
|
||||
if (renderer_alpha.codepoint_bounds(codepoint, subpixel_scale, bounds[i])) {
|
||||
// Invalid glyph
|
||||
bounds[i].x1 = 0;
|
||||
bounds[i].y1 = 0;
|
||||
bounds[i].x2 = -1;
|
||||
bounds[i].y2 = -1;
|
||||
} else {
|
||||
if (bounds[i].x2 > bounds[i].x1) {
|
||||
x_size_sum += bounds[i].x2 - bounds[i].x1;
|
||||
glyph_count++;
|
||||
}
|
||||
bounds[i].x1 = subpixel_scale * div_neg(bounds[i].x1, subpixel_scale);
|
||||
bounds[i].x2 = subpixel_scale * div_pos(bounds[i].x2, subpixel_scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple insertion sort algorithm: https://en.wikipedia.org/wiki/Insertion_sort
|
||||
int i = 1;
|
||||
while (i < num_chars) {
|
||||
int j = i;
|
||||
while (j > 0 && bounds[index[j-1]].y2 - bounds[index[j-1]].y1 > bounds[index[j]].y2 - bounds[index[j]].y1) {
|
||||
int tmp = index[j];
|
||||
index[j] = index[j-1];
|
||||
index[j-1] = tmp;
|
||||
j = j - 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
const int glyph_avg_width = glyph_count > 0 ? x_size_sum / (glyph_count * subpixel_scale) : font_height;
|
||||
const int pixels_width = glyph_avg_width > 0 ? glyph_avg_width * 28 : 28;
|
||||
|
||||
// dry run simulating pixel position to estimate required image's height
|
||||
int x = x_start, y = 0, y_bottom = y;
|
||||
for (int i = 0; i < num_chars; i++) {
|
||||
const agg::rect_i& gbounds = bounds[index[i]];
|
||||
if (gbounds.x2 < gbounds.x1) continue;
|
||||
// 1. It is very important to ensure that the x's increment below (1) and in
|
||||
// (2), (3) and (4) are perfectly the same.
|
||||
// Note that x_step below is always an integer multiple of subpixel_scale.
|
||||
const int x_step = gbounds.x2 + 3 * subpixel_scale;
|
||||
if (x + x_step >= pixels_width * subpixel_scale) {
|
||||
x = x_start;
|
||||
y = y_bottom;
|
||||
}
|
||||
// 5. Ensure that y's increment below is exactly the same to the one used in (6)
|
||||
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
|
||||
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
|
||||
// 2. Ensure x's increment is aligned with (1)
|
||||
x = x + x_step;
|
||||
}
|
||||
|
||||
agg::int8u *cover_swap_buffer = (agg::int8u *) malloc(sizeof(agg::int8u) * (pixels_width * subpixel_scale));
|
||||
if (!cover_swap_buffer) {
|
||||
free(index);
|
||||
free(bounds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const int pixels_height = -y_bottom + 1;
|
||||
const int pixel_size = 1;
|
||||
FR_Bitmap *image = FR_Bitmap_New(font_renderer, pixels_width, pixels_height);
|
||||
if (!image) {
|
||||
free(index);
|
||||
free(bounds);
|
||||
free(cover_swap_buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
agg::int8u *pixels = image->pixels;
|
||||
memset(pixels, 0x00, pixels_width * pixels_height * subpixel_scale * pixel_size);
|
||||
agg::rendering_buffer ren_buf(pixels, pixels_width * subpixel_scale, pixels_height, -pixels_width * subpixel_scale * pixel_size);
|
||||
|
||||
// The variable y_bottom will be used to go down to the next row by taking into
|
||||
// account the space occupied by each glyph of the current row along the y direction.
|
||||
x = x_start;
|
||||
// Set y to the image's height minus one to begin writing glyphs in the upper part of the image.
|
||||
y = pixels_height - 1;
|
||||
y_bottom = y;
|
||||
for (int i = 0; i < num_chars; i++) {
|
||||
// Important: the variable x in this loop should always be an integer multiple
|
||||
// of subpixel_scale.
|
||||
int codepoint = first_char + index[i];
|
||||
const agg::rect_i& gbounds = bounds[index[i]];
|
||||
if (gbounds.x2 < gbounds.x1) continue;
|
||||
|
||||
// 3. Ensure x's increment is aligned with (1)
|
||||
// Note that x_step below is always an integer multiple of subpixel_scale.
|
||||
// We need 3 * subpixel_scale because:
|
||||
// . +1 pixel on the left, because of RGB color filter
|
||||
// . +1 pixel on the right, because of RGB color filter
|
||||
// . +1 pixel on the right, because of subpixel positioning
|
||||
// and each pixel requires "subpixel_scale" sub-pixels.
|
||||
const int x_step = gbounds.x2 + 3 * subpixel_scale;
|
||||
if (x + x_step >= pixels_width * subpixel_scale) {
|
||||
// No more space along x, begin writing the row below.
|
||||
x = x_start;
|
||||
y = y_bottom;
|
||||
}
|
||||
|
||||
const int y_baseline = y - pad_y - gbounds.y2;
|
||||
// 6. Ensure the y's increment below is aligned with the increment used in (5)
|
||||
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
|
||||
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
|
||||
|
||||
double x_next = x, y_next = y_baseline;
|
||||
renderer_alpha.render_codepoint(ren_buf, text_color, x_next, y_next, codepoint, subpixel_scale);
|
||||
|
||||
// The y coordinate for the glyph below is positive in the bottom direction,
|
||||
// like is used by Lite's drawing system.
|
||||
FR_Bitmap_Glyph_Metrics& glyph_info = glyphs[index[i]];
|
||||
glyph_info.x0 = x / subpixel_scale;
|
||||
glyph_info.y0 = pixels_height - 1 - (y_baseline + gbounds.y2 + pad_y);
|
||||
glyph_info.x1 = div_pos(x_next + 0.5, subpixel_scale);
|
||||
glyph_info.y1 = pixels_height - 1 - (y_baseline + gbounds.y1 - pad_y);
|
||||
|
||||
glyph_info.xoff = 0;
|
||||
glyph_info.yoff = -pad_y - gbounds.y2 + ascender_px;
|
||||
// Note that below the xadvance is in pixels times the subpixel_scale.
|
||||
// This is meant for subpixel positioning.
|
||||
glyph_info.xadvance = roundf(x_next - x);
|
||||
|
||||
if (subpixel_scale != 1 && glyph_info.x1 > glyph_info.x0) {
|
||||
glyph_lut_convolution(ren_buf, lcd_lut, cover_swap_buffer, glyph_info);
|
||||
}
|
||||
glyph_trim_rect(ren_buf, glyph_info, subpixel_scale);
|
||||
|
||||
// When subpixel is activated we need one padding pixel on the left and on the right
|
||||
// and one more because of subpixel positioning.
|
||||
// 4. Ensure x's increment is aligned with (1)
|
||||
x = x + x_step;
|
||||
}
|
||||
|
||||
free(index);
|
||||
free(bounds);
|
||||
free(cover_swap_buffer);
|
||||
return image;
|
||||
}
|
||||
|
||||
template <typename Order>
|
||||
void blend_solid_hspan(agg::rendering_buffer& rbuf, int x, int y, unsigned len,
|
||||
const agg::rgba8& c, const agg::int8u* covers)
|
||||
{
|
||||
const int pixel_size = 4;
|
||||
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
|
||||
do
|
||||
{
|
||||
const unsigned alpha = *covers;
|
||||
const unsigned r = p[Order::R], g = p[Order::G], b = p[Order::B];
|
||||
p[Order::R] = (((unsigned(c.r) - r) * alpha) >> 8) + r;
|
||||
p[Order::G] = (((unsigned(c.g) - g) * alpha) >> 8) + g;
|
||||
p[Order::B] = (((unsigned(c.b) - b) * alpha) >> 8) + b;
|
||||
// Leave p[3], the alpha channel value unmodified.
|
||||
p += 4;
|
||||
++covers;
|
||||
}
|
||||
while(--len);
|
||||
}
|
||||
|
||||
template <typename Order>
|
||||
void blend_solid_hspan_subpixel(agg::rendering_buffer& rbuf, agg::lcd_distribution_lut& lcd_lut,
|
||||
const int x, const int y, unsigned len,
|
||||
const agg::rgba8& c,
|
||||
const agg::int8u* covers)
|
||||
{
|
||||
const int pixel_size = 4;
|
||||
const unsigned rgb[3] = { c.r, c.g, c.b };
|
||||
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
|
||||
|
||||
// Indexes to adress RGB colors in a BGRA32 format.
|
||||
const int pixel_index[3] = {Order::R, Order::G, Order::B};
|
||||
for (unsigned cx = 0; cx < len; cx += 3)
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const unsigned cover_value = covers[cx + i];
|
||||
const unsigned alpha = (cover_value + 1) * (c.a + 1);
|
||||
const unsigned src_col = *(p + pixel_index[i]);
|
||||
*(p + pixel_index[i]) = (((rgb[i] - src_col) * alpha) + (src_col << 16)) >> 16;
|
||||
}
|
||||
// Leave p[3], the alpha channel value unmodified.
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage with subpixel scale = 3.
|
||||
// FIXME: consider using something like RenColor* instead of uint8_t * for dst.
|
||||
void FR_Blend_Glyph(FR_Renderer *font_renderer, FR_Clip_Area *clip, int x_mult, int y, uint8_t *dst, int dst_width, const FR_Bitmap *glyphs_bitmap, const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color) {
|
||||
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
|
||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
||||
const int pixel_size = 4; // Pixel size for BGRA32 format.
|
||||
|
||||
int x = x_mult / subpixel_scale;
|
||||
|
||||
x += glyph->xoff;
|
||||
y += glyph->yoff;
|
||||
|
||||
int glyph_x = glyph->x0, glyph_y = glyph->y0;
|
||||
int glyph_x_subpixel = -(x_mult % subpixel_scale);
|
||||
int glyph_width = glyph->x1 - glyph->x0;
|
||||
int glyph_height = glyph->y1 - glyph->y0;
|
||||
|
||||
int n;
|
||||
if ((n = clip->left - x) > 0) { glyph_width -= n; glyph_x += n; x += n; }
|
||||
if ((n = clip->top - y) > 0) { glyph_height -= n; glyph_y += n; y += n; }
|
||||
if ((n = x + glyph_width - clip->right ) > 0) { glyph_width -= n; }
|
||||
if ((n = y + glyph_height - clip->bottom) > 0) { glyph_height -= n; }
|
||||
|
||||
if (glyph_width <= 0 || glyph_height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dst += (x + y * dst_width) * pixel_size;
|
||||
agg::rendering_buffer dst_ren_buf(dst, glyph_width, glyph_height, dst_width * pixel_size);
|
||||
|
||||
uint8_t *src = glyphs_bitmap->pixels + (glyph_x + glyph_y * glyphs_bitmap->width) * subpixel_scale + glyph_x_subpixel;
|
||||
int src_stride = glyphs_bitmap->width * subpixel_scale;
|
||||
|
||||
const agg::rgba8 color_a(color.r, color.g, color.b);
|
||||
for (int x = 0, y = 0; y < glyph_height; y++) {
|
||||
agg::int8u *covers = src + y * src_stride;
|
||||
if (subpixel_scale == 1) {
|
||||
blend_solid_hspan<agg::order_bgra>(dst_ren_buf, x, y, glyph_width, color_a, covers);
|
||||
} else {
|
||||
blend_solid_hspan_subpixel<agg::order_bgra>(dst_ren_buf, lcd_lut, x, y, glyph_width * subpixel_scale, color_a, covers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
#ifndef FONT_RENDERER_H
|
||||
#define FONT_RENDERER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
unsigned short x0, y0, x1, y1;
|
||||
float xoff, yoff, xadvance;
|
||||
} FR_Bitmap_Glyph_Metrics;
|
||||
|
||||
typedef struct FR_Bitmap FR_Bitmap;
|
||||
|
||||
#ifdef __cplusplus
|
||||
class FR_Renderer;
|
||||
#else
|
||||
struct FR_Renderer;
|
||||
typedef struct FR_Renderer FR_Renderer;
|
||||
#endif
|
||||
|
||||
enum {
|
||||
FR_HINTING = 1 << 0,
|
||||
FR_KERNING = 1 << 1,
|
||||
FR_SUBPIXEL = 1 << 2,
|
||||
FR_PRESCALE_X = 1 << 3,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t r, g, b;
|
||||
} FR_Color;
|
||||
|
||||
typedef struct {
|
||||
int left, top, right, bottom;
|
||||
} FR_Clip_Area;
|
||||
|
||||
FR_Renderer * FR_Renderer_New(unsigned int flags);
|
||||
void FR_Renderer_Free(FR_Renderer *);
|
||||
int FR_Load_Font(FR_Renderer *, const char *filename);
|
||||
FR_Bitmap* FR_Bitmap_New(FR_Renderer *, int width, int height);
|
||||
void FR_Bitmap_Free(FR_Bitmap *image);
|
||||
int FR_Get_Font_Height(FR_Renderer *, float size);
|
||||
FR_Bitmap * FR_Bake_Font_Bitmap(FR_Renderer *, int font_height,
|
||||
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyph_info);
|
||||
void FR_Blend_Glyph(FR_Renderer *font_renderer,
|
||||
FR_Clip_Area *clip, int x, int y,
|
||||
uint8_t *dst, int dst_width,
|
||||
const FR_Bitmap *glyphs_bitmap,
|
||||
const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color);
|
||||
int FR_Subpixel_Scale(FR_Renderer *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,164 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "agg_basics.h"
|
||||
#include "agg_conv_curve.h"
|
||||
#include "agg_conv_transform.h"
|
||||
#include "agg_gamma_lut.h"
|
||||
#include "agg_font_freetype.h"
|
||||
#include "agg_pixfmt_alpha8.h"
|
||||
#include "agg_rasterizer_scanline_aa.h"
|
||||
#include "agg_renderer_primitives.h"
|
||||
#include "agg_renderer_scanline.h"
|
||||
#include "agg_rendering_buffer.h"
|
||||
#include "agg_scanline_u.h"
|
||||
|
||||
class font_renderer_alpha
|
||||
{
|
||||
typedef agg::pixfmt_alpha8 pixfmt_type;
|
||||
typedef agg::renderer_base<pixfmt_type> base_ren_type;
|
||||
typedef agg::renderer_scanline_aa_solid<base_ren_type> renderer_solid;
|
||||
typedef agg::font_engine_freetype_int32 font_engine_type;
|
||||
typedef agg::font_cache_manager<font_engine_type> font_manager_type;
|
||||
|
||||
font_engine_type m_feng;
|
||||
font_manager_type m_fman;
|
||||
|
||||
// Font rendering options.
|
||||
bool m_hinting;
|
||||
bool m_kerning;
|
||||
bool m_subpixel;
|
||||
bool m_prescale_x;
|
||||
|
||||
bool m_font_loaded;
|
||||
|
||||
// Pipeline to process the vectors glyph paths (curves + contour)
|
||||
agg::trans_affine m_mtx;
|
||||
agg::conv_curve<font_manager_type::path_adaptor_type> m_curves;
|
||||
agg::conv_transform<agg::conv_curve<font_manager_type::path_adaptor_type> > m_trans;
|
||||
public:
|
||||
typedef agg::pixfmt_alpha8::color_type color_type;
|
||||
|
||||
font_renderer_alpha(bool hinting, bool kerning, bool subpixel, bool prescale_x):
|
||||
m_feng(),
|
||||
m_fman(m_feng),
|
||||
m_hinting(hinting),
|
||||
m_kerning(kerning),
|
||||
m_subpixel(subpixel),
|
||||
m_prescale_x(prescale_x),
|
||||
m_font_loaded(false),
|
||||
m_curves(m_fman.path_adaptor()),
|
||||
m_trans(m_curves, m_mtx)
|
||||
{ }
|
||||
|
||||
int get_face_height() const {
|
||||
return m_feng.face_height();
|
||||
}
|
||||
|
||||
void get_font_vmetrics(double& ascender, double& descender) {
|
||||
double current_height = m_feng.height();
|
||||
m_feng.height(1.0);
|
||||
ascender = m_feng.ascender();
|
||||
descender = m_feng.descender();
|
||||
m_feng.height(current_height);
|
||||
}
|
||||
|
||||
float scale_for_em_to_pixels(float size) {
|
||||
int units_per_em = m_feng.face_units_em();
|
||||
if (units_per_em > 0) {
|
||||
return size / units_per_em;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool load_font(const char *font_filename) {
|
||||
if(m_feng.load_font(font_filename, 0, agg::glyph_ren_outline)) {
|
||||
m_font_loaded = true;
|
||||
m_feng.hinting(m_hinting);
|
||||
}
|
||||
return m_font_loaded;
|
||||
}
|
||||
|
||||
void set_font_height(double height) {
|
||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
||||
m_feng.height(height);
|
||||
m_feng.width(height * scale_x);
|
||||
}
|
||||
|
||||
template<class Rasterizer, class Scanline, class RenSolid>
|
||||
void draw_codepoint(Rasterizer& ras, Scanline& sl,
|
||||
RenSolid& ren_solid, const color_type color,
|
||||
int codepoint, double& x, double& y, const int subpixel_scale)
|
||||
{
|
||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
||||
// Coefficient to scale back the glyph to the final scale.
|
||||
const double cx_inv_scale = subpixel_scale / scale_x;
|
||||
|
||||
// Represent the delta in x scaled by scale_x.
|
||||
double x_delta = 0;
|
||||
double start_x = x;
|
||||
|
||||
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
|
||||
if(glyph)
|
||||
{
|
||||
if(m_kerning)
|
||||
{
|
||||
m_fman.add_kerning(&x_delta, &y);
|
||||
}
|
||||
|
||||
m_fman.init_embedded_adaptors(glyph, 0, 0);
|
||||
if(glyph->data_type == agg::glyph_data_outline)
|
||||
{
|
||||
double ty = m_hinting ? floor(y + 0.5) : y;
|
||||
ras.reset();
|
||||
m_mtx.reset();
|
||||
m_mtx *= agg::trans_affine_scaling(cx_inv_scale, 1);
|
||||
m_mtx *= agg::trans_affine_translation(start_x + cx_inv_scale * x_delta, ty);
|
||||
ras.add_path(m_trans);
|
||||
ren_solid.color(color);
|
||||
agg::render_scanlines(ras, sl, ren_solid);
|
||||
}
|
||||
|
||||
y += glyph->advance_y;
|
||||
x += cx_inv_scale * (x_delta + glyph->advance_x);
|
||||
}
|
||||
}
|
||||
|
||||
void clear(agg::rendering_buffer& ren_buf, const color_type color) {
|
||||
pixfmt_type pf(ren_buf);
|
||||
base_ren_type ren_base(pf);
|
||||
ren_base.clear(color);
|
||||
}
|
||||
|
||||
void render_codepoint(agg::rendering_buffer& ren_buf,
|
||||
const color_type text_color,
|
||||
double& x, double& y,
|
||||
int codepoint, const int subpixel_scale)
|
||||
{
|
||||
if (!m_font_loaded) {
|
||||
return;
|
||||
}
|
||||
agg::scanline_u8 sl;
|
||||
agg::rasterizer_scanline_aa<> ras;
|
||||
ras.clip_box(0, 0, ren_buf.width(), ren_buf.height());
|
||||
|
||||
agg::pixfmt_alpha8 pf(ren_buf);
|
||||
base_ren_type ren_base(pf);
|
||||
renderer_solid ren_solid(ren_base);
|
||||
draw_codepoint(ras, sl, ren_solid, text_color, codepoint, x, y, subpixel_scale);
|
||||
}
|
||||
|
||||
int codepoint_bounds(int codepoint, const int subpixel_scale, agg::rect_i& bounds)
|
||||
{
|
||||
if (!m_font_loaded) return 1;
|
||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
||||
const double cx_inv_scale = subpixel_scale / scale_x;
|
||||
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
|
||||
if (glyph) {
|
||||
bounds = glyph->bounds;
|
||||
bounds.x1 *= cx_inv_scale;
|
||||
bounds.x2 *= cx_inv_scale;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
freetype_dep = dependency('freetype2')
|
||||
|
||||
libagg_dep = dependency('libagg', fallback: ['libagg', 'libagg_dep'])
|
||||
|
||||
font_renderer_sources = [
|
||||
'agg_font_freetype.cpp',
|
||||
'font_renderer.cpp',
|
||||
]
|
||||
|
||||
font_renderer_cdefs = ['-DFONT_RENDERER_HEIGHT_HACK']
|
||||
|
||||
font_renderer_include = include_directories('.')
|
||||
|
||||
libfontrenderer = static_library('fontrenderer',
|
||||
font_renderer_sources,
|
||||
dependencies: [libagg_dep, freetype_dep],
|
||||
cpp_args: font_renderer_cdefs,
|
||||
)
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
```c
|
||||
stbtt_InitFont
|
||||
|
||||
stbtt_ScaleForMappingEmToPixels x 3
|
||||
stbtt_ScaleForPixelHeight
|
||||
stbtt_BakeFontBitmap
|
||||
stbtt_GetFontVMetrics x 2
|
||||
|
||||
typedef struct {
|
||||
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
|
||||
float xoff, yoff, xadvance;
|
||||
} stbtt_bakedchar;
|
||||
|
||||
struct RenImage {
|
||||
RenColor *pixels;
|
||||
int width, height;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
RenImage *image;
|
||||
stbtt_bakedchar glyphs[256];
|
||||
} GlyphSet;
|
||||
|
||||
struct RenFont {
|
||||
void *data;
|
||||
stbtt_fontinfo stbfont;
|
||||
GlyphSet *sets[MAX_GLYPHSET];
|
||||
float size;
|
||||
int height;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet).
|
||||
Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB.
|
||||
With a single call many glyphs corresponding to a range of codepoints, all in a
|
||||
single image.
|
||||
|
||||
## STB truetype font metrics
|
||||
|
||||
stbtt_ScaleForPixelHeight takes a float 'height' and returns height / (ascent - descent).
|
||||
|
||||
stbtt_ScaleForMappingEmToPixels take a float 'pixels' and returns pixels / unitsPerEm.
|
||||
|
||||
### Computing RenFont
|
||||
|
||||
When loading a font, in renderer.c, the font->height is determined as:
|
||||
|
||||
```c
|
||||
int ascent, descent, linegap;
|
||||
stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap);
|
||||
float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size);
|
||||
font->height = (ascent - descent + linegap) * scale + 0.5;
|
||||
```
|
||||
|
||||
so, mathematically
|
||||
|
||||
```c
|
||||
font->height = (ascent - descent + linegap) * font->size / unitsPerEm + 0.5;
|
||||
```
|
||||
|
||||
**TO DO**: find out for what font->height is actually used.
|
||||
|
||||
### Call to BakeFontBitmap
|
||||
|
||||
In the same file, renderer.c, to create the glyphset image it computes:
|
||||
|
||||
```c
|
||||
// Using stbtt functions
|
||||
float s = ScaleForMappingEmToPixels(1) / ScaleForPixelHeight(1);
|
||||
```
|
||||
|
||||
so 's' is actually equal to (ascent - descent) / unitsPerEm.
|
||||
|
||||
Then BakeFontBitmap is called and `font->size * s` is used for the pixel_height argument.
|
||||
So BakeFontBitmap gets
|
||||
|
||||
```c
|
||||
pixel_height = (ascent - descent) * font->size / unitsPerEm;
|
||||
```
|
||||
|
||||
In turns BakeFontBitmap passes pixel_height to ScaleForPixelHeight() and uses the
|
||||
resulting 'scale' to scale the glyphs by passing it to MakeGlyphBitmap().
|
||||
|
||||
This is equal almost equal to font->height except the 0.5, the missing linegap calculation
|
||||
and the fact that this latter is an integer instead of a float.
|
||||
|
||||
## AGG Font Engine
|
||||
|
||||
Calls
|
||||
|
||||
`FT_Init_FreeType` (initialize the library)
|
||||
|
||||
In `load_font()` method:
|
||||
`FT_New_Face` or `FT_New_Memory_Face` (use `FT_Done_Face` when done)
|
||||
|
||||
`FT_Attach_File`
|
||||
`FT_Select_Charmap`
|
||||
|
||||
In `update_char_size()` method:
|
||||
`FT_Set_Char_Size` or `FT_Set_Pixel_Sizes`
|
||||
|
||||
In `prepare_glyph()` method:
|
||||
`FT_Get_Char_Index`
|
||||
`FT_Load_Glyph`
|
||||
`FT_Render_Glyph` (if glyph_render_native_mono or native_gray8)
|
||||
|
||||
in `add_kerning()` method
|
||||
`FT_Get_Kerning`
|
||||
|
||||
`FT_Done_FreeType` (end with library)
|
||||
|
||||
## Freetype2's metrics related function and structs
|
||||
|
||||
`FT_New_Face` return a `FT_Face` (a pointer) with font face information.
|
||||
|
||||
## AGG font engine's font size
|
||||
|
||||
The variable `m_height` is the size of the font muliplied by 64.
|
||||
It will be used to set font size with:
|
||||
|
||||
- `FT_Set_Char_Size` if m_resolution is set (> 0)
|
||||
- `FT_Set_Pixel_Sizes`, divided by 64, if m_resolution is not set (= 0)
|
||||
|
||||
The method height() returns m_height / 64;
|
14
meson.build
14
meson.build
|
@ -1,9 +1,9 @@
|
|||
project('lite-xl',
|
||||
['c', 'cpp'],
|
||||
version : '2.0.1',
|
||||
['c'],
|
||||
version : '2.0.2',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.54',
|
||||
default_options : ['c_std=gnu11', 'cpp_std=c++03']
|
||||
default_options : ['c_std=gnu11']
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
|
@ -33,7 +33,7 @@ endif
|
|||
#===============================================================================
|
||||
lite_link_args = []
|
||||
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
||||
lite_link_args += ['-static-libgcc', '-static-libstdc++']
|
||||
lite_link_args += ['-static-libgcc']
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
|
@ -49,6 +49,7 @@ if not get_option('source-only')
|
|||
default_options: ['shared=false', 'use_readline=false', 'app=false']
|
||||
)
|
||||
pcre2_dep = dependency('libpcre2-8')
|
||||
freetype_dep = dependency('freetype2')
|
||||
sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||
reproc_dep = dependency('reproc', fallback: ['reproc', 'reproc_dep'],
|
||||
default_options: [
|
||||
|
@ -57,12 +58,12 @@ if not get_option('source-only')
|
|||
]
|
||||
)
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl]
|
||||
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl, freetype_dep]
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
# Note that we need to explicitly add the windows socket DLL because
|
||||
# the pkg-config file from reproc does not include it.
|
||||
lite_deps += meson.get_compiler('cpp').find_library('ws2_32', required: true)
|
||||
lite_deps += meson.get_compiler('c').find_library('ws2_32', required: true)
|
||||
endif
|
||||
endif
|
||||
#===============================================================================
|
||||
|
@ -121,7 +122,6 @@ configure_file(
|
|||
# Targets
|
||||
#===============================================================================
|
||||
if not get_option('source-only')
|
||||
subdir('lib/font_renderer')
|
||||
subdir('src')
|
||||
subdir('scripts')
|
||||
endif
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,360 @@
|
|||
#ifndef LITE_XL_PLUGIN_API
|
||||
#define LITE_XL_PLUGIN_API
|
||||
/*
|
||||
The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long
|
||||
as it has an entrypoint that looks like the following, where xxxxx is the plugin name:
|
||||
|
||||
#include "lite_xl_plugin_api.h"
|
||||
|
||||
int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {
|
||||
lite_xl_plugin_init(XL);
|
||||
...
|
||||
return 1;
|
||||
}
|
||||
|
||||
In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!
|
||||
Due to the way the API is structured, you *should not* link or include lua libraries.
|
||||
|
||||
This file was automatically generated by the below code. Do NOT MODIFY DIRECTLY.
|
||||
|
||||
|
||||
#!/bin/sh
|
||||
echo "#ifndef LITE_XL_PLUGIN_API"
|
||||
echo "#define LITE_XL_PLUGIN_API"
|
||||
echo "/* "
|
||||
echo "The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long"
|
||||
echo "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:"
|
||||
echo
|
||||
echo '#include "lite_xl_plugin_api.h"'
|
||||
echo
|
||||
echo "int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {"
|
||||
echo " lite_xl_plugin_init(XL);"
|
||||
echo " ..."
|
||||
echo " return 1;"
|
||||
echo "}"
|
||||
echo
|
||||
echo "In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!"
|
||||
echo "Due to the way the API is structured, you *should not* link or include lua libraries."
|
||||
echo
|
||||
echo "This file was automatically generated by the below code. Do NOT MODIFY DIRECTLY."
|
||||
echo
|
||||
echo
|
||||
cat $0
|
||||
echo "*""/"
|
||||
echo "#include <stddef.h>"
|
||||
echo "typedef struct lua_State lua_State; typedef double lua_Number; typedef int (*lua_CFunction)(lua_State*); typedef ptrdiff_t lua_Integer;"
|
||||
echo "typedef unsigned long lua_Unsigned; typedef struct luaL_Buffer luaL_Buffer; typedef struct luaL_Reg luaL_Reg; typedef struct lua_Debug lua_Debug;"
|
||||
echo "typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);"
|
||||
echo "typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);"
|
||||
echo "typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);"
|
||||
LUA_HEADERS=`pkg-config --cflags lua5.2 | sed 's/^-I//' | sed 's/$/\/*.h/'`
|
||||
grep -h "^LUA\(LIB\)*_API" $LUA_HEADERS | sed "s/LUA\(LIB\)*_API //" | sed "s/(lua/(*lua/" | grep -v ",\s*$" | sed "s/^/static /"
|
||||
grep -h "#define luaL*_" $LUA_HEADERS | grep -v "\\\s*$" | grep -v "\(assert\|lock\)" | grep -v "\(asm\|int32\)" | grep -v "#define lua_number2integer(i,n)\s*lua_number2int(i, n)"
|
||||
echo "#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)"
|
||||
echo "#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)"
|
||||
echo "#define IMPORT_SYMBOL(name, ret, ...) name = (ret (*)(__VA_ARGS__))symbol(#name)"
|
||||
echo "static void lite_xl_plugin_init(void* XL) {"
|
||||
echo "\tvoid* (*symbol)(const char*) = (void* (*)(const char*))XL;"
|
||||
grep -h "^LUA\(LIB\)*_API" $LUA_HEADERS | sed "s/LUA\(LIB\)*_API //" | sed "s/(lua/(*lua/" | grep -v ",\s*$" | sed "s/^\([^)]*\)(\*\(lua\w*\))\s*(/\tIMPORT_SYMBOL(\2, \1,/"
|
||||
echo "}"
|
||||
echo "#endif"
|
||||
*/
|
||||
#include <stddef.h>
|
||||
typedef struct lua_State lua_State; typedef double lua_Number; typedef int (*lua_CFunction)(lua_State*); typedef ptrdiff_t lua_Integer;
|
||||
typedef unsigned long lua_Unsigned; typedef struct luaL_Buffer luaL_Buffer; typedef struct luaL_Reg luaL_Reg; typedef struct lua_Debug lua_Debug;
|
||||
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
|
||||
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
|
||||
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
|
||||
static void (*luaL_checkversion_) (lua_State *L, lua_Number ver);
|
||||
static int (*luaL_getmetafield) (lua_State *L, int obj, const char *e);
|
||||
static int (*luaL_callmeta) (lua_State *L, int obj, const char *e);
|
||||
static const char *(*luaL_tolstring) (lua_State *L, int idx, size_t *len);
|
||||
static int (*luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
|
||||
static lua_Number (*luaL_checknumber) (lua_State *L, int numArg);
|
||||
static lua_Number (*luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
|
||||
static lua_Integer (*luaL_checkinteger) (lua_State *L, int numArg);
|
||||
static lua_Unsigned (*luaL_checkunsigned) (lua_State *L, int numArg);
|
||||
static void (*luaL_checkstack) (lua_State *L, int sz, const char *msg);
|
||||
static void (*luaL_checktype) (lua_State *L, int narg, int t);
|
||||
static void (*luaL_checkany) (lua_State *L, int narg);
|
||||
static int (*luaL_newmetatable) (lua_State *L, const char *tname);
|
||||
static void (*luaL_setmetatable) (lua_State *L, const char *tname);
|
||||
static void *(*luaL_testudata) (lua_State *L, int ud, const char *tname);
|
||||
static void *(*luaL_checkudata) (lua_State *L, int ud, const char *tname);
|
||||
static void (*luaL_where) (lua_State *L, int lvl);
|
||||
static int (*luaL_error) (lua_State *L, const char *fmt, ...);
|
||||
static int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
|
||||
static int (*luaL_execresult) (lua_State *L, int stat);
|
||||
static int (*luaL_ref) (lua_State *L, int t);
|
||||
static void (*luaL_unref) (lua_State *L, int t, int ref);
|
||||
static int (*luaL_loadstring) (lua_State *L, const char *s);
|
||||
static lua_State *(*luaL_newstate) (void);
|
||||
static int (*luaL_len) (lua_State *L, int idx);
|
||||
static void (*luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
|
||||
static int (*luaL_getsubtable) (lua_State *L, int idx, const char *fname);
|
||||
static void (*luaL_buffinit) (lua_State *L, luaL_Buffer *B);
|
||||
static char *(*luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
|
||||
static void (*luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
|
||||
static void (*luaL_addstring) (luaL_Buffer *B, const char *s);
|
||||
static void (*luaL_addvalue) (luaL_Buffer *B);
|
||||
static void (*luaL_pushresult) (luaL_Buffer *B);
|
||||
static void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
|
||||
static char *(*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
|
||||
static lua_State *(*lua_newstate) (lua_Alloc f, void *ud);
|
||||
static void (*lua_close) (lua_State *L);
|
||||
static lua_State *(*lua_newthread) (lua_State *L);
|
||||
static lua_CFunction (*lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||||
static const lua_Number *(*lua_version) (lua_State *L);
|
||||
static int (*lua_absindex) (lua_State *L, int idx);
|
||||
static int (*lua_gettop) (lua_State *L);
|
||||
static void (*lua_settop) (lua_State *L, int idx);
|
||||
static void (*lua_pushvalue) (lua_State *L, int idx);
|
||||
static void (*lua_remove) (lua_State *L, int idx);
|
||||
static void (*lua_insert) (lua_State *L, int idx);
|
||||
static void (*lua_replace) (lua_State *L, int idx);
|
||||
static void (*lua_copy) (lua_State *L, int fromidx, int toidx);
|
||||
static int (*lua_checkstack) (lua_State *L, int sz);
|
||||
static void (*lua_xmove) (lua_State *from, lua_State *to, int n);
|
||||
static int (*lua_isnumber) (lua_State *L, int idx);
|
||||
static int (*lua_isstring) (lua_State *L, int idx);
|
||||
static int (*lua_iscfunction) (lua_State *L, int idx);
|
||||
static int (*lua_isuserdata) (lua_State *L, int idx);
|
||||
static int (*lua_type) (lua_State *L, int idx);
|
||||
static const char *(*lua_typename) (lua_State *L, int tp);
|
||||
static lua_Number (*lua_tonumberx) (lua_State *L, int idx, int *isnum);
|
||||
static lua_Integer (*lua_tointegerx) (lua_State *L, int idx, int *isnum);
|
||||
static lua_Unsigned (*lua_tounsignedx) (lua_State *L, int idx, int *isnum);
|
||||
static int (*lua_toboolean) (lua_State *L, int idx);
|
||||
static const char *(*lua_tolstring) (lua_State *L, int idx, size_t *len);
|
||||
static size_t (*lua_rawlen) (lua_State *L, int idx);
|
||||
static lua_CFunction (*lua_tocfunction) (lua_State *L, int idx);
|
||||
static void *(*lua_touserdata) (lua_State *L, int idx);
|
||||
static lua_State *(*lua_tothread) (lua_State *L, int idx);
|
||||
static const void *(*lua_topointer) (lua_State *L, int idx);
|
||||
static void (*lua_arith) (lua_State *L, int op);
|
||||
static int (*lua_rawequal) (lua_State *L, int idx1, int idx2);
|
||||
static int (*lua_compare) (lua_State *L, int idx1, int idx2, int op);
|
||||
static void (*lua_pushnil) (lua_State *L);
|
||||
static void (*lua_pushnumber) (lua_State *L, lua_Number n);
|
||||
static void (*lua_pushinteger) (lua_State *L, lua_Integer n);
|
||||
static void (*lua_pushunsigned) (lua_State *L, lua_Unsigned n);
|
||||
static const char *(*lua_pushlstring) (lua_State *L, const char *s, size_t l);
|
||||
static const char *(*lua_pushstring) (lua_State *L, const char *s);
|
||||
static const char *(*lua_pushfstring) (lua_State *L, const char *fmt, ...);
|
||||
static void (*lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
|
||||
static void (*lua_pushboolean) (lua_State *L, int b);
|
||||
static void (*lua_pushlightuserdata) (lua_State *L, void *p);
|
||||
static int (*lua_pushthread) (lua_State *L);
|
||||
static void (*lua_getglobal) (lua_State *L, const char *var);
|
||||
static void (*lua_gettable) (lua_State *L, int idx);
|
||||
static void (*lua_getfield) (lua_State *L, int idx, const char *k);
|
||||
static void (*lua_rawget) (lua_State *L, int idx);
|
||||
static void (*lua_rawgeti) (lua_State *L, int idx, int n);
|
||||
static void (*lua_rawgetp) (lua_State *L, int idx, const void *p);
|
||||
static void (*lua_createtable) (lua_State *L, int narr, int nrec);
|
||||
static void *(*lua_newuserdata) (lua_State *L, size_t sz);
|
||||
static int (*lua_getmetatable) (lua_State *L, int objindex);
|
||||
static void (*lua_getuservalue) (lua_State *L, int idx);
|
||||
static void (*lua_setglobal) (lua_State *L, const char *var);
|
||||
static void (*lua_settable) (lua_State *L, int idx);
|
||||
static void (*lua_setfield) (lua_State *L, int idx, const char *k);
|
||||
static void (*lua_rawset) (lua_State *L, int idx);
|
||||
static void (*lua_rawseti) (lua_State *L, int idx, int n);
|
||||
static void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
|
||||
static int (*lua_setmetatable) (lua_State *L, int objindex);
|
||||
static void (*lua_setuservalue) (lua_State *L, int idx);
|
||||
static int (*lua_getctx) (lua_State *L, int *ctx);
|
||||
static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data);
|
||||
static int (*lua_resume) (lua_State *L, lua_State *from, int narg);
|
||||
static int (*lua_status) (lua_State *L);
|
||||
static int (*lua_gc) (lua_State *L, int what, int data);
|
||||
static int (*lua_error) (lua_State *L);
|
||||
static int (*lua_next) (lua_State *L, int idx);
|
||||
static void (*lua_concat) (lua_State *L, int n);
|
||||
static void (*lua_len) (lua_State *L, int idx);
|
||||
static lua_Alloc (*lua_getallocf) (lua_State *L, void **ud);
|
||||
static void (*lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
|
||||
static int (*lua_getstack) (lua_State *L, int level, lua_Debug *ar);
|
||||
static int (*lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
|
||||
static const char *(*lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
|
||||
static const char *(*lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
|
||||
static const char *(*lua_getupvalue) (lua_State *L, int funcindex, int n);
|
||||
static const char *(*lua_setupvalue) (lua_State *L, int funcindex, int n);
|
||||
static void *(*lua_upvalueid) (lua_State *L, int fidx, int n);
|
||||
static int (*lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
|
||||
static lua_Hook (*lua_gethook) (lua_State *L);
|
||||
static int (*lua_gethookmask) (lua_State *L);
|
||||
static int (*lua_gethookcount) (lua_State *L);
|
||||
static void (*luaL_openlibs) (lua_State *L);
|
||||
#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM)
|
||||
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
|
||||
#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
|
||||
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
|
||||
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
|
||||
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
|
||||
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
|
||||
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
|
||||
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
|
||||
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)
|
||||
#define luaL_addsize(B,s) ((B)->n += (s))
|
||||
#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
|
||||
#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0))
|
||||
#define lua_h
|
||||
#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))
|
||||
#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL)
|
||||
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
|
||||
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
|
||||
#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL)
|
||||
#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL)
|
||||
#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL)
|
||||
#define lua_pop(L,n) lua_settop(L, -(n)-1)
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
|
||||
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
|
||||
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
|
||||
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
|
||||
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
|
||||
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
|
||||
#define lua_strlen(L,i) lua_rawlen(L, (i))
|
||||
#define lua_objlen(L,i) lua_rawlen(L, (i))
|
||||
#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
|
||||
#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT)
|
||||
#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n))
|
||||
#define lua_str2number(s,p) strtod((s), (p))
|
||||
#define lua_strx2number(s,p) strtod((s), (p))
|
||||
#define lua_pushliteral(L, s) lua_pushlstring(L, s, (sizeof(s)/sizeof(char))-1)
|
||||
#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
|
||||
#define IMPORT_SYMBOL(name, ret, ...) name = (ret (*)(__VA_ARGS__))symbol(#name)
|
||||
static void lite_xl_plugin_init(void* XL) {
|
||||
void* (*symbol)(const char*) = (void* (*)(const char*))XL;
|
||||
IMPORT_SYMBOL(luaL_checkversion_, void ,lua_State *L, lua_Number ver);
|
||||
IMPORT_SYMBOL(luaL_getmetafield, int ,lua_State *L, int obj, const char *e);
|
||||
IMPORT_SYMBOL(luaL_callmeta, int ,lua_State *L, int obj, const char *e);
|
||||
IMPORT_SYMBOL(luaL_tolstring, const char *,lua_State *L, int idx, size_t *len);
|
||||
IMPORT_SYMBOL(luaL_argerror, int ,lua_State *L, int numarg, const char *extramsg);
|
||||
IMPORT_SYMBOL(luaL_checknumber, lua_Number ,lua_State *L, int numArg);
|
||||
IMPORT_SYMBOL(luaL_optnumber, lua_Number ,lua_State *L, int nArg, lua_Number def);
|
||||
IMPORT_SYMBOL(luaL_checkinteger, lua_Integer ,lua_State *L, int numArg);
|
||||
IMPORT_SYMBOL(luaL_checkunsigned, lua_Unsigned ,lua_State *L, int numArg);
|
||||
IMPORT_SYMBOL(luaL_checkstack, void ,lua_State *L, int sz, const char *msg);
|
||||
IMPORT_SYMBOL(luaL_checktype, void ,lua_State *L, int narg, int t);
|
||||
IMPORT_SYMBOL(luaL_checkany, void ,lua_State *L, int narg);
|
||||
IMPORT_SYMBOL(luaL_newmetatable, int ,lua_State *L, const char *tname);
|
||||
IMPORT_SYMBOL(luaL_setmetatable, void ,lua_State *L, const char *tname);
|
||||
IMPORT_SYMBOL(luaL_testudata, void *,lua_State *L, int ud, const char *tname);
|
||||
IMPORT_SYMBOL(luaL_checkudata, void *,lua_State *L, int ud, const char *tname);
|
||||
IMPORT_SYMBOL(luaL_where, void ,lua_State *L, int lvl);
|
||||
IMPORT_SYMBOL(luaL_error, int ,lua_State *L, const char *fmt, ...);
|
||||
IMPORT_SYMBOL(luaL_fileresult, int ,lua_State *L, int stat, const char *fname);
|
||||
IMPORT_SYMBOL(luaL_execresult, int ,lua_State *L, int stat);
|
||||
IMPORT_SYMBOL(luaL_ref, int ,lua_State *L, int t);
|
||||
IMPORT_SYMBOL(luaL_unref, void ,lua_State *L, int t, int ref);
|
||||
IMPORT_SYMBOL(luaL_loadstring, int ,lua_State *L, const char *s);
|
||||
IMPORT_SYMBOL(luaL_newstate, lua_State *,void);
|
||||
IMPORT_SYMBOL(luaL_len, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(luaL_setfuncs, void ,lua_State *L, const luaL_Reg *l, int nup);
|
||||
IMPORT_SYMBOL(luaL_getsubtable, int ,lua_State *L, int idx, const char *fname);
|
||||
IMPORT_SYMBOL(luaL_buffinit, void ,lua_State *L, luaL_Buffer *B);
|
||||
IMPORT_SYMBOL(luaL_prepbuffsize, char *,luaL_Buffer *B, size_t sz);
|
||||
IMPORT_SYMBOL(luaL_addlstring, void ,luaL_Buffer *B, const char *s, size_t l);
|
||||
IMPORT_SYMBOL(luaL_addstring, void ,luaL_Buffer *B, const char *s);
|
||||
IMPORT_SYMBOL(luaL_addvalue, void ,luaL_Buffer *B);
|
||||
IMPORT_SYMBOL(luaL_pushresult, void ,luaL_Buffer *B);
|
||||
IMPORT_SYMBOL(luaL_pushresultsize, void ,luaL_Buffer *B, size_t sz);
|
||||
IMPORT_SYMBOL(luaL_buffinitsize, char *,lua_State *L, luaL_Buffer *B, size_t sz);
|
||||
IMPORT_SYMBOL(lua_newstate, lua_State *,lua_Alloc f, void *ud);
|
||||
IMPORT_SYMBOL(lua_close, void ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_newthread, lua_State *,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_atpanic, lua_CFunction ,lua_State *L, lua_CFunction panicf);
|
||||
IMPORT_SYMBOL(lua_version, const lua_Number *,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_absindex, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_gettop, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_settop, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_pushvalue, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_remove, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_insert, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_replace, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_copy, void ,lua_State *L, int fromidx, int toidx);
|
||||
IMPORT_SYMBOL(lua_checkstack, int ,lua_State *L, int sz);
|
||||
IMPORT_SYMBOL(lua_xmove, void ,lua_State *from, lua_State *to, int n);
|
||||
IMPORT_SYMBOL(lua_isnumber, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_isstring, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_iscfunction, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_isuserdata, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_type, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_typename, const char *,lua_State *L, int tp);
|
||||
IMPORT_SYMBOL(lua_tonumberx, lua_Number ,lua_State *L, int idx, int *isnum);
|
||||
IMPORT_SYMBOL(lua_tointegerx, lua_Integer ,lua_State *L, int idx, int *isnum);
|
||||
IMPORT_SYMBOL(lua_tounsignedx, lua_Unsigned ,lua_State *L, int idx, int *isnum);
|
||||
IMPORT_SYMBOL(lua_toboolean, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_tolstring, const char *,lua_State *L, int idx, size_t *len);
|
||||
IMPORT_SYMBOL(lua_rawlen, size_t ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_tocfunction, lua_CFunction ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_touserdata, void *,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_tothread, lua_State *,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_topointer, const void *,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_arith, void ,lua_State *L, int op);
|
||||
IMPORT_SYMBOL(lua_rawequal, int ,lua_State *L, int idx1, int idx2);
|
||||
IMPORT_SYMBOL(lua_compare, int ,lua_State *L, int idx1, int idx2, int op);
|
||||
IMPORT_SYMBOL(lua_pushnil, void ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_pushnumber, void ,lua_State *L, lua_Number n);
|
||||
IMPORT_SYMBOL(lua_pushinteger, void ,lua_State *L, lua_Integer n);
|
||||
IMPORT_SYMBOL(lua_pushunsigned, void ,lua_State *L, lua_Unsigned n);
|
||||
IMPORT_SYMBOL(lua_pushlstring, const char *,lua_State *L, const char *s, size_t l);
|
||||
IMPORT_SYMBOL(lua_pushstring, const char *,lua_State *L, const char *s);
|
||||
IMPORT_SYMBOL(lua_pushfstring, const char *,lua_State *L, const char *fmt, ...);
|
||||
IMPORT_SYMBOL(lua_pushcclosure, void ,lua_State *L, lua_CFunction fn, int n);
|
||||
IMPORT_SYMBOL(lua_pushboolean, void ,lua_State *L, int b);
|
||||
IMPORT_SYMBOL(lua_pushlightuserdata, void ,lua_State *L, void *p);
|
||||
IMPORT_SYMBOL(lua_pushthread, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_getglobal, void ,lua_State *L, const char *var);
|
||||
IMPORT_SYMBOL(lua_gettable, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_getfield, void ,lua_State *L, int idx, const char *k);
|
||||
IMPORT_SYMBOL(lua_rawget, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_rawgeti, void ,lua_State *L, int idx, int n);
|
||||
IMPORT_SYMBOL(lua_rawgetp, void ,lua_State *L, int idx, const void *p);
|
||||
IMPORT_SYMBOL(lua_createtable, void ,lua_State *L, int narr, int nrec);
|
||||
IMPORT_SYMBOL(lua_newuserdata, void *,lua_State *L, size_t sz);
|
||||
IMPORT_SYMBOL(lua_getmetatable, int ,lua_State *L, int objindex);
|
||||
IMPORT_SYMBOL(lua_getuservalue, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_setglobal, void ,lua_State *L, const char *var);
|
||||
IMPORT_SYMBOL(lua_settable, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_setfield, void ,lua_State *L, int idx, const char *k);
|
||||
IMPORT_SYMBOL(lua_rawset, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_rawseti, void ,lua_State *L, int idx, int n);
|
||||
IMPORT_SYMBOL(lua_rawsetp, void ,lua_State *L, int idx, const void *p);
|
||||
IMPORT_SYMBOL(lua_setmetatable, int ,lua_State *L, int objindex);
|
||||
IMPORT_SYMBOL(lua_setuservalue, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_getctx, int ,lua_State *L, int *ctx);
|
||||
IMPORT_SYMBOL(lua_dump, int ,lua_State *L, lua_Writer writer, void *data);
|
||||
IMPORT_SYMBOL(lua_resume, int ,lua_State *L, lua_State *from, int narg);
|
||||
IMPORT_SYMBOL(lua_status, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_gc, int ,lua_State *L, int what, int data);
|
||||
IMPORT_SYMBOL(lua_error, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_next, int ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_concat, void ,lua_State *L, int n);
|
||||
IMPORT_SYMBOL(lua_len, void ,lua_State *L, int idx);
|
||||
IMPORT_SYMBOL(lua_getallocf, lua_Alloc ,lua_State *L, void **ud);
|
||||
IMPORT_SYMBOL(lua_setallocf, void ,lua_State *L, lua_Alloc f, void *ud);
|
||||
IMPORT_SYMBOL(lua_getstack, int ,lua_State *L, int level, lua_Debug *ar);
|
||||
IMPORT_SYMBOL(lua_getinfo, int ,lua_State *L, const char *what, lua_Debug *ar);
|
||||
IMPORT_SYMBOL(lua_getlocal, const char *,lua_State *L, const lua_Debug *ar, int n);
|
||||
IMPORT_SYMBOL(lua_setlocal, const char *,lua_State *L, const lua_Debug *ar, int n);
|
||||
IMPORT_SYMBOL(lua_getupvalue, const char *,lua_State *L, int funcindex, int n);
|
||||
IMPORT_SYMBOL(lua_setupvalue, const char *,lua_State *L, int funcindex, int n);
|
||||
IMPORT_SYMBOL(lua_upvalueid, void *,lua_State *L, int fidx, int n);
|
||||
IMPORT_SYMBOL(lua_sethook, int ,lua_State *L, lua_Hook func, int mask, int count);
|
||||
IMPORT_SYMBOL(lua_gethook, lua_Hook ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_gethookmask, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(lua_gethookcount, int ,lua_State *L);
|
||||
IMPORT_SYMBOL(luaL_openlibs, void ,lua_State *L);
|
||||
}
|
||||
#endif
|
|
@ -22,6 +22,7 @@ Various scripts and configurations used to configure, build, and package Lite XL
|
|||
and run Lite XL, mainly useful for CI and documentation purpose.
|
||||
Preferably not to be used in user systems.
|
||||
- **fontello-config.json**: Used by the icons generator.
|
||||
- **keymap-generator**: Generates a JSON file containing the keymap
|
||||
|
||||
[1]: https://github.com/LinusU/node-appdmg
|
||||
[2]: https://docs.appimage.org/
|
||||
|
|
|
@ -0,0 +1,714 @@
|
|||
-- Module options:
|
||||
local always_try_using_lpeg = true
|
||||
local register_global_module_table = false
|
||||
local global_module_name = 'json'
|
||||
|
||||
--[==[
|
||||
|
||||
David Kolf's JSON module for Lua 5.1/5.2
|
||||
|
||||
Version 2.5
|
||||
|
||||
|
||||
For the documentation see the corresponding readme.txt or visit
|
||||
<http://dkolf.de/src/dkjson-lua.fsl/>.
|
||||
|
||||
You can contact the author by sending an e-mail to 'david' at the
|
||||
domain 'dkolf.de'.
|
||||
|
||||
|
||||
Copyright (C) 2010-2013 David Heiko Kolf
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
|
||||
--]==]
|
||||
|
||||
-- global dependencies:
|
||||
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
|
||||
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
|
||||
local error, require, pcall, select = error, require, pcall, select
|
||||
local floor, huge = math.floor, math.huge
|
||||
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
|
||||
string.rep, string.gsub, string.sub, string.byte, string.char,
|
||||
string.find, string.len, string.format
|
||||
local strmatch = string.match
|
||||
local concat = table.concat
|
||||
|
||||
local json = { version = "dkjson 2.5" }
|
||||
|
||||
if register_global_module_table then
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
|
||||
local _ENV = nil -- blocking globals in Lua 5.2
|
||||
|
||||
pcall (function()
|
||||
-- Enable access to blocked metatables.
|
||||
-- Don't worry, this module doesn't change anything in them.
|
||||
local debmeta = require "debug".getmetatable
|
||||
if debmeta then getmetatable = debmeta end
|
||||
end)
|
||||
|
||||
json.null = setmetatable ({}, {
|
||||
__tojson = function () return "null" end
|
||||
})
|
||||
|
||||
local function isarray (tbl)
|
||||
local max, n, arraylen = 0, 0, 0
|
||||
for k,v in pairs (tbl) do
|
||||
if k == 'n' and type(v) == 'number' then
|
||||
arraylen = v
|
||||
if v > max then
|
||||
max = v
|
||||
end
|
||||
else
|
||||
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
|
||||
return false
|
||||
end
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
if max > 10 and max > arraylen and max > n * 2 then
|
||||
return false -- don't create an array with too many holes
|
||||
end
|
||||
return true, max
|
||||
end
|
||||
|
||||
local escapecodes = {
|
||||
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
|
||||
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
|
||||
}
|
||||
|
||||
local function escapeutf8 (uchar)
|
||||
local value = escapecodes[uchar]
|
||||
if value then
|
||||
return value
|
||||
end
|
||||
local a, b, c, d = strbyte (uchar, 1, 4)
|
||||
a, b, c, d = a or 0, b or 0, c or 0, d or 0
|
||||
if a <= 0x7f then
|
||||
value = a
|
||||
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
|
||||
value = (a - 0xc0) * 0x40 + b - 0x80
|
||||
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
|
||||
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
|
||||
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
|
||||
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
|
||||
else
|
||||
return ""
|
||||
end
|
||||
if value <= 0xffff then
|
||||
return strformat ("\\u%.4x", value)
|
||||
elseif value <= 0x10ffff then
|
||||
-- encode as UTF-16 surrogate pair
|
||||
value = value - 0x10000
|
||||
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
|
||||
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
local function fsub (str, pattern, repl)
|
||||
-- gsub always builds a new string in a buffer, even when no match
|
||||
-- exists. First using find should be more efficient when most strings
|
||||
-- don't contain the pattern.
|
||||
if strfind (str, pattern) then
|
||||
return gsub (str, pattern, repl)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
local function quotestring (value)
|
||||
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
|
||||
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
|
||||
if strfind (value, "[\194\216\220\225\226\239]") then
|
||||
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
|
||||
value = fsub (value, "\216[\128-\132]", escapeutf8)
|
||||
value = fsub (value, "\220\143", escapeutf8)
|
||||
value = fsub (value, "\225\158[\180\181]", escapeutf8)
|
||||
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
|
||||
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
|
||||
value = fsub (value, "\239\187\191", escapeutf8)
|
||||
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
|
||||
end
|
||||
return "\"" .. value .. "\""
|
||||
end
|
||||
json.quotestring = quotestring
|
||||
|
||||
local function replace(str, o, n)
|
||||
local i, j = strfind (str, o, 1, true)
|
||||
if i then
|
||||
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
-- locale independent num2str and str2num functions
|
||||
local decpoint, numfilter
|
||||
|
||||
local function updatedecpoint ()
|
||||
decpoint = strmatch(tostring(0.5), "([^05+])")
|
||||
-- build a filter that can be used to remove group separators
|
||||
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
|
||||
end
|
||||
|
||||
updatedecpoint()
|
||||
|
||||
local function num2str (num)
|
||||
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
|
||||
end
|
||||
|
||||
local function str2num (str)
|
||||
local num = tonumber(replace(str, ".", decpoint))
|
||||
if not num then
|
||||
updatedecpoint()
|
||||
num = tonumber(replace(str, ".", decpoint))
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
local function addnewline2 (level, buffer, buflen)
|
||||
buffer[buflen+1] = "\n"
|
||||
buffer[buflen+2] = strrep (" ", level)
|
||||
buflen = buflen + 2
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.addnewline (state)
|
||||
if state.indent then
|
||||
state.bufferlen = addnewline2 (state.level or 0,
|
||||
state.buffer, state.bufferlen or #(state.buffer))
|
||||
end
|
||||
end
|
||||
|
||||
local encode2 -- forward declaration
|
||||
|
||||
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local kt = type (key)
|
||||
if kt ~= 'string' and kt ~= 'number' then
|
||||
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
|
||||
end
|
||||
if prev then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level, buffer, buflen)
|
||||
end
|
||||
buffer[buflen+1] = quotestring (key)
|
||||
buffer[buflen+2] = ":"
|
||||
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
|
||||
end
|
||||
|
||||
local function appendcustom(res, buffer, state)
|
||||
local buflen = state.bufferlen
|
||||
if type (res) == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = res
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
local function exception(reason, value, state, buffer, buflen, defaultmessage)
|
||||
defaultmessage = defaultmessage or reason
|
||||
local handler = state.exception
|
||||
if not handler then
|
||||
return nil, defaultmessage
|
||||
else
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = handler (reason, value, state, defaultmessage)
|
||||
if not ret then return nil, msg or defaultmessage end
|
||||
return appendcustom(ret, buffer, state)
|
||||
end
|
||||
end
|
||||
|
||||
function json.encodeexception(reason, value, state, defaultmessage)
|
||||
return quotestring("<" .. defaultmessage .. ">")
|
||||
end
|
||||
|
||||
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local valtype = type (value)
|
||||
local valmeta = getmetatable (value)
|
||||
valmeta = type (valmeta) == 'table' and valmeta -- only tables
|
||||
local valtojson = valmeta and valmeta.__tojson
|
||||
if valtojson then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = valtojson (value, state)
|
||||
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
|
||||
tables[value] = nil
|
||||
buflen = appendcustom(ret, buffer, state)
|
||||
elseif value == nil then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "null"
|
||||
elseif valtype == 'number' then
|
||||
local s
|
||||
if value ~= value or value >= huge or -value >= huge then
|
||||
-- This is the behaviour of the original JSON implementation.
|
||||
s = "null"
|
||||
else
|
||||
s = num2str (value)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = s
|
||||
elseif valtype == 'boolean' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = value and "true" or "false"
|
||||
elseif valtype == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = quotestring (value)
|
||||
elseif valtype == 'table' then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
level = level + 1
|
||||
local isa, n = isarray (value)
|
||||
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
|
||||
isa = false
|
||||
end
|
||||
local msg
|
||||
if isa then -- JSON array
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "["
|
||||
for i = 1, n do
|
||||
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
if i < n then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "]"
|
||||
else -- JSON object
|
||||
local prev = false
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "{"
|
||||
local order = valmeta and valmeta.__jsonorder or globalorder
|
||||
if order then
|
||||
local used = {}
|
||||
n = #order
|
||||
for i = 1, n do
|
||||
local k = order[i]
|
||||
local v = value[k]
|
||||
if v then
|
||||
used[k] = true
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
for k,v in pairs (value) do
|
||||
if not used[k] then
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
else -- unordered
|
||||
for k,v in pairs (value) do
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level - 1, buffer, buflen)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "}"
|
||||
end
|
||||
tables[value] = nil
|
||||
else
|
||||
return exception ('unsupported type', value, state, buffer, buflen,
|
||||
"type '" .. valtype .. "' is not supported by JSON.")
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.encode (value, state)
|
||||
state = state or {}
|
||||
local oldbuffer = state.buffer
|
||||
local buffer = oldbuffer or {}
|
||||
state.buffer = buffer
|
||||
updatedecpoint()
|
||||
local ret, msg = encode2 (value, state.indent, state.level or 0,
|
||||
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
|
||||
if not ret then
|
||||
error (msg, 2)
|
||||
elseif oldbuffer == buffer then
|
||||
state.bufferlen = ret
|
||||
return true
|
||||
else
|
||||
state.bufferlen = nil
|
||||
state.buffer = nil
|
||||
return concat (buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local function loc (str, where)
|
||||
local line, pos, linepos = 1, 1, 0
|
||||
while true do
|
||||
pos = strfind (str, "\n", pos, true)
|
||||
if pos and pos < where then
|
||||
line = line + 1
|
||||
linepos = pos
|
||||
pos = pos + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return "line " .. line .. ", column " .. (where - linepos)
|
||||
end
|
||||
|
||||
local function unterminated (str, what, where)
|
||||
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
|
||||
end
|
||||
|
||||
local function scanwhite (str, pos)
|
||||
while true do
|
||||
pos = strfind (str, "%S", pos)
|
||||
if not pos then return nil end
|
||||
local sub2 = strsub (str, pos, pos + 1)
|
||||
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
|
||||
-- UTF-8 Byte Order Mark
|
||||
pos = pos + 3
|
||||
elseif sub2 == "//" then
|
||||
pos = strfind (str, "[\n\r]", pos + 2)
|
||||
if not pos then return nil end
|
||||
elseif sub2 == "/*" then
|
||||
pos = strfind (str, "*/", pos + 2)
|
||||
if not pos then return nil end
|
||||
pos = pos + 2
|
||||
else
|
||||
return pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local escapechars = {
|
||||
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
|
||||
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
|
||||
}
|
||||
|
||||
local function unichar (value)
|
||||
if value < 0 then
|
||||
return nil
|
||||
elseif value <= 0x007f then
|
||||
return strchar (value)
|
||||
elseif value <= 0x07ff then
|
||||
return strchar (0xc0 + floor(value/0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0xffff then
|
||||
return strchar (0xe0 + floor(value/0x1000),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0x10ffff then
|
||||
return strchar (0xf0 + floor(value/0x40000),
|
||||
0x80 + (floor(value/0x1000) % 0x40),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function scanstring (str, pos)
|
||||
local lastpos = pos + 1
|
||||
local buffer, n = {}, 0
|
||||
while true do
|
||||
local nextpos = strfind (str, "[\"\\]", lastpos)
|
||||
if not nextpos then
|
||||
return unterminated (str, "string", pos)
|
||||
end
|
||||
if nextpos > lastpos then
|
||||
n = n + 1
|
||||
buffer[n] = strsub (str, lastpos, nextpos - 1)
|
||||
end
|
||||
if strsub (str, nextpos, nextpos) == "\"" then
|
||||
lastpos = nextpos + 1
|
||||
break
|
||||
else
|
||||
local escchar = strsub (str, nextpos + 1, nextpos + 1)
|
||||
local value
|
||||
if escchar == "u" then
|
||||
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
|
||||
if value then
|
||||
local value2
|
||||
if 0xD800 <= value and value <= 0xDBff then
|
||||
-- we have the high surrogate of UTF-16. Check if there is a
|
||||
-- low surrogate escaped nearby to combine them.
|
||||
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
|
||||
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
|
||||
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
|
||||
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
|
||||
else
|
||||
value2 = nil -- in case it was out of range for a low surrogate
|
||||
end
|
||||
end
|
||||
end
|
||||
value = value and unichar (value)
|
||||
if value then
|
||||
if value2 then
|
||||
lastpos = nextpos + 12
|
||||
else
|
||||
lastpos = nextpos + 6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not value then
|
||||
value = escapechars[escchar] or escchar
|
||||
lastpos = nextpos + 2
|
||||
end
|
||||
n = n + 1
|
||||
buffer[n] = value
|
||||
end
|
||||
end
|
||||
if n == 1 then
|
||||
return buffer[1], lastpos
|
||||
elseif n > 1 then
|
||||
return concat (buffer), lastpos
|
||||
else
|
||||
return "", lastpos
|
||||
end
|
||||
end
|
||||
|
||||
local scanvalue -- forward declaration
|
||||
|
||||
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
|
||||
local len = strlen (str)
|
||||
local tbl, n = {}, 0
|
||||
local pos = startpos + 1
|
||||
if what == 'object' then
|
||||
setmetatable (tbl, objectmeta)
|
||||
else
|
||||
setmetatable (tbl, arraymeta)
|
||||
end
|
||||
while true do
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == closechar then
|
||||
return tbl, pos + 1
|
||||
end
|
||||
local val1, err
|
||||
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
if char == ":" then
|
||||
if val1 == nil then
|
||||
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
|
||||
end
|
||||
pos = scanwhite (str, pos + 1)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local val2
|
||||
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
tbl[val1] = val2
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
else
|
||||
n = n + 1
|
||||
tbl[n] = val1
|
||||
end
|
||||
if char == "," then
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
|
||||
pos = pos or 1
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then
|
||||
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
|
||||
end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == "{" then
|
||||
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "[" then
|
||||
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "\"" then
|
||||
return scanstring (str, pos)
|
||||
else
|
||||
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
|
||||
if pstart then
|
||||
local number = str2num (strsub (str, pstart, pend))
|
||||
if number then
|
||||
return number, pend + 1
|
||||
end
|
||||
end
|
||||
pstart, pend = strfind (str, "^%a%w*", pos)
|
||||
if pstart then
|
||||
local name = strsub (str, pstart, pend)
|
||||
if name == "true" then
|
||||
return true, pend + 1
|
||||
elseif name == "false" then
|
||||
return false, pend + 1
|
||||
elseif name == "null" then
|
||||
return nullval, pend + 1
|
||||
end
|
||||
end
|
||||
return nil, pos, "no valid JSON value at " .. loc (str, pos)
|
||||
end
|
||||
end
|
||||
|
||||
local function optionalmetatables(...)
|
||||
if select("#", ...) > 0 then
|
||||
return ...
|
||||
else
|
||||
return {__jsontype = 'object'}, {__jsontype = 'array'}
|
||||
end
|
||||
end
|
||||
|
||||
function json.decode (str, pos, nullval, ...)
|
||||
local objectmeta, arraymeta = optionalmetatables(...)
|
||||
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
end
|
||||
|
||||
function json.use_lpeg ()
|
||||
local g = require ("lpeg")
|
||||
|
||||
if g.version() == "0.11" then
|
||||
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
|
||||
end
|
||||
|
||||
local pegmatch = g.match
|
||||
local P, S, R = g.P, g.S, g.R
|
||||
|
||||
local function ErrorCall (str, pos, msg, state)
|
||||
if not state.msg then
|
||||
state.msg = msg .. " at " .. loc (str, pos)
|
||||
state.pos = pos
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function Err (msg)
|
||||
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
|
||||
end
|
||||
|
||||
local SingleLineComment = P"//" * (1 - S"\n\r")^0
|
||||
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
|
||||
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
|
||||
|
||||
local PlainChar = 1 - S"\"\\\n\r"
|
||||
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
|
||||
local HexDigit = R("09", "af", "AF")
|
||||
local function UTF16Surrogate (match, pos, high, low)
|
||||
high, low = tonumber (high, 16), tonumber (low, 16)
|
||||
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
|
||||
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function UTF16BMP (hex)
|
||||
return unichar (tonumber (hex, 16))
|
||||
end
|
||||
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
|
||||
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
|
||||
local Char = UnicodeEscape + EscapeSequence + PlainChar
|
||||
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
|
||||
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
|
||||
local Fractal = P"." * R"09"^0
|
||||
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
|
||||
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
|
||||
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
|
||||
local SimpleValue = Number + String + Constant
|
||||
local ArrayContent, ObjectContent
|
||||
|
||||
-- The functions parsearray and parseobject parse only a single value/pair
|
||||
-- at a time and store them directly to avoid hitting the LPeg limits.
|
||||
local function parsearray (str, pos, nullval, state)
|
||||
local obj, cont
|
||||
local npos
|
||||
local t, nt = {}, 0
|
||||
repeat
|
||||
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
nt = nt + 1
|
||||
t[nt] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.arraymeta)
|
||||
end
|
||||
|
||||
local function parseobject (str, pos, nullval, state)
|
||||
local obj, key, cont
|
||||
local npos
|
||||
local t = {}
|
||||
repeat
|
||||
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
t[key] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.objectmeta)
|
||||
end
|
||||
|
||||
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
|
||||
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
|
||||
local Value = Space * (Array + Object + SimpleValue)
|
||||
local ExpectedValue = Value + Space * Err "value expected"
|
||||
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
|
||||
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local DecodeValue = ExpectedValue * g.Cp ()
|
||||
|
||||
function json.decode (str, pos, nullval, ...)
|
||||
local state = {}
|
||||
state.objectmeta, state.arraymeta = optionalmetatables(...)
|
||||
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
|
||||
if state.msg then
|
||||
return nil, state.pos, state.msg
|
||||
else
|
||||
return obj, retpos
|
||||
end
|
||||
end
|
||||
|
||||
-- use this function only once:
|
||||
json.use_lpeg = function () return json end
|
||||
|
||||
json.using_lpeg = true
|
||||
|
||||
return json -- so you can get the module using json = require "dkjson".use_lpeg()
|
||||
end
|
||||
|
||||
if always_try_using_lpeg then
|
||||
pcall (json.use_lpeg)
|
||||
end
|
||||
|
||||
return json
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env lua
|
||||
local dkjson = require "dkjson"
|
||||
|
||||
|
||||
local function load_keymap(target, target_map, macos)
|
||||
_G.MACOS = macos
|
||||
package.loaded["core.keymap"] = nil
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
if target then
|
||||
keymap.map = {}
|
||||
require(target)
|
||||
end
|
||||
|
||||
target_map = target_map or {}
|
||||
-- keymap.reverse_map does not do this?
|
||||
for key, actions in pairs(keymap.map) do
|
||||
for _, action in ipairs(actions) do
|
||||
target_map[action] = target_map[action] or {}
|
||||
table.insert(target_map[action], key)
|
||||
end
|
||||
end
|
||||
|
||||
return target_map
|
||||
end
|
||||
|
||||
|
||||
local function normalize(map)
|
||||
local result = {}
|
||||
for action, keys in pairs(map) do
|
||||
local uniq = {}
|
||||
local r = { combination = {}, action = action }
|
||||
for _, v in ipairs(keys) do
|
||||
if not uniq[v] then
|
||||
uniq[v] = true
|
||||
r.combination[#r.combination+1] = v
|
||||
end
|
||||
end
|
||||
result[#result+1] = r
|
||||
end
|
||||
table.sort(result, function(a, b) return a.action < b.action end)
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local function process_module(mod, filename)
|
||||
local map = {}
|
||||
load_keymap(mod, map)
|
||||
load_keymap(mod, map, true)
|
||||
map = normalize(map)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(dkjson.encode(map, { indent = true }))
|
||||
f:close()
|
||||
end
|
||||
|
||||
|
||||
print("Warning: this is not guaranteed to work outside lite-xl's own keymap. Proceed with caution")
|
||||
local LITE_ROOT = arg[1]
|
||||
if not LITE_ROOT then
|
||||
error("LITE_ROOT is not given")
|
||||
end
|
||||
package.path = package.path .. ";" .. LITE_ROOT .. "/?.lua;" .. LITE_ROOT .. "/?/init.lua"
|
||||
|
||||
-- fix core.command (because we don't want load the entire thing)
|
||||
package.loaded["core.command"] = {}
|
||||
|
||||
if #arg > 1 then
|
||||
for i = 2, #arg do
|
||||
process_module(arg[i], arg[i] .. ".json")
|
||||
print(string.format("Exported keymap in %q.", arg[i]))
|
||||
end
|
||||
else
|
||||
process_module(nil, "core.keymap.json")
|
||||
print("Exported the default keymap.")
|
||||
end
|
|
@ -10,7 +10,7 @@ copy_directory_from_repo () {
|
|||
fi
|
||||
local dirname="$1"
|
||||
local destdir="$2"
|
||||
git archive master "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||
git archive "$lite_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||
}
|
||||
|
||||
lite_copy_third_party_modules () {
|
||||
|
@ -23,12 +23,17 @@ lite_copy_third_party_modules () {
|
|||
rm "$build/rxi-lite-colors.zip"
|
||||
}
|
||||
|
||||
lite_branch=master
|
||||
while [ ! -z ${1+x} ]; do
|
||||
case "$1" in
|
||||
-dir)
|
||||
use_dir="$(realpath $2)"
|
||||
shift 2
|
||||
;;
|
||||
-branch)
|
||||
lite_branch="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: $1"
|
||||
exit 1
|
||||
|
@ -73,6 +78,8 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
|||
fi
|
||||
rm "$filename"
|
||||
find lite-xl -name lite -exec chmod a+x '{}' \;
|
||||
start_file=$(find lite-xl -name start.lua)
|
||||
lite_version=$(cat "$start_file" | awk 'match($0, /^\s*VERSION\s*=\s*"(.+)"/, a) { print(a[1]) }')
|
||||
xcoredir="$(find lite-xl -type d -name 'core')"
|
||||
coredir="$(dirname $xcoredir)"
|
||||
echo "coredir: $coredir"
|
||||
|
@ -81,6 +88,7 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
|||
rm -fr "$coredir/$module_name"
|
||||
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
||||
done
|
||||
sed -i "s/@PROJECT_VERSION@/$lite_version/g" "$start_file"
|
||||
for module_name in plugins colors; do
|
||||
cp -r "third/data/$module_name" "$coredir"
|
||||
done
|
||||
|
|
|
@ -6,7 +6,6 @@ int luaopen_renderer(lua_State *L);
|
|||
int luaopen_regex(lua_State *L);
|
||||
int luaopen_process(lua_State *L);
|
||||
|
||||
|
||||
static const luaL_Reg libs[] = {
|
||||
{ "system", luaopen_system },
|
||||
{ "renderer", luaopen_renderer },
|
||||
|
@ -16,7 +15,6 @@ static const luaL_Reg libs[] = {
|
|||
};
|
||||
|
||||
void api_load_libs(lua_State *L) {
|
||||
for (int i = 0; libs[i].name; i++) {
|
||||
for (int i = 0; libs[i].name; i++)
|
||||
luaL_requiref(L, libs[i].name, libs[i].func, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
#ifndef API_H
|
||||
#define API_H
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
#define API_TYPE_FONT "Font"
|
||||
#define API_TYPE_REPLACE "Replace"
|
||||
#define API_TYPE_PROCESS "Process"
|
||||
|
||||
void api_load_libs(lua_State *L);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
#include "api.h"
|
||||
#include "renderer.h"
|
||||
|
||||
|
||||
static int f_new(lua_State *L) {
|
||||
CPReplaceTable *rep_table = lua_newuserdata(L, sizeof(CPReplaceTable));
|
||||
luaL_setmetatable(L, API_TYPE_REPLACE);
|
||||
ren_cp_replace_init(rep_table);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_gc(lua_State *L) {
|
||||
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
|
||||
ren_cp_replace_free(rep_table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_add(lua_State *L) {
|
||||
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
|
||||
const char *src = luaL_checkstring(L, 2);
|
||||
const char *dst = luaL_checkstring(L, 3);
|
||||
ren_cp_replace_add(rep_table, src, dst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg lib[] = {
|
||||
{ "__gc", f_gc },
|
||||
{ "new", f_new },
|
||||
{ "add", f_add },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int luaopen_renderer_replacements(lua_State *L) {
|
||||
luaL_newmetatable(L, API_TYPE_REPLACE);
|
||||
luaL_setfuncs(L, lib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
return 1;
|
||||
}
|
|
@ -2,6 +2,131 @@
|
|||
#include "renderer.h"
|
||||
#include "rencache.h"
|
||||
|
||||
static int f_font_load(lua_State *L) {
|
||||
const char *filename = luaL_checkstring(L, 1);
|
||||
float size = luaL_checknumber(L, 2);
|
||||
unsigned int font_hinting = FONT_HINTING_SLIGHT, font_style = 0;
|
||||
bool subpixel = true;
|
||||
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
|
||||
lua_getfield(L, 3, "antialiasing");
|
||||
if (lua_isstring(L, -1)) {
|
||||
const char *antialiasing = lua_tostring(L, -1);
|
||||
if (antialiasing) {
|
||||
if (strcmp(antialiasing, "grayscale") == 0) {
|
||||
subpixel = false;
|
||||
} else if (strcmp(antialiasing, "subpixel") == 0) {
|
||||
subpixel = true;
|
||||
} else {
|
||||
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_getfield(L, 3, "hinting");
|
||||
if (lua_isstring(L, -1)) {
|
||||
const char *hinting = lua_tostring(L, -1);
|
||||
if (hinting) {
|
||||
if (strcmp(hinting, "slight") == 0) {
|
||||
font_hinting = FONT_HINTING_SLIGHT;
|
||||
} else if (strcmp(hinting, "none") == 0) {
|
||||
font_hinting = FONT_HINTING_NONE;
|
||||
} else if (strcmp(hinting, "full") == 0) {
|
||||
font_hinting = FONT_HINTING_FULL;
|
||||
} else {
|
||||
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_getfield(L, 3, "italic");
|
||||
if (lua_toboolean(L, -1))
|
||||
font_style |= FONT_STYLE_ITALIC;
|
||||
lua_getfield(L, 3, "bold");
|
||||
if (lua_toboolean(L, -1))
|
||||
font_style |= FONT_STYLE_BOLD;
|
||||
lua_getfield(L, 3, "underline");
|
||||
if (lua_toboolean(L, -1))
|
||||
font_style |= FONT_STYLE_UNDERLINE;
|
||||
lua_pop(L, 5);
|
||||
}
|
||||
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||
*font = ren_font_load(filename, size, subpixel, font_hinting, font_style);
|
||||
if (!*font)
|
||||
return luaL_error(L, "failed to load font");
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool font_retrieve(lua_State* L, RenFont** fonts, int idx) {
|
||||
memset(fonts, 0, sizeof(RenFont*)*FONT_FALLBACK_MAX);
|
||||
if (lua_type(L, 1) != LUA_TTABLE) {
|
||||
fonts[0] = *(RenFont**)luaL_checkudata(L, idx, API_TYPE_FONT);
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
do {
|
||||
lua_rawgeti(L, idx, i+1);
|
||||
fonts[i] = !lua_isnil(L, -1) ? *(RenFont**)luaL_checkudata(L, -1, API_TYPE_FONT) : NULL;
|
||||
lua_pop(L, 1);
|
||||
} while (fonts[i] && i++ < FONT_FALLBACK_MAX);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int f_font_copy(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX];
|
||||
bool table = font_retrieve(L, fonts, 1);
|
||||
float size = lua_gettop(L) >= 2 ? luaL_checknumber(L, 2) : ren_font_group_get_height(fonts);
|
||||
if (table) {
|
||||
lua_newtable(L);
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
}
|
||||
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
|
||||
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||
*font = ren_font_copy(fonts[i], size);
|
||||
if (!*font)
|
||||
return luaL_error(L, "failed to copy font");
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
if (table)
|
||||
lua_rawseti(L, -2, i+1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_font_group(lua_State* L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_font_set_tab_size(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||
int n = luaL_checknumber(L, 2);
|
||||
ren_font_group_set_tab_size(fonts, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_font_gc(lua_State *L) {
|
||||
RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
ren_font_free(*self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_font_get_width(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||
lua_pushnumber(L, ren_font_group_get_width(fonts, luaL_checkstring(L, 2)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_font_get_height(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||
lua_pushnumber(L, ren_font_group_get_height(fonts));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_font_get_size(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||
lua_pushnumber(L, ren_font_group_get_size(fonts));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static RenColor checkcolor(lua_State *L, int idx, int def) {
|
||||
RenColor color;
|
||||
|
@ -71,40 +196,18 @@ static int f_draw_rect(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
|
||||
FontDesc *font_desc = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
static int f_draw_text(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX];
|
||||
font_retrieve(L, fonts, 1);
|
||||
const char *text = luaL_checkstring(L, 2);
|
||||
/* The coordinate below will be in subpixel iff draw_subpixel is true.
|
||||
Otherwise it will be in pixels. */
|
||||
int x_subpixel = luaL_checknumber(L, 3);
|
||||
float x = luaL_checknumber(L, 3);
|
||||
int y = luaL_checknumber(L, 4);
|
||||
RenColor color = checkcolor(L, 5, 255);
|
||||
|
||||
CPReplaceTable *rep_table;
|
||||
RenColor replace_color;
|
||||
if (lua_gettop(L) >= 7) {
|
||||
rep_table = luaL_checkudata(L, 6, API_TYPE_REPLACE);
|
||||
replace_color = checkcolor(L, 7, 255);
|
||||
} else {
|
||||
rep_table = NULL;
|
||||
replace_color = (RenColor) {0};
|
||||
}
|
||||
|
||||
x_subpixel = rencache_draw_text(L, font_desc, 1, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color);
|
||||
lua_pushnumber(L, x_subpixel);
|
||||
x = rencache_draw_text(L, fonts, text, x, y, color);
|
||||
lua_pushnumber(L, x);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_draw_text(lua_State *L) {
|
||||
return draw_text_subpixel_impl(L, false);
|
||||
}
|
||||
|
||||
|
||||
static int f_draw_text_subpixel(lua_State *L) {
|
||||
return draw_text_subpixel_impl(L, true);
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg lib[] = {
|
||||
{ "show_debug", f_show_debug },
|
||||
{ "get_size", f_get_size },
|
||||
|
@ -113,19 +216,27 @@ static const luaL_Reg lib[] = {
|
|||
{ "set_clip_rect", f_set_clip_rect },
|
||||
{ "draw_rect", f_draw_rect },
|
||||
{ "draw_text", f_draw_text },
|
||||
{ "draw_text_subpixel", f_draw_text_subpixel },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
int luaopen_renderer_font(lua_State *L);
|
||||
int luaopen_renderer_replacements(lua_State *L);
|
||||
static const luaL_Reg fontLib[] = {
|
||||
{ "__gc", f_font_gc },
|
||||
{ "load", f_font_load },
|
||||
{ "copy", f_font_copy },
|
||||
{ "group", f_font_group },
|
||||
{ "set_tab_size", f_font_set_tab_size },
|
||||
{ "get_width", f_font_get_width },
|
||||
{ "get_height", f_font_get_height },
|
||||
{ "get_size", f_font_get_size },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int luaopen_renderer(lua_State *L) {
|
||||
luaL_newlib(L, lib);
|
||||
luaopen_renderer_font(L);
|
||||
luaL_newmetatable(L, API_TYPE_FONT);
|
||||
luaL_setfuncs(L, fontLib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setfield(L, -2, "font");
|
||||
luaopen_renderer_replacements(L);
|
||||
lua_setfield(L, -2, "replacements");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include "api.h"
|
||||
#include "fontdesc.h"
|
||||
#include "renderer.h"
|
||||
#include "rencache.h"
|
||||
|
||||
static int f_load(lua_State *L) {
|
||||
const char *filename = luaL_checkstring(L, 1);
|
||||
float size = luaL_checknumber(L, 2);
|
||||
unsigned int font_options = 0;
|
||||
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
|
||||
lua_getfield(L, 3, "antialiasing");
|
||||
if (lua_isstring(L, -1)) {
|
||||
const char *antialiasing = lua_tostring(L, -1);
|
||||
if (antialiasing) {
|
||||
if (strcmp(antialiasing, "grayscale") == 0) {
|
||||
font_options |= RenFontGrayscale;
|
||||
} else if (strcmp(antialiasing, "subpixel") == 0) {
|
||||
font_options |= RenFontSubpixel;
|
||||
} else {
|
||||
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 3, "hinting");
|
||||
if (lua_isstring(L, -1)) {
|
||||
const char *hinting = lua_tostring(L, -1);
|
||||
if (hinting) {
|
||||
if (strcmp(hinting, "slight") == 0) {
|
||||
font_options |= RenFontHintingSlight;
|
||||
} else if (strcmp(hinting, "none") == 0) {
|
||||
font_options |= RenFontHintingNone;
|
||||
} else if (strcmp(hinting, "full") == 0) {
|
||||
font_options |= RenFontHintingFull;
|
||||
} else {
|
||||
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
if (ren_verify_font(filename)) {
|
||||
luaL_error(L, "failed to load font");
|
||||
}
|
||||
|
||||
FontDesc *font_desc = lua_newuserdata(L, font_desc_alloc_size(filename));
|
||||
font_desc_init(font_desc, filename, size, font_options);
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_copy(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
float size;
|
||||
if (lua_gettop(L) >= 2) {
|
||||
size = luaL_checknumber(L, 2);
|
||||
} else {
|
||||
size = self->size;
|
||||
}
|
||||
FontDesc *new_font_desc = lua_newuserdata(L, font_desc_alloc_size(self->filename));
|
||||
font_desc_init(new_font_desc, self->filename, size, self->options);
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_set_tab_size(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
int n = luaL_checknumber(L, 2);
|
||||
font_desc_set_tab_size(self, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_gc(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
font_desc_clear(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_get_width(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
const char *text = luaL_checkstring(L, 2);
|
||||
/* By calling ren_get_font_width with NULL as third arguments
|
||||
we will obtain the width in points. */
|
||||
int w = ren_get_font_width(self, text, NULL);
|
||||
lua_pushnumber(L, w);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_subpixel_scale(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
lua_pushnumber(L, ren_get_font_subpixel_scale(self));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_get_width_subpixel(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
const char *text = luaL_checkstring(L, 2);
|
||||
int subpixel_scale;
|
||||
/* We need to pass a non-null subpixel_scale pointer to force
|
||||
subpixel width calculation. */
|
||||
lua_pushnumber(L, ren_get_font_width(self, text, &subpixel_scale));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_get_height(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
lua_pushnumber(L, ren_get_font_height(self) );
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_get_size(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
lua_pushnumber(L, self->size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_set_size(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
float new_size = luaL_checknumber(L, 2);
|
||||
font_desc_clear(self);
|
||||
self->size = new_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg lib[] = {
|
||||
{ "__gc", f_gc },
|
||||
{ "load", f_load },
|
||||
{ "copy", f_copy },
|
||||
{ "set_tab_size", f_set_tab_size },
|
||||
{ "get_width", f_get_width },
|
||||
{ "get_width_subpixel", f_get_width_subpixel },
|
||||
{ "get_height", f_get_height },
|
||||
{ "subpixel_scale", f_subpixel_scale },
|
||||
{ "get_size", f_get_size },
|
||||
{ "set_size", f_set_size },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int luaopen_renderer_font(lua_State *L) {
|
||||
luaL_newmetatable(L, API_TYPE_FONT);
|
||||
luaL_setfuncs(L, lib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
return 1;
|
||||
}
|
167
src/api/system.c
167
src/api/system.c
|
@ -26,14 +26,11 @@ static const char* button_name(int button) {
|
|||
}
|
||||
|
||||
|
||||
static char* key_name(char *dst, int sym) {
|
||||
strcpy(dst, SDL_GetKeyName(sym));
|
||||
char *p = dst;
|
||||
static void str_tolower(char *p) {
|
||||
while (*p) {
|
||||
*p = tolower(*p);
|
||||
p++;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
struct HitTestInfo {
|
||||
|
@ -93,6 +90,23 @@ static SDL_HitTestResult SDLCALL hit_test(SDL_Window *window, const SDL_Point *p
|
|||
return SDL_HITTEST_NORMAL;
|
||||
}
|
||||
|
||||
static const char *numpad[] = { "end", "down", "pagedown", "left", "", "right", "home", "up", "pageup", "ins", "delete" };
|
||||
|
||||
static const char *get_key_name(const SDL_Event *e, char *buf) {
|
||||
SDL_Scancode scancode = e->key.keysym.scancode;
|
||||
/* Is the scancode from the keypad and the number-lock off?
|
||||
** We assume that SDL_SCANCODE_KP_1 up to SDL_SCANCODE_KP_9 and SDL_SCANCODE_KP_0
|
||||
** and SDL_SCANCODE_KP_PERIOD are declared in SDL2 in that order. */
|
||||
if (scancode >= SDL_SCANCODE_KP_1 && scancode <= SDL_SCANCODE_KP_1 + 10 &&
|
||||
!(e->key.keysym.mod & KMOD_NUM)) {
|
||||
return numpad[scancode - SDL_SCANCODE_KP_1];
|
||||
} else {
|
||||
strcpy(buf, SDL_GetKeyName(e->key.keysym.sym));
|
||||
str_tolower(buf);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
static int f_poll_event(lua_State *L) {
|
||||
char buf[16];
|
||||
int mx, my, wx, wy;
|
||||
|
@ -162,7 +176,7 @@ top:
|
|||
}
|
||||
#endif
|
||||
lua_pushstring(L, "keypressed");
|
||||
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
||||
lua_pushstring(L, get_key_name(&e, buf));
|
||||
return 2;
|
||||
|
||||
case SDL_KEYUP:
|
||||
|
@ -176,7 +190,7 @@ top:
|
|||
}
|
||||
#endif
|
||||
lua_pushstring(L, "keyreleased");
|
||||
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
||||
lua_pushstring(L, get_key_name(&e, buf));
|
||||
return 2;
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
|
@ -577,56 +591,32 @@ static int f_exec(lua_State *L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_fuzzy_match(lua_State *L) {
|
||||
size_t strLen, ptnLen;
|
||||
const char *str = luaL_checklstring(L, 1, &strLen);
|
||||
const char *ptn = luaL_checklstring(L, 2, &ptnLen);
|
||||
bool files = false;
|
||||
if (lua_gettop(L) > 2 && lua_isboolean(L,3))
|
||||
files = lua_toboolean(L, 3);
|
||||
|
||||
int score = 0;
|
||||
int run = 0;
|
||||
|
||||
// Match things *backwards*. This allows for better matching on filenames than the above
|
||||
// If true match things *backwards*. This allows for better matching on filenames than the above
|
||||
// function. For example, in the lite project, opening "renderer" has lib/font_render/build.sh
|
||||
// as the first result, rather than src/renderer.c. Clearly that's wrong.
|
||||
if (files) {
|
||||
const char* strEnd = str + strLen - 1;
|
||||
const char* ptnEnd = ptn + ptnLen - 1;
|
||||
while (strEnd >= str && ptnEnd >= ptn) {
|
||||
while (*strEnd == ' ') { strEnd--; }
|
||||
while (*ptnEnd == ' ') { ptnEnd--; }
|
||||
if (tolower(*strEnd) == tolower(*ptnEnd)) {
|
||||
score += run * 10 - (*strEnd != *ptnEnd);
|
||||
run++;
|
||||
ptnEnd--;
|
||||
} else {
|
||||
score -= 10;
|
||||
run = 0;
|
||||
}
|
||||
strEnd--;
|
||||
bool files = lua_gettop(L) > 2 && lua_isboolean(L,3) && lua_toboolean(L, 3);
|
||||
int score = 0, run = 0, increment = files ? -1 : 1;
|
||||
const char* strTarget = files ? str + strLen - 1 : str;
|
||||
const char* ptnTarget = files ? ptn + ptnLen - 1 : ptn;
|
||||
while (strTarget >= str && ptnTarget >= ptn && *strTarget && *ptnTarget) {
|
||||
while (strTarget >= str && *strTarget == ' ') { strTarget += increment; }
|
||||
while (ptnTarget >= ptn && *ptnTarget == ' ') { ptnTarget += increment; }
|
||||
if (tolower(*strTarget) == tolower(*ptnTarget)) {
|
||||
score += run * 10 - (*strTarget != *ptnTarget);
|
||||
run++;
|
||||
ptnTarget += increment;
|
||||
} else {
|
||||
score -= 10;
|
||||
run = 0;
|
||||
}
|
||||
if (ptnEnd >= ptn) { return 0; }
|
||||
} else {
|
||||
while (*str && *ptn) {
|
||||
while (*str == ' ') { str++; }
|
||||
while (*ptn == ' ') { ptn++; }
|
||||
if (tolower(*str) == tolower(*ptn)) {
|
||||
score += run * 10 - (*str != *ptn);
|
||||
run++;
|
||||
ptn++;
|
||||
} else {
|
||||
score -= 10;
|
||||
run = 0;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
if (*ptn) { return 0; }
|
||||
strTarget += increment;
|
||||
}
|
||||
|
||||
lua_pushnumber(L, score - (int)strLen);
|
||||
if (ptnTarget >= ptn && *ptnTarget) { return 0; }
|
||||
lua_pushnumber(L, score - (int)strLen * 10);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -637,6 +627,86 @@ static int f_set_window_opacity(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// Symbol table for native plugin loading. Allows for a statically
|
||||
// bound lua library to be used by native plugins.
|
||||
typedef struct {
|
||||
const char* symbol;
|
||||
void* address;
|
||||
} lua_function_node;
|
||||
|
||||
#define P(FUNC) { "lua_" #FUNC, (void*)(lua_##FUNC) }
|
||||
#define U(FUNC) { "luaL_" #FUNC, (void*)(luaL_##FUNC) }
|
||||
static void* api_require(const char* symbol) {
|
||||
static lua_function_node nodes[] = {
|
||||
P(absindex), P(arith), P(atpanic), P(callk), P(checkstack),
|
||||
P(close), P(compare), P(concat), P(copy), P(createtable), P(dump),
|
||||
P(error), P(gc), P(getallocf), P(getctx), P(getfield), P(getglobal),
|
||||
P(gethook), P(gethookcount), P(gethookmask), P(getinfo), P(getlocal),
|
||||
P(getmetatable), P(getstack), P(gettable), P(gettop), P(getupvalue),
|
||||
P(getuservalue), P(insert), P(isnumber), P(isstring), P(isuserdata),
|
||||
P(len), P(load), P(newstate), P(newthread), P(newuserdata), P(next),
|
||||
P(pcallk), P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger),
|
||||
P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber),
|
||||
P(pushstring), P(pushthread), P(pushunsigned), P(pushvalue),
|
||||
P(pushvfstring), P(rawequal), P(rawget), P(rawgeti), P(rawgetp), P(rawlen),
|
||||
P(rawset), P(rawseti), P(rawsetp), P(remove), P(replace), P(resume),
|
||||
P(setallocf), P(setfield), P(setglobal), P(sethook), P(setlocal),
|
||||
P(setmetatable), P(settable), P(settop), P(setupvalue), P(setuservalue),
|
||||
P(status), P(tocfunction), P(tointegerx), P(tolstring), P(toboolean),
|
||||
P(tonumberx), P(topointer), P(tothread), P(tounsignedx), P(touserdata),
|
||||
P(type), P(typename), P(upvalueid), P(upvaluejoin), P(version), P(xmove),
|
||||
P(yieldk), U(checkversion_), U(getmetafield), U(callmeta), U(tolstring),
|
||||
U(argerror), U(checknumber), U(optnumber), U(checkinteger),
|
||||
U(checkunsigned), U(checkstack), U(checktype), U(checkany),
|
||||
U(newmetatable), U(setmetatable), U(testudata), U(checkudata), U(where),
|
||||
U(error), U(fileresult), U(execresult), U(ref), U(unref), U(loadstring),
|
||||
U(newstate), U(len), U(setfuncs), U(getsubtable), U(buffinit),
|
||||
U(prepbuffsize), U(addlstring), U(addstring), U(addvalue), U(pushresult),
|
||||
U(pushresultsize), U(buffinitsize)
|
||||
};
|
||||
for (int i = 0; i < sizeof(nodes) / sizeof(lua_function_node); ++i) {
|
||||
if (strcmp(nodes[i].symbol, symbol) == 0)
|
||||
return nodes[i].address;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int f_load_native_plugin(lua_State *L) {
|
||||
char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0';
|
||||
int result;
|
||||
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
const char *path = luaL_checkstring(L, 2);
|
||||
void *library = SDL_LoadObject(path);
|
||||
if (!library)
|
||||
return luaL_error(L, "Unable to load %s: %s", name, SDL_GetError());
|
||||
|
||||
lua_getglobal(L, "package");
|
||||
lua_getfield(L, -1, "native_plugins");
|
||||
lua_pushlightuserdata(L, library);
|
||||
lua_setfield(L, -2, name);
|
||||
lua_pop(L, 1);
|
||||
|
||||
const char *basename = strrchr(name, '.');
|
||||
basename = !basename ? name : basename + 1;
|
||||
snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_lite_xl_%s", basename);
|
||||
int (*ext_entrypoint) (lua_State *L, void*) = SDL_LoadFunction(library, entrypoint_name);
|
||||
if (!ext_entrypoint) {
|
||||
snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_%s", basename);
|
||||
int (*entrypoint)(lua_State *L) = SDL_LoadFunction(library, entrypoint_name);
|
||||
if (!entrypoint)
|
||||
return luaL_error(L, "Unable to load %s: Can't find %s(lua_State *L, void *XL)", name, entrypoint_name);
|
||||
result = entrypoint(L);
|
||||
} else {
|
||||
result = ext_entrypoint(L, api_require);
|
||||
}
|
||||
|
||||
if (!result)
|
||||
return luaL_error(L, "Unable to load %s: entrypoint must return a value", name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg lib[] = {
|
||||
{ "poll_event", f_poll_event },
|
||||
|
@ -664,6 +734,7 @@ static const luaL_Reg lib[] = {
|
|||
{ "exec", f_exec },
|
||||
{ "fuzzy_match", f_fuzzy_match },
|
||||
{ "set_window_opacity", f_set_window_opacity },
|
||||
{ "load_native_plugin", f_load_native_plugin },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fontdesc.h"
|
||||
#include "renderer.h"
|
||||
|
||||
|
||||
int font_desc_alloc_size(const char *filename) {
|
||||
return offsetof(FontDesc, filename) + strlen(filename) + 1;
|
||||
}
|
||||
|
||||
void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options) {
|
||||
memcpy(font_desc->filename, filename, strlen(filename) + 1);
|
||||
font_desc->size = size;
|
||||
font_desc->options = font_options;
|
||||
font_desc->tab_size = 4;
|
||||
font_desc->cache_length = 0;
|
||||
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
|
||||
}
|
||||
|
||||
void font_desc_clear(FontDesc *font_desc) {
|
||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||
ren_free_font(font_desc->cache[i].font);
|
||||
}
|
||||
font_desc->cache_length = 0;
|
||||
font_desc->cache_last_index = 0;
|
||||
}
|
||||
|
||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
|
||||
font_desc->tab_size = tab_size;
|
||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||
ren_set_font_tab_size(font_desc->cache[i].font, tab_size);
|
||||
}
|
||||
}
|
||||
|
||||
int font_desc_get_tab_size(FontDesc *font_desc) {
|
||||
return font_desc->tab_size;
|
||||
}
|
||||
|
||||
static void load_scaled_font(FontDesc *font_desc, int index, int scale) {
|
||||
RenFont *font = ren_load_font(font_desc->filename, scale * font_desc->size, font_desc->options);
|
||||
if (!font) {
|
||||
/* The font was able to load when initially loaded using renderer.load.font.
|
||||
If now is no longer available we just abort the application. */
|
||||
fprintf(stderr, "Fatal error: unable to load font %s. Application will abort.\n",
|
||||
font_desc->filename);
|
||||
exit(1);
|
||||
}
|
||||
font_desc->cache[index].font = font;
|
||||
font_desc->cache[index].scale = scale;
|
||||
}
|
||||
|
||||
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale) {
|
||||
int index = -1;
|
||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||
if (font_desc->cache[i].scale == scale) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index < 0) {
|
||||
index = font_desc->cache_length;
|
||||
if (index < FONT_CACHE_ARRAY_MAX) {
|
||||
load_scaled_font(font_desc, index, scale);
|
||||
font_desc->cache_length = index + 1;
|
||||
} else {
|
||||
// FIXME: should not print into the stderr or stdout.
|
||||
fprintf(stderr, "Warning: max array of font scale reached.\n");
|
||||
index = (font_desc->cache_last_index == 0 ? 1 : 0);
|
||||
ren_free_font(font_desc->cache[index].font);
|
||||
load_scaled_font(font_desc, index, scale);
|
||||
}
|
||||
}
|
||||
font_desc->cache_last_index = index;
|
||||
return font_desc->cache[index].font;
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#ifndef FONT_DESC_H
|
||||
#define FONT_DESC_H
|
||||
|
||||
typedef struct RenFont RenFont;
|
||||
|
||||
struct FontInstance {
|
||||
RenFont *font;
|
||||
short int scale;
|
||||
};
|
||||
typedef struct FontInstance FontInstance;
|
||||
|
||||
#define FONT_CACHE_ARRAY_MAX 2
|
||||
|
||||
struct FontDesc {
|
||||
float size;
|
||||
unsigned int options;
|
||||
short int tab_size;
|
||||
FontInstance cache[FONT_CACHE_ARRAY_MAX];
|
||||
short int cache_length;
|
||||
short int cache_last_index; /* More recently used instance. */
|
||||
char filename[0];
|
||||
};
|
||||
typedef struct FontDesc FontDesc;
|
||||
|
||||
void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options);
|
||||
int font_desc_alloc_size(const char *filename);
|
||||
int font_desc_get_tab_size(FontDesc *font_desc);
|
||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
|
||||
void font_desc_clear(FontDesc *font_desc);
|
||||
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
|
||||
|
||||
#endif
|
||||
|
21
src/main.c
21
src/main.c
|
@ -18,13 +18,12 @@
|
|||
SDL_Window *window;
|
||||
|
||||
static double get_scale(void) {
|
||||
#ifdef __APPLE__
|
||||
return 1.0;
|
||||
#else
|
||||
float dpi = 96.0;
|
||||
SDL_GetDisplayDPI(0, NULL, &dpi, NULL);
|
||||
return dpi / 96.0;
|
||||
#ifndef __APPLE__
|
||||
float dpi;
|
||||
if (SDL_GetDisplayDPI(0, NULL, &dpi, NULL) == 0)
|
||||
return dpi / 96.0;
|
||||
#endif
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,8 +32,7 @@ static void get_exe_filename(char *buf, int sz) {
|
|||
int len = GetModuleFileName(NULL, buf, sz - 1);
|
||||
buf[len] = '\0';
|
||||
#elif __linux__
|
||||
char path[512];
|
||||
sprintf(path, "/proc/%d/exe", getpid());
|
||||
char path[] = "/proc/self/exe";
|
||||
int len = readlink(path, buf, sz - 1);
|
||||
buf[len] = '\0';
|
||||
#elif __APPLE__
|
||||
|
@ -52,7 +50,7 @@ static void get_exe_filename(char *buf, int sz) {
|
|||
|
||||
|
||||
static void init_window_icon(void) {
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#include "../resources/icons/icon.inl"
|
||||
(void) icon_rgba_len; /* unused */
|
||||
SDL_Surface *surf = SDL_CreateRGBSurfaceFrom(
|
||||
|
@ -140,8 +138,6 @@ init_lua:
|
|||
lua_setglobal(L, "EXEFILE");
|
||||
|
||||
#ifdef __APPLE__
|
||||
lua_pushboolean(L, true);
|
||||
lua_setglobal(L, "MACOS");
|
||||
enable_momentum_scroll();
|
||||
#ifdef MACOS_USE_BUNDLE
|
||||
set_macos_bundle_resources(L);
|
||||
|
@ -155,7 +151,7 @@ init_lua:
|
|||
" local exedir = EXEFILE:match('^(.*)" LITE_PATHSEP_PATTERN LITE_NONPATHSEP_PATTERN "$')\n"
|
||||
" local prefix = exedir:match('^(.*)" LITE_PATHSEP_PATTERN "bin$')\n"
|
||||
" dofile((MACOS_RESOURCES or (prefix and prefix .. '/share/lite-xl' or exedir .. '/data')) .. '/core/start.lua')\n"
|
||||
" core = require('core')\n"
|
||||
" core = require(os.getenv('LITE_XL_RUNTIME') or 'core')\n"
|
||||
" core.init()\n"
|
||||
" core.run()\n"
|
||||
"end, function(err)\n"
|
||||
|
@ -186,6 +182,7 @@ init_lua:
|
|||
lua_pcall(L, 0, 1, 0);
|
||||
if (lua_toboolean(L, -1)) {
|
||||
lua_close(L);
|
||||
rencache_invalidate();
|
||||
goto init_lua;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
lite_sources = [
|
||||
'api/api.c',
|
||||
'api/cp_replace.c',
|
||||
'api/renderer.c',
|
||||
'api/renderer_font.c',
|
||||
'api/regex.c',
|
||||
'api/system.c',
|
||||
'api/process.c',
|
||||
'renderer.c',
|
||||
'renwindow.c',
|
||||
'fontdesc.c',
|
||||
'rencache.c',
|
||||
'main.c',
|
||||
]
|
||||
|
@ -25,11 +22,10 @@ lite_include = include_directories('.')
|
|||
|
||||
executable('lite-xl',
|
||||
lite_sources + lite_rc,
|
||||
include_directories: [lite_include, font_renderer_include],
|
||||
include_directories: [lite_include],
|
||||
dependencies: lite_deps,
|
||||
c_args: lite_cargs,
|
||||
objc_args: lite_cargs,
|
||||
link_with: libfontrenderer,
|
||||
link_args: lite_link_args,
|
||||
install_dir: lite_bindir,
|
||||
install: true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdalign.h>
|
||||
|
||||
#include <lauxlib.h>
|
||||
#include "rencache.h"
|
||||
|
@ -16,32 +17,19 @@
|
|||
#define COMMAND_BUF_SIZE (1024 * 512)
|
||||
#define COMMAND_BARE_SIZE offsetof(Command, text)
|
||||
|
||||
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
|
||||
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT };
|
||||
|
||||
typedef struct {
|
||||
int8_t type;
|
||||
int8_t tab_size;
|
||||
int8_t subpixel_scale;
|
||||
int8_t x_subpixel_offset;
|
||||
int32_t size;
|
||||
RenRect rect;
|
||||
RenColor color;
|
||||
FontDesc *font_desc;
|
||||
CPReplaceTable *replacements;
|
||||
RenColor replace_color;
|
||||
RenFont *fonts[FONT_FALLBACK_MAX];
|
||||
float text_x;
|
||||
char text[0];
|
||||
} Command;
|
||||
|
||||
#define FONT_REFS_MAX 12
|
||||
struct FontRef {
|
||||
FontDesc *font_desc;
|
||||
int index;
|
||||
};
|
||||
typedef struct FontRef FontRef;
|
||||
FontRef font_refs[FONT_REFS_MAX];
|
||||
int font_refs_len = 0;
|
||||
|
||||
|
||||
static unsigned cells_buf1[CELLS_X * CELLS_Y];
|
||||
static unsigned cells_buf2[CELLS_X * CELLS_Y];
|
||||
static unsigned *cells_prev = cells_buf1;
|
||||
|
@ -52,36 +40,9 @@ static int command_buf_idx;
|
|||
static RenRect screen_rect;
|
||||
static bool show_debug;
|
||||
|
||||
|
||||
static inline int min(int a, int b) { return a < b ? a : b; }
|
||||
static inline int max(int a, int b) { return a > b ? a : b; }
|
||||
|
||||
static int font_refs_add(lua_State *L, FontDesc *font_desc, int index) {
|
||||
for (int i = 0; i < font_refs_len; i++) {
|
||||
if (font_refs[i].font_desc == font_desc) {
|
||||
return font_refs[i].index;
|
||||
}
|
||||
}
|
||||
|
||||
if (font_refs_len >= FONT_REFS_MAX) {
|
||||
fprintf(stderr, "Warning: (" __FILE__ "): exhausted font reference buffer\n");
|
||||
return LUA_NOREF;
|
||||
}
|
||||
|
||||
lua_pushvalue(L, index);
|
||||
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
font_refs[font_refs_len++] = (FontRef) { font_desc, ref };
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
static void font_refs_clear(lua_State *L) {
|
||||
for (int i = 0; i < font_refs_len; i++) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, font_refs[i].index);
|
||||
}
|
||||
font_refs_len = 0;
|
||||
}
|
||||
|
||||
/* 32bit fnv-1a hash */
|
||||
#define HASH_INITIAL 2166136261
|
||||
|
@ -124,6 +85,8 @@ static RenRect merge_rects(RenRect a, RenRect b) {
|
|||
|
||||
|
||||
static Command* push_command(int type, int size) {
|
||||
size_t alignment = alignof(max_align_t) - 1;
|
||||
size = (size + alignment) & ~alignment;
|
||||
Command *cmd = (Command*) (command_buf + command_buf_idx);
|
||||
int n = command_buf_idx + size;
|
||||
if (n > COMMAND_BUF_SIZE) {
|
||||
|
@ -168,35 +131,23 @@ void rencache_draw_rect(RenRect rect, RenColor color) {
|
|||
}
|
||||
}
|
||||
|
||||
int rencache_draw_text(lua_State *L, FontDesc *font_desc, int font_index,
|
||||
const char *text, int x, int y, RenColor color, bool draw_subpixel,
|
||||
CPReplaceTable *replacements, RenColor replace_color)
|
||||
float rencache_draw_text(lua_State *L, RenFont **fonts, const char *text, float x, int y, RenColor color)
|
||||
{
|
||||
int subpixel_scale;
|
||||
int w_subpixel = ren_get_font_width(font_desc, text, &subpixel_scale);
|
||||
RenRect rect;
|
||||
rect.x = (draw_subpixel ? ren_font_subpixel_round(x, subpixel_scale, -1) : x);
|
||||
rect.y = y;
|
||||
rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0);
|
||||
rect.height = ren_get_font_height(font_desc);
|
||||
|
||||
if (rects_overlap(screen_rect, rect) && font_refs_add(L, font_desc, font_index) >= 0) {
|
||||
float width = ren_font_group_get_width(fonts, text);
|
||||
RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) };
|
||||
if (rects_overlap(screen_rect, rect)) {
|
||||
int sz = strlen(text) + 1;
|
||||
Command *cmd = push_command(draw_subpixel ? DRAW_TEXT_SUBPIXEL : DRAW_TEXT, COMMAND_BARE_SIZE + sz);
|
||||
Command *cmd = push_command(DRAW_TEXT, COMMAND_BARE_SIZE + sz);
|
||||
if (cmd) {
|
||||
memcpy(cmd->text, text, sz);
|
||||
cmd->color = color;
|
||||
cmd->font_desc = font_desc;
|
||||
memcpy(cmd->fonts, fonts, sizeof(RenFont*)*FONT_FALLBACK_MAX);
|
||||
cmd->rect = rect;
|
||||
cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1);
|
||||
cmd->x_subpixel_offset = x - subpixel_scale * rect.x;
|
||||
cmd->tab_size = font_desc_get_tab_size(font_desc);
|
||||
cmd->replacements = replacements;
|
||||
cmd->replace_color = replace_color;
|
||||
cmd->text_x = x;
|
||||
cmd->tab_size = ren_font_group_get_tab_size(fonts);
|
||||
}
|
||||
}
|
||||
|
||||
return x + (draw_subpixel ? w_subpixel : rect.width);
|
||||
return x + width;
|
||||
}
|
||||
|
||||
|
||||
|
@ -214,7 +165,6 @@ void rencache_begin_frame(lua_State *L) {
|
|||
screen_rect.height = h;
|
||||
rencache_invalidate();
|
||||
}
|
||||
font_refs_clear(L);
|
||||
}
|
||||
|
||||
|
||||
|
@ -301,15 +251,8 @@ void rencache_end_frame(lua_State *L) {
|
|||
ren_draw_rect(cmd->rect, cmd->color);
|
||||
break;
|
||||
case DRAW_TEXT:
|
||||
font_desc_set_tab_size(cmd->font_desc, cmd->tab_size);
|
||||
ren_draw_text(cmd->font_desc, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color,
|
||||
cmd->replacements, cmd->replace_color);
|
||||
break;
|
||||
case DRAW_TEXT_SUBPIXEL:
|
||||
font_desc_set_tab_size(cmd->font_desc, cmd->tab_size);
|
||||
ren_draw_text_subpixel(cmd->font_desc, cmd->text,
|
||||
cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color,
|
||||
cmd->replacements, cmd->replace_color);
|
||||
ren_font_group_set_tab_size(cmd->fonts, cmd->tab_size);
|
||||
ren_draw_text(cmd->fonts, cmd->text, cmd->text_x, cmd->rect.y, cmd->color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
#include <lua.h>
|
||||
#include "renderer.h"
|
||||
|
||||
void rencache_show_debug(bool enable);
|
||||
void rencache_set_clip_rect(RenRect rect);
|
||||
void rencache_draw_rect(RenRect rect, RenColor color);
|
||||
int rencache_draw_text(lua_State *L, FontDesc *font_desc, int font_index, const char *text, int x, int y, RenColor color,
|
||||
bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color);
|
||||
void rencache_invalidate(void);
|
||||
void rencache_begin_frame(lua_State *L);
|
||||
void rencache_end_frame(lua_State *L);
|
||||
void rencache_show_debug(bool enable);
|
||||
void rencache_set_clip_rect(RenRect rect);
|
||||
void rencache_draw_rect(RenRect rect, RenColor color);
|
||||
float rencache_draw_text(lua_State *L, RenFont **font,
|
||||
const char *text, float x, int y, RenColor color);
|
||||
void rencache_invalidate(void);
|
||||
void rencache_begin_frame(lua_State *L);
|
||||
void rencache_end_frame(lua_State *L);
|
||||
|
||||
#endif
|
||||
|
|
632
src/renderer.c
632
src/renderer.c
|
@ -2,37 +2,20 @@
|
|||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include "font_renderer.h"
|
||||
#include <ft2build.h>
|
||||
#include <freetype/ftlcdfil.h>
|
||||
#include <freetype/ftoutln.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "renderer.h"
|
||||
#include "renwindow.h"
|
||||
|
||||
#define MAX_GLYPHSET 256
|
||||
#define REPLACEMENT_CHUNK_SIZE 8
|
||||
|
||||
struct RenImage {
|
||||
RenColor *pixels;
|
||||
int width, height;
|
||||
};
|
||||
|
||||
struct GlyphSet {
|
||||
FR_Bitmap *image;
|
||||
FR_Bitmap_Glyph_Metrics glyphs[256];
|
||||
};
|
||||
typedef struct GlyphSet GlyphSet;
|
||||
|
||||
/* The field "padding" below must be there just before GlyphSet *sets[MAX_GLYPHSET]
|
||||
because the field "sets" can be indexed and writted with an index -1. For this
|
||||
reason the "padding" field must be there but is never explicitly used. */
|
||||
struct RenFont {
|
||||
GlyphSet *padding;
|
||||
GlyphSet *sets[MAX_GLYPHSET];
|
||||
float size;
|
||||
int height;
|
||||
int space_advance;
|
||||
FR_Renderer *renderer;
|
||||
};
|
||||
#define MAX_LOADABLE_GLYPHSETS 1024
|
||||
#define SUBPIXEL_BITMAPS_CACHED 3
|
||||
|
||||
static RenWindow window_renderer = {0};
|
||||
static FT_Library library;
|
||||
|
||||
static void* check_alloc(void *ptr) {
|
||||
if (!ptr) {
|
||||
|
@ -42,6 +25,29 @@ static void* check_alloc(void *ptr) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
/************************* Fonts *************************/
|
||||
|
||||
typedef struct {
|
||||
unsigned short x0, x1, y0, y1, loaded;
|
||||
short bitmap_left, bitmap_top;
|
||||
float xadvance;
|
||||
} GlyphMetric;
|
||||
|
||||
typedef struct {
|
||||
SDL_Surface* surface;
|
||||
GlyphMetric metrics[MAX_GLYPHSET];
|
||||
} GlyphSet;
|
||||
|
||||
typedef struct RenFont {
|
||||
FT_Face face;
|
||||
GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS];
|
||||
float size, space_advance, tab_advance;
|
||||
short max_height;
|
||||
bool subpixel;
|
||||
ERenFontHinting hinting;
|
||||
unsigned char style;
|
||||
char path[0];
|
||||
} RenFont;
|
||||
|
||||
static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
|
||||
unsigned res, n;
|
||||
|
@ -59,43 +65,292 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
|
|||
return p + 1;
|
||||
}
|
||||
|
||||
|
||||
void ren_cp_replace_init(CPReplaceTable *rep_table) {
|
||||
rep_table->size = 0;
|
||||
rep_table->replacements = NULL;
|
||||
}
|
||||
|
||||
|
||||
void ren_cp_replace_free(CPReplaceTable *rep_table) {
|
||||
free(rep_table->replacements);
|
||||
}
|
||||
|
||||
|
||||
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst) {
|
||||
int table_size = rep_table->size;
|
||||
if (table_size % REPLACEMENT_CHUNK_SIZE == 0) {
|
||||
CPReplace *old_replacements = rep_table->replacements;
|
||||
const int new_size = (table_size / REPLACEMENT_CHUNK_SIZE + 1) * REPLACEMENT_CHUNK_SIZE;
|
||||
rep_table->replacements = malloc(new_size * sizeof(CPReplace));
|
||||
if (!rep_table->replacements) {
|
||||
rep_table->replacements = old_replacements;
|
||||
return;
|
||||
}
|
||||
memcpy(rep_table->replacements, old_replacements, table_size * sizeof(CPReplace));
|
||||
free(old_replacements);
|
||||
static int font_set_load_options(RenFont* font) {
|
||||
switch (font->hinting) {
|
||||
case FONT_HINTING_SLIGHT: return FT_LOAD_TARGET_LIGHT | FT_LOAD_FORCE_AUTOHINT;
|
||||
case FONT_HINTING_FULL: return FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT;
|
||||
case FONT_HINTING_NONE: return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING;
|
||||
}
|
||||
CPReplace *rep = &rep_table->replacements[table_size];
|
||||
utf8_to_codepoint(src, &rep->codepoint_src);
|
||||
utf8_to_codepoint(dst, &rep->codepoint_dst);
|
||||
rep_table->size = table_size + 1;
|
||||
return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING;
|
||||
}
|
||||
|
||||
static int font_set_render_options(RenFont* font) {
|
||||
if (font->subpixel) {
|
||||
unsigned char weights[] = { 0x10, 0x40, 0x70, 0x40, 0x10 } ;
|
||||
switch (font->hinting) {
|
||||
case FONT_HINTING_NONE: FT_Library_SetLcdFilter(library, FT_LCD_FILTER_NONE); break;
|
||||
case FONT_HINTING_SLIGHT:
|
||||
case FONT_HINTING_FULL: FT_Library_SetLcdFilterWeights(library, weights); break;
|
||||
}
|
||||
return FT_RENDER_MODE_LCD;
|
||||
} else {
|
||||
switch (font->hinting) {
|
||||
case FONT_HINTING_NONE: return FT_RENDER_MODE_NORMAL; break;
|
||||
case FONT_HINTING_SLIGHT: return FT_RENDER_MODE_LIGHT; break;
|
||||
case FONT_HINTING_FULL: return FT_RENDER_MODE_LIGHT; break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int font_set_style(FT_Outline* outline, int x_translation, unsigned char style) {
|
||||
FT_Outline_Translate(outline, x_translation, 0 );
|
||||
if (style & FONT_STYLE_BOLD)
|
||||
FT_Outline_EmboldenXY(outline, 1 << 5, 0);
|
||||
if (style & FONT_STYLE_ITALIC) {
|
||||
FT_Matrix matrix = { 1 << 16, 1 << 14, 0, 1 << 16 };
|
||||
FT_Outline_Transform(outline, &matrix);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void font_load_glyphset(RenFont* font, int idx) {
|
||||
unsigned int render_option = font_set_render_options(font), load_option = font_set_load_options(font);
|
||||
int bitmaps_cached = font->subpixel ? SUBPIXEL_BITMAPS_CACHED : 1;
|
||||
unsigned int byte_width = font->subpixel ? 3 : 1;
|
||||
for (int j = 0, pen_x = 0; j < bitmaps_cached; ++j) {
|
||||
GlyphSet* set = check_alloc(calloc(1, sizeof(GlyphSet)));
|
||||
font->sets[j][idx] = set;
|
||||
for (int i = 0; i < MAX_GLYPHSET; ++i) {
|
||||
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
|
||||
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option))
|
||||
continue;
|
||||
FT_GlyphSlot slot = font->face->glyph;
|
||||
int glyph_width = slot->bitmap.width / byte_width;
|
||||
set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, true, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f};
|
||||
pen_x += glyph_width;
|
||||
font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height;
|
||||
}
|
||||
if (pen_x == 0)
|
||||
continue;
|
||||
set->surface = check_alloc(SDL_CreateRGBSurface(0, pen_x, font->max_height, font->subpixel ? 24 : 8, 0, 0, 0, 0));
|
||||
unsigned char* pixels = set->surface->pixels;
|
||||
for (int i = 0; i < MAX_GLYPHSET; ++i) {
|
||||
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
|
||||
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option))
|
||||
continue;
|
||||
FT_GlyphSlot slot = font->face->glyph;
|
||||
font_set_style(&slot->outline, (64 / bitmaps_cached) * j, font->style);
|
||||
if (FT_Render_Glyph(slot, render_option))
|
||||
continue;
|
||||
for (int line = 0; line < slot->bitmap.rows; ++line) {
|
||||
int target_offset = set->surface->pitch * line + set->metrics[i].x0 * byte_width;
|
||||
int source_offset = line * slot->bitmap.pitch;
|
||||
memcpy(&pixels[target_offset], &slot->bitmap.buffer[source_offset], slot->bitmap.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GlyphSet* font_get_glyphset(RenFont* font, unsigned int codepoint, int subpixel_idx) {
|
||||
int idx = (codepoint >> 8) % MAX_LOADABLE_GLYPHSETS;
|
||||
if (!font->sets[font->subpixel ? subpixel_idx : 0][idx])
|
||||
font_load_glyphset(font, idx);
|
||||
return font->sets[font->subpixel ? subpixel_idx : 0][idx];
|
||||
}
|
||||
|
||||
static RenFont* font_group_get_glyph(GlyphSet** set, GlyphMetric** metric, RenFont** fonts, unsigned int codepoint, int bitmap_index) {
|
||||
if (bitmap_index < 0)
|
||||
bitmap_index += SUBPIXEL_BITMAPS_CACHED;
|
||||
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
|
||||
*set = font_get_glyphset(fonts[i], codepoint, bitmap_index);
|
||||
*metric = &(*set)->metrics[codepoint % 256];
|
||||
if ((*metric)->loaded || codepoint < 0xFF)
|
||||
return fonts[i];
|
||||
}
|
||||
if (!(*metric)->loaded && codepoint > 0xFF && codepoint != 0x25A1)
|
||||
return font_group_get_glyph(set, metric, fonts, 0x25A1, bitmap_index);
|
||||
return fonts[0];
|
||||
}
|
||||
|
||||
RenFont* ren_font_load(const char* path, float size, bool subpixel, unsigned char hinting, unsigned char style) {
|
||||
FT_Face face;
|
||||
if (FT_New_Face( library, path, 0, &face))
|
||||
return NULL;
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
if (FT_Set_Pixel_Sizes(face, 0, (int)(size*surface_scale)))
|
||||
goto failure;
|
||||
int len = strlen(path);
|
||||
RenFont* font = check_alloc(calloc(1, sizeof(RenFont) + len + 1));
|
||||
strcpy(font->path, path);
|
||||
font->face = face;
|
||||
font->size = size;
|
||||
font->subpixel = subpixel;
|
||||
font->hinting = hinting;
|
||||
font->style = style;
|
||||
font->space_advance = (int)font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
|
||||
font->tab_advance = font->space_advance * 2;
|
||||
return font;
|
||||
failure:
|
||||
FT_Done_Face(face);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RenFont* ren_font_copy(RenFont* font, float size) {
|
||||
return ren_font_load(font->path, size, font->subpixel, font->hinting, font->style);
|
||||
}
|
||||
|
||||
void ren_font_free(RenFont* font) {
|
||||
for (int i = 0; i < SUBPIXEL_BITMAPS_CACHED; ++i) {
|
||||
for (int j = 0; j < MAX_GLYPHSET; ++j) {
|
||||
if (font->sets[i][j]) {
|
||||
if (font->sets[i][j]->surface)
|
||||
SDL_FreeSurface(font->sets[i][j]->surface);
|
||||
free(font->sets[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
FT_Done_Face(font->face);
|
||||
free(font);
|
||||
}
|
||||
|
||||
void ren_font_group_set_tab_size(RenFont **fonts, int n) {
|
||||
for (int j = 0; j < FONT_FALLBACK_MAX && fonts[j]; ++j) {
|
||||
for (int i = 0; i < (fonts[j]->subpixel ? SUBPIXEL_BITMAPS_CACHED : 1); ++i)
|
||||
font_get_glyphset(fonts[j], '\t', i)->metrics['\t'].xadvance = fonts[j]->space_advance * n;
|
||||
}
|
||||
}
|
||||
|
||||
int ren_font_group_get_tab_size(RenFont **fonts) {
|
||||
return font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance / fonts[0]->space_advance;
|
||||
}
|
||||
|
||||
float ren_font_group_get_size(RenFont **fonts) {
|
||||
return fonts[0]->size;
|
||||
}
|
||||
int ren_font_group_get_height(RenFont **fonts) {
|
||||
return fonts[0]->size + 3;
|
||||
}
|
||||
|
||||
float ren_font_group_get_width(RenFont **fonts, const char *text) {
|
||||
float width = 0;
|
||||
const char* end = text + strlen(text);
|
||||
GlyphMetric* metric = NULL; GlyphSet* set = NULL;
|
||||
while (text < end) {
|
||||
unsigned int codepoint;
|
||||
text = utf8_to_codepoint(text, &codepoint);
|
||||
font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
|
||||
width += metric->xadvance ? metric->xadvance : fonts[0]->space_advance;
|
||||
}
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
return width / surface_scale;
|
||||
}
|
||||
|
||||
float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) {
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
const RenRect clip = window_renderer.clip;
|
||||
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
float pen_x = x * surface_scale;
|
||||
y *= surface_scale;
|
||||
int bytes_per_pixel = surface->format->BytesPerPixel;
|
||||
const char* end = text + strlen(text);
|
||||
unsigned char* destination_pixels = surface->pixels;
|
||||
int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height;
|
||||
|
||||
while (text < end) {
|
||||
unsigned int codepoint, r, g, b;
|
||||
text = utf8_to_codepoint(text, &codepoint);
|
||||
GlyphSet* set = NULL; GlyphMetric* metric = NULL;
|
||||
RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, (int)(fmod(pen_x, 1.0) * SUBPIXEL_BITMAPS_CACHED));
|
||||
int start_x = floor(pen_x) + metric->bitmap_left;
|
||||
int end_x = (metric->x1 - metric->x0) + start_x;
|
||||
int glyph_end = metric->x1, glyph_start = metric->x0;
|
||||
if (!metric->loaded && codepoint > 0xFF)
|
||||
ren_draw_rect((RenRect){ start_x + 1, y, font->space_advance - 1, ren_font_group_get_height(fonts) }, color);
|
||||
if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) {
|
||||
unsigned char* source_pixels = set->surface->pixels;
|
||||
for (int line = metric->y0; line < metric->y1; ++line) {
|
||||
int target_y = line + y - metric->y0 - metric->bitmap_top + font->size * surface_scale;
|
||||
if (target_y < clip.y)
|
||||
continue;
|
||||
if (target_y >= clip_end_y)
|
||||
break;
|
||||
if (start_x + (glyph_end - glyph_start) >= clip_end_x)
|
||||
glyph_end = glyph_start + (clip_end_x - start_x);
|
||||
else if (start_x < clip.x) {
|
||||
int offset = clip.x - start_x;
|
||||
start_x += offset;
|
||||
glyph_start += offset;
|
||||
}
|
||||
unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel];
|
||||
unsigned char* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->subpixel ? 3 : 1)];
|
||||
for (int x = glyph_start; x < glyph_end; ++x) {
|
||||
unsigned int destination_color = *destination_pixel;
|
||||
SDL_Color dst = { (destination_color & surface->format->Rmask) >> surface->format->Rshift, (destination_color & surface->format->Gmask) >> surface->format->Gshift, (destination_color & surface->format->Bmask) >> surface->format->Bshift, (destination_color & surface->format->Amask) >> surface->format->Ashift };
|
||||
SDL_Color src = { *(font->subpixel ? source_pixel++ : source_pixel), *(font->subpixel ? source_pixel++ : source_pixel), *source_pixel++ };
|
||||
r = (color.r * src.r * color.a + dst.r * (65025 - src.r * color.a) + 32767) / 65025;
|
||||
g = (color.g * src.g * color.a + dst.g * (65025 - src.g * color.a) + 32767) / 65025;
|
||||
b = (color.b * src.b * color.a + dst.b * (65025 - src.b * color.a) + 32767) / 65025;
|
||||
*destination_pixel++ = dst.a << surface->format->Ashift | r << surface->format->Rshift | g << surface->format->Gshift | b << surface->format->Bshift;
|
||||
}
|
||||
}
|
||||
}
|
||||
pen_x += metric->xadvance ? metric->xadvance : font->space_advance;
|
||||
}
|
||||
if (fonts[0]->style & FONT_STYLE_UNDERLINE)
|
||||
ren_draw_rect((RenRect){ x, y / surface_scale + ren_font_group_get_height(fonts) - 1, (pen_x - x) / surface_scale, 1 }, color);
|
||||
return pen_x / surface_scale;
|
||||
}
|
||||
|
||||
/******************* Rectangles **********************/
|
||||
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
|
||||
int ia = 0xff - src.a;
|
||||
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
|
||||
dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8;
|
||||
dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8;
|
||||
return dst;
|
||||
}
|
||||
|
||||
void ren_draw_rect(RenRect rect, RenColor color) {
|
||||
if (color.a == 0) { return; }
|
||||
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
|
||||
/* transforms coordinates in pixels. */
|
||||
rect.x *= surface_scale;
|
||||
rect.y *= surface_scale;
|
||||
rect.width *= surface_scale;
|
||||
rect.height *= surface_scale;
|
||||
|
||||
const RenRect clip = window_renderer.clip;
|
||||
int x1 = rect.x < clip.x ? clip.x : rect.x;
|
||||
int y1 = rect.y < clip.y ? clip.y : rect.y;
|
||||
int x2 = rect.x + rect.width;
|
||||
int y2 = rect.y + rect.height;
|
||||
x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2;
|
||||
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
|
||||
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
RenColor *d = (RenColor*) surface->pixels;
|
||||
d += x1 + y1 * surface->w;
|
||||
int dr = surface->w - (x2 - x1);
|
||||
unsigned int translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
|
||||
if (color.a == 0xff) {
|
||||
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
|
||||
SDL_FillRect(surface, &rect, translated);
|
||||
} else {
|
||||
RenColor translated_color = (RenColor){ translated & 0xFF, (translated >> 8) & 0xFF, (translated >> 16) & 0xFF, color.a };
|
||||
for (int j = y1; j < y2; j++) {
|
||||
for (int i = x1; i < x2; i++, d++)
|
||||
*d = blend_pixel(*d, translated_color);
|
||||
d += dr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************** Window Management ****************/
|
||||
void ren_free_window_resources() {
|
||||
renwin_free(&window_renderer);
|
||||
}
|
||||
|
||||
void ren_init(SDL_Window *win) {
|
||||
assert(win);
|
||||
int error = FT_Init_FreeType( &library );
|
||||
if ( error ) {
|
||||
fprintf(stderr, "internal font error when starting the application\n");
|
||||
return;
|
||||
}
|
||||
window_renderer.window = win;
|
||||
renwin_init_surface(&window_renderer);
|
||||
renwin_clip_to_surface(&window_renderer);
|
||||
|
@ -130,274 +385,3 @@ void ren_get_size(int *x, int *y) {
|
|||
*y = surface->h / scale;
|
||||
}
|
||||
|
||||
|
||||
RenImage* ren_new_image(int width, int height) {
|
||||
assert(width > 0 && height > 0);
|
||||
RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor));
|
||||
check_alloc(image);
|
||||
image->pixels = (void*) (image + 1);
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
return image;
|
||||
}
|
||||
|
||||
void ren_free_image(RenImage *image) {
|
||||
free(image);
|
||||
}
|
||||
|
||||
static GlyphSet* load_glyphset(RenFont *font, int idx) {
|
||||
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
|
||||
|
||||
set->image = FR_Bake_Font_Bitmap(font->renderer, font->height, idx << 8, 256, set->glyphs);
|
||||
check_alloc(set->image);
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
static GlyphSet* get_glyphset(RenFont *font, int codepoint) {
|
||||
int idx = (codepoint >> 8) % MAX_GLYPHSET;
|
||||
if (!font->sets[idx]) {
|
||||
font->sets[idx] = load_glyphset(font, idx);
|
||||
}
|
||||
return font->sets[idx];
|
||||
}
|
||||
|
||||
|
||||
int ren_verify_font(const char *filename) {
|
||||
RenFont font[1];
|
||||
font->renderer = FR_Renderer_New(0);
|
||||
if (FR_Load_Font(font->renderer, filename)) {
|
||||
return 1;
|
||||
}
|
||||
FR_Renderer_Free(font->renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags) {
|
||||
RenFont *font = NULL;
|
||||
|
||||
/* init font */
|
||||
font = check_alloc(calloc(1, sizeof(RenFont)));
|
||||
font->size = size;
|
||||
|
||||
unsigned int fr_renderer_flags = 0;
|
||||
if ((renderer_flags & RenFontAntialiasingMask) == RenFontSubpixel) {
|
||||
fr_renderer_flags |= FR_SUBPIXEL;
|
||||
}
|
||||
if ((renderer_flags & RenFontHintingMask) == RenFontHintingSlight) {
|
||||
fr_renderer_flags |= (FR_HINTING | FR_PRESCALE_X);
|
||||
} else if ((renderer_flags & RenFontHintingMask) == RenFontHintingFull) {
|
||||
fr_renderer_flags |= FR_HINTING;
|
||||
}
|
||||
font->renderer = FR_Renderer_New(fr_renderer_flags);
|
||||
if (FR_Load_Font(font->renderer, filename)) {
|
||||
free(font);
|
||||
return NULL;
|
||||
}
|
||||
font->height = FR_Get_Font_Height(font->renderer, size);
|
||||
|
||||
FR_Bitmap_Glyph_Metrics *gs = get_glyphset(font, ' ')->glyphs;
|
||||
font->space_advance = gs[' '].xadvance;
|
||||
|
||||
/* make tab and newline glyphs invisible */
|
||||
FR_Bitmap_Glyph_Metrics *g = get_glyphset(font, '\n')->glyphs;
|
||||
g['\t'].x1 = g['\t'].x0;
|
||||
g['\n'].x1 = g['\n'].x0;
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
|
||||
void ren_free_font(RenFont *font) {
|
||||
for (int i = 0; i < MAX_GLYPHSET; i++) {
|
||||
GlyphSet *set = font->sets[i];
|
||||
if (set) {
|
||||
FR_Bitmap_Free(set->image);
|
||||
free(set);
|
||||
}
|
||||
}
|
||||
FR_Renderer_Free(font->renderer);
|
||||
free(font);
|
||||
}
|
||||
|
||||
|
||||
void ren_set_font_tab_size(RenFont *font, int n) {
|
||||
GlyphSet *set = get_glyphset(font, '\t');
|
||||
set->glyphs['\t'].xadvance = font->space_advance * n;
|
||||
}
|
||||
|
||||
|
||||
int ren_get_font_tab_size(RenFont *font) {
|
||||
GlyphSet *set = get_glyphset(font, '\t');
|
||||
return set->glyphs['\t'].xadvance / font->space_advance;
|
||||
}
|
||||
|
||||
|
||||
/* Important: if subpixel_scale is NULL we will return width in points. Otherwise we will
|
||||
return width in subpixels. */
|
||||
int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale) {
|
||||
int x = 0;
|
||||
const char *p = text;
|
||||
unsigned codepoint;
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
|
||||
while (*p) {
|
||||
p = utf8_to_codepoint(p, &codepoint);
|
||||
GlyphSet *set = get_glyphset(font, codepoint);
|
||||
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
|
||||
x += g->xadvance;
|
||||
}
|
||||
/* At this point here x is in subpixel units */
|
||||
const int x_scale_to_points = FR_Subpixel_Scale(font->renderer) * surface_scale;
|
||||
if (subpixel_scale) {
|
||||
*subpixel_scale = x_scale_to_points;
|
||||
return x;
|
||||
}
|
||||
return (x + x_scale_to_points / 2) / x_scale_to_points;
|
||||
}
|
||||
|
||||
|
||||
int ren_get_font_height(FontDesc *font_desc) {
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
|
||||
return (font->height + surface_scale / 2) / surface_scale;
|
||||
}
|
||||
|
||||
|
||||
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
|
||||
int ia = 0xff - src.a;
|
||||
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
|
||||
dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8;
|
||||
dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8;
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
#define rect_draw_loop(expr) \
|
||||
for (int j = y1; j < y2; j++) { \
|
||||
for (int i = x1; i < x2; i++) { \
|
||||
*d = expr; \
|
||||
d++; \
|
||||
} \
|
||||
d += dr; \
|
||||
}
|
||||
|
||||
void ren_draw_rect(RenRect rect, RenColor color) {
|
||||
if (color.a == 0) { return; }
|
||||
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
|
||||
/* transforms coordinates in pixels. */
|
||||
rect.x *= surface_scale;
|
||||
rect.y *= surface_scale;
|
||||
rect.width *= surface_scale;
|
||||
rect.height *= surface_scale;
|
||||
|
||||
const RenRect clip = window_renderer.clip;
|
||||
int x1 = rect.x < clip.x ? clip.x : rect.x;
|
||||
int y1 = rect.y < clip.y ? clip.y : rect.y;
|
||||
int x2 = rect.x + rect.width;
|
||||
int y2 = rect.y + rect.height;
|
||||
x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2;
|
||||
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
|
||||
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
RenColor *d = (RenColor*) surface->pixels;
|
||||
d += x1 + y1 * surface->w;
|
||||
int dr = surface->w - (x2 - x1);
|
||||
|
||||
if (color.a == 0xff) {
|
||||
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
|
||||
SDL_FillRect(surface, &rect, SDL_MapRGBA(surface->format, color.r, color.g, color.b, color.a));
|
||||
} else {
|
||||
rect_draw_loop(blend_pixel(*d, color));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) {
|
||||
for (int i = 0; i < rep_table->size; i++) {
|
||||
const CPReplace *rep = &rep_table->replacements[i];
|
||||
if (*codepoint == rep->codepoint_src) {
|
||||
*codepoint = rep->codepoint_dst;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static FR_Clip_Area clip_area_from_rect(const RenRect r) {
|
||||
return (FR_Clip_Area) {r.x, r.y, r.x + r.width, r.y + r.height};
|
||||
}
|
||||
|
||||
|
||||
static void draw_text_impl(RenFont *font, const char *text, int x_subpixel, int y_pixel, RenColor color,
|
||||
CPReplaceTable *replacements, RenColor replace_color)
|
||||
{
|
||||
SDL_Surface *surf = renwin_get_surface(&window_renderer);
|
||||
FR_Clip_Area clip = clip_area_from_rect(window_renderer.clip);
|
||||
const char *p = text;
|
||||
unsigned codepoint;
|
||||
const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b };
|
||||
while (*p) {
|
||||
FR_Color color_rep;
|
||||
p = utf8_to_codepoint(p, &codepoint);
|
||||
GlyphSet *set = get_glyphset(font, codepoint);
|
||||
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
|
||||
const int xadvance_original_cp = g->xadvance;
|
||||
const int replaced = replacements ? codepoint_replace(replacements, &codepoint) : 0;
|
||||
if (replaced) {
|
||||
set = get_glyphset(font, codepoint);
|
||||
g = &set->glyphs[codepoint & 0xff];
|
||||
color_rep = (FR_Color) { .r = replace_color.r, .g = replace_color.g, .b = replace_color.b};
|
||||
} else {
|
||||
color_rep = color_fr;
|
||||
}
|
||||
if (color.a != 0) {
|
||||
FR_Blend_Glyph(font->renderer, &clip,
|
||||
x_subpixel, y_pixel, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep);
|
||||
}
|
||||
x_subpixel += xadvance_original_cp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, int y, RenColor color,
|
||||
CPReplaceTable *replacements, RenColor replace_color)
|
||||
{
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
|
||||
draw_text_impl(font, text, x_subpixel, surface_scale * y, color, replacements, replace_color);
|
||||
}
|
||||
|
||||
void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color,
|
||||
CPReplaceTable *replacements, RenColor replace_color)
|
||||
{
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
|
||||
const int subpixel_scale = surface_scale * FR_Subpixel_Scale(font->renderer);
|
||||
draw_text_impl(font, text, subpixel_scale * x, surface_scale * y, color, replacements, replace_color);
|
||||
}
|
||||
|
||||
// Could be declared as static inline
|
||||
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) {
|
||||
int w_mult;
|
||||
if (orientation < 0) {
|
||||
w_mult = width;
|
||||
} else if (orientation == 0) {
|
||||
w_mult = width + subpixel_scale / 2;
|
||||
} else {
|
||||
w_mult = width + subpixel_scale - 1;
|
||||
}
|
||||
return w_mult / subpixel_scale;
|
||||
}
|
||||
|
||||
|
||||
int ren_get_font_subpixel_scale(FontDesc *font_desc) {
|
||||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
|
||||
return FR_Subpixel_Scale(font->renderer) * surface_scale;
|
||||
}
|
||||
|
|
|
@ -3,37 +3,26 @@
|
|||
|
||||
#include <SDL.h>
|
||||
#include <stdint.h>
|
||||
#include "fontdesc.h"
|
||||
|
||||
typedef struct RenImage RenImage;
|
||||
|
||||
enum {
|
||||
RenFontAntialiasingMask = 1,
|
||||
RenFontGrayscale = 1,
|
||||
RenFontSubpixel = 0,
|
||||
|
||||
RenFontHintingMask = 3 << 1,
|
||||
RenFontHintingSlight = 0 << 1,
|
||||
RenFontHintingNone = 1 << 1,
|
||||
RenFontHintingFull = 2 << 1,
|
||||
};
|
||||
#include <stdbool.h>
|
||||
|
||||
#define FONT_FALLBACK_MAX 4
|
||||
typedef struct RenFont RenFont;
|
||||
typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting;
|
||||
typedef enum { FONT_STYLE_BOLD = 1, FONT_STYLE_ITALIC = 2, FONT_STYLE_UNDERLINE = 4 } ERenFontStyle;
|
||||
typedef struct { uint8_t b, g, r, a; } RenColor;
|
||||
typedef struct { int x, y, width, height; } RenRect;
|
||||
|
||||
struct CPReplace {
|
||||
unsigned codepoint_src;
|
||||
unsigned codepoint_dst;
|
||||
};
|
||||
typedef struct CPReplace CPReplace;
|
||||
|
||||
|
||||
struct CPReplaceTable {
|
||||
int size;
|
||||
CPReplace *replacements;
|
||||
};
|
||||
typedef struct CPReplaceTable CPReplaceTable;
|
||||
RenFont* ren_font_load(const char *filename, float size, bool subpixel, unsigned char hinting, unsigned char style);
|
||||
RenFont* ren_font_copy(RenFont* font, float size);
|
||||
void ren_font_free(RenFont *font);
|
||||
int ren_font_group_get_tab_size(RenFont **font);
|
||||
int ren_font_group_get_height(RenFont **font);
|
||||
float ren_font_group_get_size(RenFont **font);
|
||||
void ren_font_group_set_tab_size(RenFont **font, int n);
|
||||
float ren_font_group_get_width(RenFont **font, const char *text);
|
||||
float ren_draw_text(RenFont **font, const char *text, float x, int y, RenColor color);
|
||||
|
||||
void ren_draw_rect(RenRect rect, RenColor color);
|
||||
|
||||
void ren_init(SDL_Window *win);
|
||||
void ren_resize_window();
|
||||
|
@ -42,27 +31,5 @@ void ren_set_clip_rect(RenRect rect);
|
|||
void ren_get_size(int *x, int *y); /* Reports the size in points. */
|
||||
void ren_free_window_resources();
|
||||
|
||||
RenImage* ren_new_image(int width, int height);
|
||||
void ren_free_image(RenImage *image);
|
||||
|
||||
RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags);
|
||||
int ren_verify_font(const char *filename);
|
||||
void ren_free_font(RenFont *font);
|
||||
void ren_set_font_tab_size(RenFont *font, int n);
|
||||
int ren_get_font_tab_size(RenFont *font);
|
||||
|
||||
int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale);
|
||||
int ren_get_font_height(FontDesc *font_desc);
|
||||
int ren_get_font_subpixel_scale(FontDesc *font_desc);
|
||||
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation);
|
||||
|
||||
void ren_draw_rect(RenRect rect, RenColor color);
|
||||
void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
|
||||
void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
|
||||
|
||||
void ren_cp_replace_init(CPReplaceTable *rep_table);
|
||||
void ren_cp_replace_free(CPReplaceTable *rep_table);
|
||||
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst);
|
||||
void ren_cp_replace_clear(CPReplaceTable *rep_table);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[wrap-git]
|
||||
directory = libagg
|
||||
url = https://github.com/franko/agg
|
||||
revision = v2.4-lhelper4
|
Loading…
Reference in New Issue