6 Commits
23 changed files with 30770 additions and 7923 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].2"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source: With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].4"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped. 1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`. 2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
+22 -2
View File
@@ -36,6 +36,11 @@ jobs:
python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only) python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only)
python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo) python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo)
python3 tools/bundle.py --target love --no-demo dist/love-nodemo python3 tools/bundle.py --target love --no-demo dist/love-nodemo
# minified variants (comments/whitespace stripped, same code): tools/minify.py
python3 tools/minify.py dist/roblox/ImGui.luau dist/ImGui.min.luau
python3 tools/minify.py dist/roblox-nodemo/ImGui.luau dist/ImGui-nodemo.min.luau
python3 tools/minify.py dist/love/imgui_love.lua dist/imgui_love.min.lua
python3 tools/minify.py dist/love-nodemo/imgui_love.lua dist/imgui_love-nodemo.min.lua
- name: Check - name: Check
run: | run: |
@@ -45,12 +50,25 @@ jobs:
sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau
luajit -bl dist/love/imgui_love.lua > /dev/null luajit -bl dist/love/imgui_love.lua > /dev/null
luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null
python3 tools/minify.py --test
luau-compile --null dist/ImGui.min.luau
luau-compile --null dist/ImGui-nodemo.min.luau
luajit -bl dist/imgui_love.min.lua > /dev/null
luajit -bl dist/imgui_love-nodemo.min.lua > /dev/null
python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
grep -q "README OK" /tmp/r.txt grep -q "README OK" /tmp/r.txt
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
grep -q "CACHED OK" /tmp/c.txt grep -q "CACHED OK" /tmp/c.txt
MINIFY=1 python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/rm.txt
grep -q "README OK" /tmp/rm.txt
python3 tools/test/run.py --main tools/test/scripts/t_fixes.luau --frames 25 --quiet --roblox --timeout 300 | tee /tmp/f.txt
grep -q "FIXES OK" /tmp/f.txt
python3 tools/bundle.py --target love >/dev/null python3 tools/bundle.py --target love >/dev/null
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/h.txt
cp build/imgui_love.lua /tmp/imgui_love.lua && cp dist/imgui_love.min.lua build/imgui_love.lua
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/hm.txt
cp /tmp/imgui_love.lua build/imgui_love.lua
diff /tmp/h.txt /tmp/hm.txt # minified LÖVE bundle draws exactly the same
luajit tools/test/love/t_examples.lua luajit tools/test/love/t_examples.lua
- name: Package - name: Package
@@ -80,6 +98,8 @@ jobs:
dist/ImGui-nodemo.luau dist/ImGui-nodemo.luau
dist/imgui_love.lua dist/imgui_love.lua
dist/imgui_love-nodemo.lua dist/imgui_love-nodemo.lua
dist/*.min.luau
dist/*.min.lua
dist/*.zip dist/*.zip
generate_release_notes: true generate_release_notes: true
@@ -105,4 +125,4 @@ jobs:
gh release delete latest --yes --cleanup-tag || true gh release delete latest --yes --cleanup-tag || true
gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \ gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \
--notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \ --notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \
dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.zip dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.min.luau dist/*.min.lua dist/*.zip
+1 -1
View File
@@ -113,7 +113,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].2"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source: With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].4"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped. 1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`. 2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
+13 -1
View File
@@ -862,7 +862,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
if rate > 0 then if rate > 0 then
local now = os.clock() local now = os.clock()
if now + 0.002 < bd.NextRender then table.clear(bd.Overlays); PE(); return end -- Skipped: kept pending, so if no new frame comes (the app stopped rendering) Heartbeat still draws it
if now + 0.002 < bd.NextRender then bd.Pending = draw_data; PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now) bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end end
@@ -1129,6 +1130,16 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
bd.PendingChars = {} bd.PendingChars = {}
-- Render rate cap trailing edge: the last frame submitted is always drawn, even if it was skipped and the app
-- then stopped calling NewFrame (e.g. it rendered one empty frame to hide its windows)
connect(game:GetService("RunService").Heartbeat, function()
local d = bd.Pending
if d and os.clock() + 0.002 >= bd.NextRender then
bd.Pending = nil
ImGui_ImplRoblox_RenderDrawData(d)
end
end)
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end) connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end) connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
end end
@@ -1188,6 +1199,7 @@ function ImGui_ImplRoblox_NewFrame()
PB("ImGui NewFrame (backend)") PB("ImGui NewFrame (backend)")
local io = ImGui.GetIO() local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset)
local screen = bd.Gui.AbsoluteSize local screen = bd.Gui.AbsoluteSize
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale) local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
+1 -1
View File
@@ -1 +1 @@
[[12, "imgui.lua"], [18510, "imgui_demo.lua"], [25795, "imgui_draw.lua"], [31033, "imgui_h.lua"], [33231, "imgui_impl_roblox.lua"], [34571, "imgui_impl_roblox_fonts.lua"], [40974, "imgui_internal.lua"], [44150, "imgui_tables.lua"], [48497, "imgui_widgets.lua"], [58252, "imstb_rectpack.lua"], [58698, "imstb_textedit.lua"], [59939, "imstb_truetype.lua"], [62407, "imstd_minstdio.lua"]] [[12, "imgui.lua"], [21288, "imgui_demo.lua"], [29706, "imgui_draw.lua"], [35476, "imgui_h.lua"], [38142, "imgui_impl_roblox.lua"], [39494, "imgui_impl_roblox_fonts.lua"], [45897, "imgui_internal.lua"], [49343, "imgui_tables.lua"], [54338, "imgui_widgets.lua"], [64948, "imstb_rectpack.lua"], [65612, "imstb_textedit.lua"], [67154, "imstb_truetype.lua"], [69942, "imstd_minstdio.lua"]]
+10157 -2622
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
[[149, "imgui.lua"], [18647, "imgui_demo.lua"], [25932, "imgui_draw.lua"], [31170, "imgui_h.lua"], [33368, "imgui_impl_love.lua"], [33908, "imgui_internal.lua"], [37084, "imgui_tables.lua"], [41431, "imgui_widgets.lua"], [51186, "imstb_rectpack.lua"], [51632, "imstb_textedit.lua"], [52873, "imstb_truetype.lua"], [55341, "imstd_minstdio.lua"]] [[149, "imgui.lua"], [21425, "imgui_demo.lua"], [29843, "imgui_draw.lua"], [35613, "imgui_h.lua"], [38279, "imgui_impl_love.lua"], [38819, "imgui_internal.lua"], [42265, "imgui_tables.lua"], [47260, "imgui_widgets.lua"], [57870, "imstb_rectpack.lua"], [58534, "imstb_textedit.lua"], [60076, "imstb_truetype.lua"], [62864, "imstd_minstdio.lua"]]
+10144 -2621
View File
File diff suppressed because it is too large Load Diff
+3320 -542
View File
File diff suppressed because it is too large Load Diff
+1314 -181
View File
File diff suppressed because it is too large Load Diff
+632 -100
View File
File diff suppressed because it is too large Load Diff
+1169 -701
View File
File diff suppressed because it is too large Load Diff
+884 -614
View File
File diff suppressed because it is too large Load Diff
+732 -84
View File
File diff suppressed because it is too large Load Diff
+1154 -299
View File
File diff suppressed because it is too large Load Diff
+229 -11
View File
@@ -4,6 +4,63 @@
-- ALL TABLES IN THIS FILE ARE 1-BASED! -- ALL TABLES IN THIS FILE ARE 1-BASED!
-- Avoids table creation when possible -- Avoids table creation when possible
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_rect_pack.h 1.01.
-- Grep for [DEAR IMGUI] to find the changes.
--
-- stb_rect_pack.h - v1.01 - public domain - rectangle packing
-- Sean Barrett 2014
--
-- Useful for e.g. packing rectangular textures into an atlas.
-- Does not do rotation.
--
-- Not necessarily the awesomest packing method, but better than
-- the totally naive one in stb_truetype (which is primarily what
-- this is meant to replace).
--
-- Has only had a few tests run, may have issues.
--
-- More docs to come.
--
-- Uses table.sort and assert (STBRP_SORT and STBRP_ASSERT below).
--
-- This library currently uses the Skyline Bottom-Left algorithm.
--
-- Please note: better rectangle packers are welcome! Please
-- implement them to the same API, but with a different init
-- function.
--
-- Credits
--
-- Library
-- Sean Barrett
-- Minor features
-- Martins Mozeiko
-- github:IntellectualKitty
--
-- Bugfixes / warning fixes
-- Jeremy Jaussaud
-- Fabian Giesen
--
-- Version history:
--
-- 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
-- 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
-- 0.99 (2019-02-07) warning fixes
-- 0.11 (2017-03-03) return packing success/fail result
-- 0.10 (2016-10-25) remove cast-away-const to avoid warnings
-- 0.09 (2016-08-27) fix compiler warnings
-- 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
-- 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
-- 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
-- 0.05: added STBRP_ASSERT to allow replacing assert
-- 0.04: fixed minor bug in STBRP_LARGE_RECTS support
-- 0.01: initial release
--
-- LICENSE
--
-- See end of file for license information.
--- The `prev_link` can be stbrp_context or stbrp_node --- The `prev_link` can be stbrp_context or stbrp_node
--- @diagnostic disable --- @diagnostic disable
@@ -13,7 +70,13 @@ local STBRP_SORT = table.sort
local STBRP_ASSERT = assert local STBRP_ASSERT = assert
local STBRP__NOTUSED = function(_) end local STBRP__NOTUSED = function(_) end
-------------------------------------------------------------------------------
--
-- INCLUDE SECTION
--
local STBRP__MAXVAL = 0x7fffffff local STBRP__MAXVAL = 0x7fffffff
-- Mostly for internal use, but this is the maximum supported coordinate value.
--- @enum STBRP_HEURISTIC_Skyline --- @enum STBRP_HEURISTIC_Skyline
local STBRP_HEURISTIC_Skyline = { local STBRP_HEURISTIC_Skyline = {
@@ -44,12 +107,17 @@ local STBRP__PREV_LINK_TYPE = {
--- @nodiscard --- @nodiscard
local function stbrp_rect() local function stbrp_rect()
return { return {
-- reserved for your use:
id = 0, id = 0,
-- input:
w = 0, w = 0,
h = 0, h = 0,
-- output:
x = 0, x = 0,
y = 0, y = 0,
was_packed = 0 was_packed = 0 -- non-zero if valid packing
} }
end end
@@ -92,7 +160,7 @@ local function stbrp_context()
active_head = nil, active_head = nil,
free_head = nil, free_head = nil,
extra = {stbrp_node(), stbrp_node()} extra = {stbrp_node(), stbrp_node()} -- we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
} }
end end
@@ -113,13 +181,14 @@ local function stbrp__findresult()
} }
end end
-------------------------- -------------------------------------------------------------------------------
-------------------------- --
--- -- IMPLEMENTATION SECTION
--- IMPLEMENTATION SECTION --
---
---
-- Optionally select which packing heuristic the library should use. Different
-- heuristics will produce better/worse results for different data sets.
-- If you call init again, this will be reset to the default.
--- @param context stbrp_context --- @param context stbrp_context
--- @param heuristic int --- @param heuristic int
local function stbrp_setup_heuristic(context, heuristic) local function stbrp_setup_heuristic(context, heuristic)
@@ -131,16 +200,49 @@ local function stbrp_setup_heuristic(context, heuristic)
end end
end end
-- Optionally call this function after init but before doing any packing to
-- change the handling of the out-of-temp-memory scenario, described above.
-- If you call init again, this will be reset to the default (false).
--- @param context stbrp_context --- @param context stbrp_context
--- @param allow_out_of_mem boolean --- @param allow_out_of_mem boolean
local function stbrp_setup_allow_out_of_mem(context, allow_out_of_mem) local function stbrp_setup_allow_out_of_mem(context, allow_out_of_mem)
if allow_out_of_mem then if allow_out_of_mem then
-- if it's ok to run out of memory, then don't bother aligning them;
-- this gives better packing, but may fail due to OOM (even though
-- the rectangles easily fit). @TODO a smarter approach would be to only
-- quantize once we've hit OOM, then we could get rid of this parameter.
context.align = 1 context.align = 1
else else
-- if it's not ok to run out of memory, then quantize the widths
-- so that num_nodes is always enough nodes.
--
-- I.e. num_nodes * align >= width
-- align >= width / num_nodes
-- align = ceil(width/num_nodes)
context.align = (context.width + context.num_nodes - 1) / context.num_nodes context.align = (context.width + context.num_nodes - 1) / context.num_nodes
end end
end end
-- Initialize a rectangle packer to:
-- pack a rectangle that is 'width' by 'height' in dimensions
-- using temporary storage provided by the array 'nodes', which is 'num_nodes' long
--
-- You must call this function every time you start packing into a new target.
--
-- There is no "shutdown" function. The 'nodes' memory must stay valid for
-- the following stbrp_pack_rects() call (or calls), but can be freed after
-- the call (or calls) finish.
--
-- Note: to guarantee best results, either:
-- 1. make sure 'num_nodes' >= 'width'
-- or 2. call stbrp_setup_allow_out_of_mem() defined above with 'allow_out_of_mem = true'
--
-- If you don't do either of the above things, widths will be quantized to multiples
-- of small integers to guarantee the algorithm doesn't run out of temporary storage.
--
-- If you do #2, then the non-quantized algorithm will be used, but the algorithm
-- may run out of temporary storage and be unable to pack some rectangles.
--- @param context stbrp_context --- @param context stbrp_context
--- @param width int --- @param width int
--- @param height int --- @param height int
@@ -160,6 +262,7 @@ function stbrp_init_target(context, width, height, nodes, num_nodes)
context.num_nodes = num_nodes context.num_nodes = num_nodes
stbrp_setup_allow_out_of_mem(context, false) stbrp_setup_allow_out_of_mem(context, false)
-- node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context.extra[1].x = 0 context.extra[1].x = 0
context.extra[1].y = 0 context.extra[1].y = 0
context.extra[1].next = context.extra[2] context.extra[1].next = context.extra[2]
@@ -168,6 +271,8 @@ function stbrp_init_target(context, width, height, nodes, num_nodes)
context.extra[2].next = nil context.extra[2].next = nil
end end
-- find minimum y position if it starts at x1
-- Returns (min_y, waste_area)
--- @param c stbrp_context --- @param c stbrp_context
--- @param first stbrp_node --- @param first stbrp_node
--- @param x0 int --- @param x0 int
@@ -181,7 +286,7 @@ local function stbrp__skyline_find_min_y(c, first, x0, width)
STBRP_ASSERT(first.x <= x0) STBRP_ASSERT(first.x <= x0)
STBRP_ASSERT(node.next.x > x0) STBRP_ASSERT(node.next.x > x0) -- we ended up handling this in the caller for efficiency
STBRP_ASSERT(node.x <= x0) STBRP_ASSERT(node.x <= x0)
@@ -190,14 +295,19 @@ local function stbrp__skyline_find_min_y(c, first, x0, width)
local visited_width = 0 local visited_width = 0
while node.x < x1 do while node.x < x1 do
if node.y > min_y then if node.y > min_y then
-- raise min_y higher.
-- we've accounted for all waste up to min_y,
-- but we'll now add more waste for everything we've visited
waste_area = waste_area + visited_width * (node.y - min_y) waste_area = waste_area + visited_width * (node.y - min_y)
min_y = node.y min_y = node.y
-- the first time through, visited_width might be reduced
if node.x < x0 then if node.x < x0 then
visited_width = visited_width + node.next.x - x0 visited_width = visited_width + node.next.x - x0
else else
visited_width = visited_width + node.next.x - node.x visited_width = visited_width + node.next.x - node.x
end end
else else
-- add waste area
local under_width = node.next.x - node.x local under_width = node.next.x - node.x
if under_width + visited_width > width then if under_width + visited_width > width then
under_width = width - visited_width under_width = width - visited_width
@@ -223,10 +333,12 @@ local function stbrp__skyline_find_best_pos(c, width, height)
local best_prev_link = nil local best_prev_link = nil
local best_prev_link_type = nil local best_prev_link_type = nil
-- align to multiple of c.align
width = width + c.align - 1 width = width + c.align - 1
width = width - width % c.align width = width - width % c.align
STBRP_ASSERT(width % c.align == 0) STBRP_ASSERT(width % c.align == 0)
-- if it can't possibly fit, bail immediately
if width > c.width or height > c.height then if width > c.width or height > c.height then
fr.prev_link = nil fr.prev_link = nil
fr.prev_link_type = nil fr.prev_link_type = nil
@@ -242,7 +354,8 @@ local function stbrp__skyline_find_best_pos(c, width, height)
while (node.x + width <= c.width) do while (node.x + width <= c.width) do
local y, waste = stbrp__skyline_find_min_y(c, node, node.x, width) local y, waste = stbrp__skyline_find_min_y(c, node, node.x, width)
if c.heuristic == STBRP_HEURISTIC_Skyline.BL_sortHeight then if c.heuristic == STBRP_HEURISTIC_Skyline.BL_sortHeight then -- actually just want to test BL
-- bottom left
if y < best_y then if y < best_y then
best_y = y best_y = y
best_prev_link = prev_link best_prev_link = prev_link
@@ -250,7 +363,9 @@ local function stbrp__skyline_find_best_pos(c, width, height)
best_x = node.x best_x = node.x
end end
else else
-- best-fit
if y + height <= c.height then if y + height <= c.height then
-- can only use it if it first vertically
if y < best_y or (y == best_y and waste < best_waste) then if y < best_y or (y == best_y and waste < best_waste) then
best_y = y best_y = y
best_waste = waste best_waste = waste
@@ -266,12 +381,30 @@ local function stbrp__skyline_find_best_pos(c, width, height)
node = node.next node = node.next
end end
-- if doing best-fit (BF), we also have to try aligning right edge to each node position
--
-- e.g, if fitting
--
-- ____________________
-- |____________________|
--
-- into
--
-- | |
-- | ____________|
-- |____________|
--
-- then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
--
-- This makes BF take about 2x the time
if c.heuristic == STBRP_HEURISTIC_Skyline.BF_sortHeight then if c.heuristic == STBRP_HEURISTIC_Skyline.BF_sortHeight then
local tail = c.active_head local tail = c.active_head
local node_scan = c.active_head local node_scan = c.active_head
local prev_link_scan = c local prev_link_scan = c
local prev_link_scan_type = STBRP__PREV_LINK_TYPE.ACTIVE_HEAD local prev_link_scan_type = STBRP__PREV_LINK_TYPE.ACTIVE_HEAD
-- find first node that's admissible
while tail and tail.x < width do while tail and tail.x < width do
tail = tail.next tail = tail.next
end end
@@ -280,6 +413,7 @@ local function stbrp__skyline_find_best_pos(c, width, height)
local xpos = tail.x - width local xpos = tail.x - width
STBRP_ASSERT(xpos >= 0) STBRP_ASSERT(xpos >= 0)
-- find the left position that matches this
while node_scan.next and node_scan.next.x <= xpos do while node_scan.next and node_scan.next.x <= xpos do
prev_link_scan = node_scan prev_link_scan = node_scan
prev_link_scan_type = STBRP__PREV_LINK_TYPE.NEXT prev_link_scan_type = STBRP__PREV_LINK_TYPE.NEXT
@@ -295,6 +429,7 @@ local function stbrp__skyline_find_best_pos(c, width, height)
if y <= best_y then if y <= best_y then
if y < best_y or waste < best_waste or (waste == best_waste and xpos < best_x) then if y < best_y or waste < best_waste or (waste == best_waste and xpos < best_x) then
best_x = xpos best_x = xpos
-- STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y best_y = y
best_waste = waste best_waste = waste
best_prev_link = prev_link_scan best_prev_link = prev_link_scan
@@ -320,19 +455,29 @@ end
--- @param height int --- @param height int
--- @return stbrp__findresult --- @return stbrp__findresult
local function stbrp__skyline_pack_rectangle(context, width, height) local function stbrp__skyline_pack_rectangle(context, width, height)
-- find best position according to heuristic
local res = stbrp__skyline_find_best_pos(context, width, height) local res = stbrp__skyline_find_best_pos(context, width, height)
-- bail if:
-- 1. it failed
-- 2. the best node doesn't fit (we don't always check this)
-- 3. we're out of memory
if res.prev_link == nil or res.y + height > context.height or context.free_head == nil then if res.prev_link == nil or res.y + height > context.height or context.free_head == nil then
res.prev_link = nil res.prev_link = nil
res.prev_link_type = nil res.prev_link_type = nil
return res return res
end end
-- on success, create new node
local node = context.free_head local node = context.free_head
node.x = res.x node.x = res.x
node.y = res.y + height node.y = res.y + height
context.free_head = node.next context.free_head = node.next
-- insert the new node into the right starting point, and
-- let 'cur' point to the remaining nodes needing to be
-- stitched back in
local cur local cur
if res.prev_link_type == STBRP__PREV_LINK_TYPE.ACTIVE_HEAD then if res.prev_link_type == STBRP__PREV_LINK_TYPE.ACTIVE_HEAD then
cur = res.prev_link.active_head cur = res.prev_link.active_head
@@ -341,6 +486,7 @@ local function stbrp__skyline_pack_rectangle(context, width, height)
end end
if cur.x < res.x then if cur.x < res.x then
-- preserve the existing one, so start testing with the next one
local next = cur.next local next = cur.next
cur.next = node cur.next = node
cur = next cur = next
@@ -352,13 +498,17 @@ local function stbrp__skyline_pack_rectangle(context, width, height)
end end
end end
-- from here, traverse cur and free the nodes, until we get to one
-- that shouldn't be freed
while cur.next and cur.next.x <= res.x + width do while cur.next and cur.next.x <= res.x + width do
local next = cur.next local next = cur.next
-- move the current node to the free list
cur.next = context.free_head cur.next = context.free_head
context.free_head = cur context.free_head = cur
cur = next cur = next
end end
-- stitch the list back in
node.next = cur node.next = cur
if cur.x < res.x + width then if cur.x < res.x + width then
@@ -390,6 +540,29 @@ local function rect_original_order(a, b)
return a.was_packed < b.was_packed return a.was_packed < b.was_packed
end end
-- Assign packed locations to rectangles. The rectangles are of type
-- 'stbrp_rect' defined above, stored in the array 'rects', and there
-- are 'num_rects' many of them.
--
-- Rectangles which are successfully packed have the 'was_packed' flag
-- set to a non-zero value and 'x' and 'y' store the minimum location
-- on each axis (i.e. bottom-left in cartesian coordinates, top-left
-- if you imagine y increasing downwards). Rectangles which do not fit
-- have the 'was_packed' flag set to 0.
--
-- You should not try to access the 'rects' array from another thread
-- while this function is running, as the function temporarily reorders
-- the array while it executes.
--
-- To pack into another rectangle, you need to call stbrp_init_target
-- again. To continue packing into the same rectangle, you can call
-- this function again. Calling this multiple times with multiple rect
-- arrays will probably produce worse packing results than calling it
-- a single time with the full rectangle array, but the option is
-- available.
--
-- The function returns 1 if all of the rectangles were successfully
-- packed and 0 otherwise.
--- @param context stbrp_context --- @param context stbrp_context
--- @param rects stbrp_rect[] --- @param rects stbrp_rect[]
--- @param num_rects int --- @param num_rects int
@@ -397,15 +570,17 @@ end
local function stbrp_pack_rects(context, rects, num_rects) local function stbrp_pack_rects(context, rects, num_rects)
local all_rects_packed = 1 local all_rects_packed = 1
-- we use the 'was_packed' field internally to allow sorting/unsorting
for i = 1, num_rects do for i = 1, num_rects do
rects[i].was_packed = (i - 1) rects[i].was_packed = (i - 1)
end end
-- sort according to heuristic
STBRP_SORT(rects, rect_height_compare) STBRP_SORT(rects, rect_height_compare)
for i = 1, num_rects do for i = 1, num_rects do
if rects[i].w == 0 or rects[i].h == 0 then if rects[i].w == 0 or rects[i].h == 0 then
rects[i].x = 0 rects[i].x = 0 -- empty rect needs no space
rects[i].y = 0 rects[i].y = 0
else else
local fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h) local fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h)
@@ -419,8 +594,10 @@ local function stbrp_pack_rects(context, rects, num_rects)
end end
end end
-- unsort
STBRP_SORT(rects, rect_original_order) STBRP_SORT(rects, rect_original_order)
-- set was_packed flags and all_rects_packed status
for i = 1, num_rects do for i = 1, num_rects do
rects[i].was_packed = (rects[i].x == STBRP__MAXVAL and rects[i].y == STBRP__MAXVAL) and 0 or 1 rects[i].was_packed = (rects[i].x == STBRP__MAXVAL and rects[i].y == STBRP__MAXVAL) and 0 or 1
if rects[i].was_packed == 0 then if rects[i].was_packed == 0 then
@@ -428,6 +605,7 @@ local function stbrp_pack_rects(context, rects, num_rects)
end end
end end
-- return the all_rects_packed status
return all_rects_packed return all_rects_packed
end end
@@ -441,3 +619,43 @@ return {
setup_allow_out_of_mem = stbrp_setup_allow_out_of_mem, setup_allow_out_of_mem = stbrp_setup_allow_out_of_mem,
setup_heuristic = stbrp_setup_heuristic setup_heuristic = stbrp_setup_heuristic
} }
-- ------------------------------------------------------------------------------
-- This software is available under 2 licenses -- choose whichever you prefer.
-- ------------------------------------------------------------------------------
-- ALTERNATIVE A - MIT License
-- Copyright (c) 2017 Sean Barrett
-- 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.
-- ------------------------------------------------------------------------------
-- ALTERNATIVE B - Public Domain (www.unlicense.org)
-- This is free and unencumbered software released into the public domain.
-- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-- software, either in source code form or as a compiled binary, for any purpose,
-- commercial or non-commercial, and by any means.
-- In jurisdictions that recognize copyright laws, the author or authors of this
-- software dedicate any and all copyright interest in the software to the public
-- domain. We make this dedication for the benefit of the public at large and to
-- the detriment of our heirs and successors. We intend this dedication to be an
-- overt act of relinquishment in perpetuity of all present and future rights to
-- this software under copyright law.
-- 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 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.
-- ------------------------------------------------------------------------------
+336 -35
View File
@@ -1,6 +1,221 @@
--- Dear ImGui --- Dear ImGui
-- This is a Lua port of original `imstb_textedit.h` -- This is a Lua port of original `imstb_textedit.h`
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_textedit.h 1.14.
-- Those changes would need to be pushed into nothings/stb:
-- - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
-- - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
-- - Added name to struct or it may be forward declared in our code.
-- - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
-- - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
-- Grep for [DEAR IMGUI] to find some changes.
-- - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
-- stb_textedit.h - v1.14 - public domain - Sean Barrett
-- Development of this library was sponsored by RAD Game Tools
--
-- This C header file implements the guts of a multi-line text-editing
-- widget; you implement display, word-wrapping, and low-level string
-- insertion/deletion, and stb_textedit will map user inputs into
-- insertions & deletions, plus updates to the cursor position,
-- selection state, and undo state.
--
-- It is intended for use in games and other systems that need to build
-- their own custom widgets and which do not have heavy text-editing
-- requirements (this library is not recommended for use for editing large
-- texts, as its performance does not scale and it has limited undo).
--
-- Non-trivial behaviors are modelled after Windows text controls.
--
--
-- LICENSE
--
-- See end of file for license information.
--
--
-- DEPENDENCIES
--
-- Uses the C runtime function 'memmove', which you can override
-- by defining IMSTB_TEXTEDIT_memmove before the implementation.
-- (In this port: ImStb.TEXTEDIT_memmove.)
--
--
-- VERSION HISTORY
--
-- !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
-- 1.14 (2021-07-11) page up/down, various fixes
-- 1.13 (2019-02-07) fix bug in undo size management
-- 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
-- 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
-- 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
-- 1.9 (2016-08-27) customizable move-by-word
-- 1.8 (2016-04-02) better keyboard handling when mouse button is down
-- 1.7 (2015-09-13) change y range handling in case baseline is non-0
-- 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove
-- 1.5 (2014-09-10) add support for secondary keys for OS X
-- 1.4 (2014-08-17) fix signed/unsigned warnings
-- 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary
-- 1.2 (2014-05-27) fix some RAD types that had crept into the new code
-- 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
-- 1.0 (2012-07-26) improve documentation, initial public release
-- 0.3 (2012-02-24) bugfixes, single-line mode; insert mode
-- 0.2 (2011-11-28) fixes to undo/redo
-- 0.1 (2010-07-08) initial version
--
-- ADDITIONAL CONTRIBUTORS
--
-- Ulf Winklemann: move-by-word in 1.1
-- Fabian Giesen: secondary key inputs in 1.5
-- Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
-- Louis Schnellbach: page up/down in 1.14
--
-- Bugfixes:
-- Scott Graham
-- Daniel Keller
-- Omar Cornut
-- Dan Thompson
--
-- USAGE
--
-- The basic concept is that you provide a "string" object which
-- behaves like an array of characters. stb_textedit uses indices to
-- refer to positions in the string, implicitly representing positions
-- in the displayed textedit. This is true for both plain text and
-- rich text; even with rich text stb_truetype interacts with your
-- code as if there was an array of all the displayed characters.
--
-- Symbols you must define for implementation mode (in this port: fields of ImStb, see below):
--
-- STB_TEXTEDIT_STRING the type of object representing a string being edited,
-- typically this is a wrapper object with other data you need
--
-- STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))
-- STB_TEXTEDIT_LAYOUTROW(r,obj,n) returns the results of laying out a line of characters
-- starting from character #n (see discussion below)
-- STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character
-- to the xpos of the i+1'th char for a line of characters
-- starting at character #n (i.e. accounts for kerning
-- with previous char)
-- STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 1-based in this port
-- STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
-- as manually wordwrapping for end-of-line positioning
--
-- STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
-- STB_TEXTEDIT_INSERTCHARS(obj,i,c,c_offset,n) try to insert n characters at i (taken from array c starting at c_offset)
-- returns number of characters actually inserted. [DEAR IMGUI]
--
-- STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
--
-- STB_TEXTEDIT_K_LEFT keyboard input to move cursor left
-- STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
-- STB_TEXTEDIT_K_UP keyboard input to move cursor up
-- STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
-- STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
-- STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
-- STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line -- e.g. HOME
-- STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line -- e.g. END
-- STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text -- e.g. ctrl-HOME
-- STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text -- e.g. ctrl-END
-- STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor
-- STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor
-- STB_TEXTEDIT_K_UNDO keyboard input to perform undo
-- STB_TEXTEDIT_K_REDO keyboard input to perform redo
--
-- STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to
-- STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to
-- STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word -- e.g. ctrl-LEFT
-- STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word -- e.g. ctrl-RIGHT
--
-- To support UTF-8:
--
-- STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
-- STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
-- Do NOT define STB_TEXTEDIT_KEYTOTEXT.
-- Instead, call stb_textedit_text() directly for text contents.
--
-- Keyboard input must be encoded as a single integer value; e.g. a character code
-- and some bitflags that represent shift states. to simplify the interface, SHIFT must
-- be a bitflag, so we can test the shifted state of cursor movements to allow selection,
-- i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
--
-- STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
-- row of characters assuming they start on the i'th character--the width and
-- the height and the number of characters consumed. This allows this library
-- to traverse the entire layout incrementally. You need to compute word-wrapping
-- here.
--
-- Each textfield keeps its own insert mode state, which is not how normal
-- applications work. To keep an app-wide insert mode, update/copy the
-- "insert_mode" field of STB_TexteditState before/after calling API functions.
--
-- API (in this port: fields of the table returned by this file)
--
-- initialize_state(state, is_single_line)
--
-- click(str, state, x, y)
-- drag(str, state, x, y)
-- cut(str, state) -> bool
-- paste(str, state, text, len) -> bool
-- key(str, state, key)
-- text(str, state, text, text_len)
--
-- Each of these functions potentially updates the string and updates the
-- state.
--
-- initialize_state:
-- set the textedit state to a known good default state when initially
-- constructing the textedit.
--
-- click:
-- call this with the mouse x,y on a mouse down; it will update the cursor
-- and reset the selection start/end to the cursor point. the x,y must
-- be relative to the text widget, with (0,0) being the top left.
--
-- drag:
-- call this with the mouse x,y on a mouse drag/up; it will update the
-- cursor and the selection end point
--
-- cut:
-- call this to delete the current selection; returns true if there was
-- one. you should FIRST copy the current selection to the system paste buffer.
-- (To copy, just copy the current selection out of the string yourself.)
--
-- paste:
-- call this to paste text at the current cursor point or over the current
-- selection if there is one.
--
-- key:
-- call this for keyboard inputs sent to the textfield. you can use it
-- for "key down" events or for "translated" key events.
--
-- text: (added 2025)
-- call this to directly send text input the textfield, which is required
-- for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
-- cannot infer text length.
--
--
-- When rendering, you can read the cursor position and selection state from
-- the STB_TexteditState.
--
--
-- Notes:
--
-- This is designed to be usable in IMGUI, so it allows for the possibility of
-- running in an IMGUI that has NOT cached the multi-line layout. For this
-- reason, it provides an interface that is compatible with computing the
-- layout incrementally--we try to make sure we make as few passes through
-- as possible. (For example, to locate the mouse pointer in the text, we
-- could define functions that return the X and Y positions of characters
-- and binary search Y and then X, but if we're doing dynamic layout this
-- will run the layout algorithm many times, so instead we manually search
-- forward in one pass. Similar logic applies to e.g. up-arrow and
-- down-arrow movement.)
--
-- If it's run in a widget that *has* cached the layout, then this is less
-- efficient, but it's not horrible on modern computers. But you wouldn't
-- want to edit million-line files with it.
-- ALL TABLES IN THIS FILE ARE 1-BASED! -- ALL TABLES IN THIS FILE ARE 1-BASED!
-- Character Indexing: -- Character Indexing:
-- idx=n, the n'th char (1-based) -- idx=n, the n'th char (1-based)
@@ -13,6 +228,10 @@ local STB_TEXTEDIT_STRINGLEN = ImStb.TEXTEDIT_STRINGLEN
local STB_TEXTEDIT_GETCHAR = ImStb.TEXTEDIT_GETCHAR local STB_TEXTEDIT_GETCHAR = ImStb.TEXTEDIT_GETCHAR
local STB_TEXTEDIT_GETWIDTH = ImStb.TEXTEDIT_GETWIDTH local STB_TEXTEDIT_GETWIDTH = ImStb.TEXTEDIT_GETWIDTH
local STB_TEXTEDIT_LAYOUTROW = ImStb.TEXTEDIT_LAYOUTROW local STB_TEXTEDIT_LAYOUTROW = ImStb.TEXTEDIT_LAYOUTROW
-- [DEAR IMGUI]
-- Functions must be implemented for UTF8 support
-- Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
-- There is not necessarily a '[DEAR IMGUI]' at the usage sites.
local IMSTB_TEXTEDIT_GETNEXTCHARINDEX = ImStb.TEXTEDIT_GETNEXTCHARINDEX local IMSTB_TEXTEDIT_GETNEXTCHARINDEX = ImStb.TEXTEDIT_GETNEXTCHARINDEX
local IMSTB_TEXTEDIT_GETPREVCHARINDEX = ImStb.TEXTEDIT_GETPREVCHARINDEX local IMSTB_TEXTEDIT_GETPREVCHARINDEX = ImStb.TEXTEDIT_GETPREVCHARINDEX
local STB_TEXTEDIT_MOVEWORDLEFT = ImStb.TEXTEDIT_MOVEWORDLEFT local STB_TEXTEDIT_MOVEWORDLEFT = ImStb.TEXTEDIT_MOVEWORDLEFT
@@ -22,34 +241,35 @@ local STB_TEXTEDIT_MOVELINEEND = ImStb.TEXTEDIT_MOVELINEEND
local STB_TEXTEDIT_DELETECHARS = ImStb.TEXTEDIT_DELETECHARS local STB_TEXTEDIT_DELETECHARS = ImStb.TEXTEDIT_DELETECHARS
local STB_TEXTEDIT_INSERTCHARS = ImStb.TEXTEDIT_INSERTCHARS local STB_TEXTEDIT_INSERTCHARS = ImStb.TEXTEDIT_INSERTCHARS
STB_TEXTEDIT_NEWLINE = 10 STB_TEXTEDIT_NEWLINE = 10 -- the character returned by _GETCHAR() we recognize as manually wordwrapping for end-of-line positioning
STB_TEXTEDIT_K_LEFT = 0x200000 -- keyboard input to move cursor left STB_TEXTEDIT_K_LEFT = 0x200000 -- keyboard input to move cursor left
STB_TEXTEDIT_K_RIGHT = 0x200001 -- keyboard input to move cursor right STB_TEXTEDIT_K_RIGHT = 0x200001 -- keyboard input to move cursor right
STB_TEXTEDIT_K_UP = 0x200002 -- keyboard input to move cursor up STB_TEXTEDIT_K_UP = 0x200002 -- keyboard input to move cursor up
STB_TEXTEDIT_K_DOWN = 0x200003 -- keyboard input to move cursor down STB_TEXTEDIT_K_DOWN = 0x200003 -- keyboard input to move cursor down
STB_TEXTEDIT_K_LINESTART = 0x200004 -- keyboard input to move cursor to start of line STB_TEXTEDIT_K_LINESTART = 0x200004 -- keyboard input to move cursor to start of line -- e.g. HOME
STB_TEXTEDIT_K_LINEEND = 0x200005 -- keyboard input to move cursor to end of line STB_TEXTEDIT_K_LINEEND = 0x200005 -- keyboard input to move cursor to end of line -- e.g. END
STB_TEXTEDIT_K_TEXTSTART = 0x200006 -- keyboard input to move cursor to start of text STB_TEXTEDIT_K_TEXTSTART = 0x200006 -- keyboard input to move cursor to start of text -- e.g. ctrl-HOME
STB_TEXTEDIT_K_TEXTEND = 0x200007 -- keyboard input to move cursor to end of text STB_TEXTEDIT_K_TEXTEND = 0x200007 -- keyboard input to move cursor to end of text -- e.g. ctrl-END
STB_TEXTEDIT_K_DELETE = 0x200008 -- keyboard input to delete selection or character under cursor STB_TEXTEDIT_K_DELETE = 0x200008 -- keyboard input to delete selection or character under cursor
STB_TEXTEDIT_K_BACKSPACE = 0x200009 -- keyboard input to delete selection or character left of cursor STB_TEXTEDIT_K_BACKSPACE = 0x200009 -- keyboard input to delete selection or character left of cursor
STB_TEXTEDIT_K_UNDO = 0x20000A -- keyboard input to perform undo STB_TEXTEDIT_K_UNDO = 0x20000A -- keyboard input to perform undo
STB_TEXTEDIT_K_REDO = 0x20000B -- keyboard input to perform redo STB_TEXTEDIT_K_REDO = 0x20000B -- keyboard input to perform redo
STB_TEXTEDIT_K_WORDLEFT = 0x20000C -- keyboard input to move cursor left one word STB_TEXTEDIT_K_WORDLEFT = 0x20000C -- keyboard input to move cursor left one word -- e.g. ctrl-LEFT
STB_TEXTEDIT_K_WORDRIGHT = 0x20000D -- keyboard input to move cursor right one word STB_TEXTEDIT_K_WORDRIGHT = 0x20000D -- keyboard input to move cursor right one word -- e.g. ctrl-RIGHT
STB_TEXTEDIT_K_PGUP = 0x20000E -- keyboard input to move cursor up a page STB_TEXTEDIT_K_PGUP = 0x20000E -- keyboard input to move cursor up a page
STB_TEXTEDIT_K_PGDOWN = 0x20000F -- keyboard input to move cursor down a page STB_TEXTEDIT_K_PGDOWN = 0x20000F -- keyboard input to move cursor down a page
STB_TEXTEDIT_K_SHIFT = 0x400000 STB_TEXTEDIT_K_SHIFT = 0x400000 -- a power of two that is or'd in to a keyboard input to represent the shift key
IMSTB_TEXTEDIT_UNDOSTATECOUNT = 99 IMSTB_TEXTEDIT_UNDOSTATECOUNT = 99 -- the number of undo states to allow
local IMSTB_TEXTEDIT_UNDOCHARCOUNT = 999 local IMSTB_TEXTEDIT_UNDOCHARCOUNT = 999 -- the number of characters to store in the undo buffer
local IMSTB_TEXTEDIT_memmove = ImStb.TEXTEDIT_memmove local IMSTB_TEXTEDIT_memmove = ImStb.TEXTEDIT_memmove
-- forward declarations
local stb_text_undo local stb_text_undo
local stb_text_redo local stb_text_redo
---------------------------------------------------------------- -------------------------------------------------------------------------------
---------------------------------------------------------------- -------------------------------------------------------------------------------
--- ---
--- STB_TexteditState --- STB_TexteditState
--- ---
@@ -72,6 +292,7 @@ local stb_text_redo
--- @nodiscard --- @nodiscard
local function StbUndoRecord() local function StbUndoRecord()
return { return {
-- private data
where = 1, where = 1,
insert_length = 0, insert_length = 0,
delete_length = 0, delete_length = 0,
@@ -101,6 +322,7 @@ local function StbUndoState()
end end
return { return {
-- private data
undo_rec = undo_rec, undo_rec = undo_rec,
undo_char = undo_char, undo_char = undo_char,
@@ -131,16 +353,34 @@ end
--- @nodiscard --- @nodiscard
function STB_TexteditState() function STB_TexteditState()
return { return {
cursor = 0, ---------------------
--
-- public data
--
select_start = 0, cursor = 0,
-- position of the text cursor within the string
select_start = 0, -- selection start point
select_end = 0, select_end = 0,
-- selection start and end point in characters; if equal, no selection.
-- note that start may be less than or greater than end (e.g. when
-- dragging the mouse, start is where the initial click was, and you
-- can drag in either direction)
insert_mode = false, insert_mode = false,
-- each textfield keeps its own insert mode state. to keep an app-wide
-- insert mode, copy this value in/out of the app state
row_count_per_page = 0, row_count_per_page = 0,
-- page size in number of row.
-- this value MUST be set to >0 for pageup or pagedown in multilines documents.
cursor_at_end_of_line = false, ---------------------
--
-- private data
--
cursor_at_end_of_line = false, -- not implemented yet
initialized = false, initialized = false,
has_preferred_x = false, has_preferred_x = false,
single_line = false, single_line = false,
@@ -148,7 +388,7 @@ function STB_TexteditState()
padding2 = 0, padding2 = 0,
padding3 = 0, padding3 = 0,
preferred_x = 0.0, preferred_x = 0.0, -- this determines where the cursor up/down tries to seek to along x
undostate = StbUndoState() undostate = StbUndoState()
} }
@@ -159,12 +399,20 @@ local function STB_TEXT_HAS_SELECTION(s)
return s.select_start ~= s.select_end return s.select_start ~= s.select_end
end end
-------------------------------------------------------------------------------
---
--- StbTexteditRow
---
--- Result of layout query, used by stb_textedit to determine where
--- the text in each row is.
-- result of layout query
--- @class StbTexteditRow --- @class StbTexteditRow
--- @field x0 float # starting x location --- @field x0 float # starting x location, end x location (allows for align=right, etc)
--- @field x1 float # end x location --- @field x1 float # end x location
--- @field baseline_y_delta float # position of baseline relative to previous row's baseline --- @field baseline_y_delta float # position of baseline relative to previous row's baseline
--- @field ymin float # height of row above baseline --- @field ymin float # height of row above and below baseline
--- @field ymax float # height of row below baseline --- @field ymax float # height of row above and below baseline
--- @field num_chars int --- @field num_chars int
--- @return StbTexteditRow --- @return StbTexteditRow
@@ -196,10 +444,15 @@ end
---------------------------------------- ----------------------------------------
---------------------------------------- ----------------------------------------
--- ---
--- Implementation --- Implementation mode
--- ---
--- ---
-------------------------------------------------------------------------------
---
--- Mouse input handling
---
local stb_text_locate_coord do local stb_text_locate_coord do
-- only create once, reuse later -- only create once, reuse later
@@ -351,6 +604,7 @@ end
--- ---
--- ---
-- forward declarations
local stb_text_makeundo_insert local stb_text_makeundo_insert
local stb_text_makeundo_delete local stb_text_makeundo_delete
local stb_text_makeundo_replace local stb_text_makeundo_replace
@@ -399,8 +653,8 @@ function stb_textedit_find_charpos(find, str, n, single_line)
local z = STB_TEXTEDIT_STRINGLEN(str) local z = STB_TEXTEDIT_STRINGLEN(str)
local first local first
-- special case if it's at the end (may not be needed?)
if n == z + 1 and single_line then if n == z + 1 and single_line then
-- special case if it's at the end (may not be needed?)
STB_TEXTEDIT_LAYOUTROW(r, str, 1) STB_TEXTEDIT_LAYOUTROW(r, str, 1)
find.y = 0 find.y = 0
find.first_char = 1 find.first_char = 1
@@ -420,18 +674,18 @@ function stb_textedit_find_charpos(find, str, n, single_line)
if n < i + r.num_chars - 1 then if n < i + r.num_chars - 1 then
break break
end end
if str.LastMoveDirectionLR == ImGuiDir.Right and str.Stb.cursor > 1 and str.Stb.cursor == i + r.num_chars and STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) ~= STB_TEXTEDIT_NEWLINE then -- [IMGUI] Wrapping point handling if str.LastMoveDirectionLR == ImGuiDir.Right and str.Stb.cursor > 1 and str.Stb.cursor == i + r.num_chars and STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) ~= STB_TEXTEDIT_NEWLINE then -- [DEAR IMGUI] Wrapping point handling
break break
end end
if (i - 1) + r.num_chars == z and z > 0 and STB_TEXTEDIT_GETCHAR(str, z) ~= STB_TEXTEDIT_NEWLINE then -- [IMGUI] special handling for last line if (i - 1) + r.num_chars == z and z > 0 and STB_TEXTEDIT_GETCHAR(str, z) ~= STB_TEXTEDIT_NEWLINE then -- [DEAR IMGUI] special handling for last line
break break -- [DEAR IMGUI]
end end
prev_start = i prev_start = i
i = i + r.num_chars i = i + r.num_chars
find.y = find.y + r.baseline_y_delta find.y = find.y + r.baseline_y_delta
if i == z + 1 then -- [IMGUI] if i == z + 1 then -- [DEAR IMGUI]
r.num_chars = 0 r.num_chars = 0 -- [DEAR IMGUI]
break break -- [DEAR IMGUI]
end end
end end
@@ -479,7 +733,7 @@ local function stb_textedit_delete(str, state, where, len)
state.has_preferred_x = false state.has_preferred_x = false
end end
-- delete the selection -- delete the section
--- @param str IMSTB_TEXTEDIT_STRING --- @param str IMSTB_TEXTEDIT_STRING
--- @param state STB_TexteditState --- @param state STB_TexteditState
local function stb_textedit_delete_selection(str, state) local function stb_textedit_delete_selection(str, state)
@@ -498,7 +752,7 @@ local function stb_textedit_delete_selection(str, state)
end end
end end
-- canonicalize the selection so start <= end -- canoncialize the selection so start <= end
--- @param state STB_TexteditState --- @param state STB_TexteditState
local function stb_textedit_sortselection(state) local function stb_textedit_sortselection(state)
if state.select_end < state.select_start then if state.select_end < state.select_start then
@@ -579,7 +833,7 @@ local function stb_textedit_paste_internal(str, state, text, len)
end end
-- API key: process text input -- API key: process text input
-- [IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. -- [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
--- @param str IMSTB_TEXTEDIT_STRING --- @param str IMSTB_TEXTEDIT_STRING
--- @param state STB_TexteditState --- @param state STB_TexteditState
--- @param text IMSTB_TEXTEDIT_CHARTYPE[] --- @param text IMSTB_TEXTEDIT_CHARTYPE[]
@@ -732,7 +986,7 @@ function stb_textedit_key(str, state, key)
break break
end end
-- [IMGUI] -- [DEAR IMGUI]
-- going down while being on the last line shouldn't bring us to that line end -- going down while being on the last line shouldn't bring us to that line end
--if STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) ~= STB_TEXTEDIT_NEWLINE then --if STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) ~= STB_TEXTEDIT_NEWLINE then
-- break -- break
@@ -929,8 +1183,8 @@ end
----------------------------------------------------- -----------------------------------------------------
--- ---
--- Undo processing --- Undo processing
--- OPTIMIZE: the undo/redo buffer should be circular
--- ---
--- @OPTIMIZE: the undo/redo buffer should be circular
--- @param state StbUndoState --- @param state StbUndoState
local function stb_textedit_flush_redo(state) local function stb_textedit_flush_redo(state)
@@ -950,7 +1204,7 @@ local function stb_textedit_discard_undo(state)
IMSTB_TEXTEDIT_memmove(state.undo_char, 1, state.undo_char, n + 1, state.undo_char_point - 1) IMSTB_TEXTEDIT_memmove(state.undo_char, 1, state.undo_char, n + 1, state.undo_char_point - 1)
for i = 1, state.undo_point do for i = 1, state.undo_point do
if state.undo_rec[i].char_storage >= 0 then if state.undo_rec[i].char_storage >= 0 then
state.undo_rec[i].char_storage = state.undo_rec[i].char_storage - n -- OPTIMIZE: get rid of char_storage and infer it state.undo_rec[i].char_storage = state.undo_rec[i].char_storage - n -- @OPTIMIZE: get rid of char_storage and infer it
end end
end end
end end
@@ -983,7 +1237,7 @@ local function stb_textedit_discard_redo(state)
end end
-- now move all the redo records towards the end of the buffer; the first one is at 'redo_point' -- now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
-- [IMGUI] -- [DEAR IMGUI]
local move_size = IMSTB_TEXTEDIT_UNDOSTATECOUNT - state.redo_point - 1 local move_size = IMSTB_TEXTEDIT_UNDOSTATECOUNT - state.redo_point - 1
IMSTB_TEXTEDIT_memmove(state.undo_rec, state.redo_point + 1, state.undo_rec, state.redo_point, move_size) IMSTB_TEXTEDIT_memmove(state.undo_rec, state.redo_point + 1, state.undo_rec, state.redo_point, move_size)
@@ -1129,6 +1383,9 @@ function stb_text_redo(str, state)
local u = s.undo_rec[s.undo_point] local u = s.undo_rec[s.undo_point]
local r = s.undo_rec[s.redo_point] local r = s.undo_rec[s.redo_point]
-- we KNOW there must be room for the undo record, because the redo record
-- was derived from an undo record
u.delete_length = r.insert_length u.delete_length = r.insert_length
u.insert_length = r.delete_length u.insert_length = r.delete_length
u.where = r.where u.where = r.where
@@ -1137,6 +1394,7 @@ function stb_text_redo(str, state)
if r.delete_length > 0 then if r.delete_length > 0 then
-- the redo record requires us to delete characters, so the undo record -- the redo record requires us to delete characters, so the undo record
-- needs to store the characters -- needs to store the characters
if s.undo_char_point + u.insert_length > s.redo_char_point then if s.undo_char_point + u.insert_length > s.redo_char_point then
u.insert_length = 0 u.insert_length = 0
u.delete_length = 0 u.delete_length = 0
@@ -1144,6 +1402,7 @@ function stb_text_redo(str, state)
u.char_storage = s.undo_char_point u.char_storage = s.undo_char_point
s.undo_char_point = s.undo_char_point + u.insert_length s.undo_char_point = s.undo_char_point + u.insert_length
-- now save the characters
for i = 0, u.insert_length - 1 do for i = 0, u.insert_length - 1 do
s.undo_char[u.char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i) s.undo_char[u.char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i)
end end
@@ -1153,6 +1412,7 @@ function stb_text_redo(str, state)
end end
if r.insert_length ~= 0 then if r.insert_length ~= 0 then
-- easy case: need to insert n characters
r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, s.undo_char, r.char_storage, r.insert_length) r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, s.undo_char, r.char_storage, r.insert_length)
s.redo_char_point = s.redo_char_point + r.insert_length s.redo_char_point = s.redo_char_point + r.insert_length
end end
@@ -1199,6 +1459,7 @@ function stb_text_makeundo_replace(str, state, where, old_length, new_length)
end end
end end
-- reset the state to default
--- @param state STB_TexteditState --- @param state STB_TexteditState
--- @param is_single_line bool --- @param is_single_line bool
local function stb_textedit_clear_state(state, is_single_line) local function stb_textedit_clear_state(state, is_single_line)
@@ -1222,7 +1483,7 @@ return {
click = stb_textedit_click, click = stb_textedit_click,
drag = stb_textedit_drag, drag = stb_textedit_drag,
createundo = stb_text_createundo, createundo = stb_text_createundo,
initialize_state = stb_textedit_clear_state, initialize_state = stb_textedit_clear_state, -- API initialize
text = stb_textedit_text, text = stb_textedit_text,
key = stb_textedit_key, key = stb_textedit_key,
@@ -1236,3 +1497,43 @@ return {
HAS_SELECTION = STB_TEXT_HAS_SELECTION HAS_SELECTION = STB_TEXT_HAS_SELECTION
} }
-------------------------------------------------------------------------------
-- This software is available under 2 licenses -- choose whichever you prefer.
-------------------------------------------------------------------------------
-- ALTERNATIVE A - MIT License
-- Copyright (c) 2017 Sean Barrett
-- 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.
-------------------------------------------------------------------------------
-- ALTERNATIVE B - Public Domain (www.unlicense.org)
-- This is free and unencumbered software released into the public domain.
-- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-- software, either in source code form or as a compiled binary, for any purpose,
-- commercial or non-commercial, and by any means.
-- In jurisdictions that recognize copyright laws, the author or authors of this
-- software dedicate any and all copyright interest in the software to the public
-- domain. We make this dedication for the benefit of the public at large and to
-- the detriment of our heirs and successors. We intend this dedication to be an
-- overt act of relinquishment in perpetuity of all present and future rights to
-- this software under copyright law.
-- 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 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.
-------------------------------------------------------------------------------
+374 -54
View File
@@ -1,6 +1,164 @@
--- Dear ImGui --- Dear ImGui
-- This is a Lua port of original `imstb_truetype.h` -- This is a Lua port of original `imstb_truetype.h`
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_truetype.h 1.26.
-- Mostly fixing for compiler and static analyzer warnings.
-- Grep for [DEAR IMGUI] to find the changes.
-- stb_truetype.h - v1.26 - public domain
-- authored from 2009-2021 by Sean Barrett / RAD Game Tools
--
-- =======================================================================
--
-- NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES
--
-- This library does no range checking of the offsets found in the file,
-- meaning an attacker can use it to read arbitrary memory.
--
-- =======================================================================
--
-- This library processes TrueType files:
-- parse files
-- extract glyph metrics
-- extract glyph shapes
-- render glyphs to one-channel bitmaps with antialiasing (box filter)
--
-- Todo:
-- non-MS cmaps
-- crashproof on bad data
-- hinting? (no longer patented)
-- cleartype-style AA?
-- optimize: build edge-list directly from curves
-- optimize: rasterize directly from curves?
--
-- ADDITIONAL CONTRIBUTORS
--
-- Mikko Mononen: compound shape support, more cmap formats
-- Tor Andersson: kerning, subpixel rendering
-- Dougall Johnson: OpenType / Type 2 font handling
-- Daniel Ribeiro Maciel: basic GPOS-based kerning
--
-- Misc other:
-- Ryan Gordon
-- Simon Glass
-- github:IntellectualKitty
-- Imanol Celaya
-- Daniel Ribeiro Maciel
--
-- Bug/warning reports/fixes:
-- "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe
-- Cass Everitt Martins Mozeiko github:aloucks
-- stoiko (Haemimont Games) Cap Petschulat github:oyvindjam
-- Brian Hook Omar Cornut github:vassvik
-- Walter van Niftrik Ryan Griege
-- David Gow Peter LaValle
-- David Given Sergey Popov
-- Ivan-Assen Ivanov Giumo X. Clanjor
-- Anthony Pesch Higor Euripedes
-- Johan Duparc Thomas Fields
-- Hou Qiming Derek Vinyard
-- Rob Loach Cort Stratton
-- Kenney Phillis Jr. Brian Costabile
-- Ken Voskuil (kaesve)
--
-- USAGE
--
-- "Load" a font file from a memory buffer (you have to keep the buffer loaded)
-- stbtt_InitFont()
-- stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections
--
-- Character advance/positioning
-- stbtt_GetFontVMetrics()
--
-- Starting with version 1.06, the rasterizer was replaced with a new,
-- faster and generally-more-precise rasterizer. The new rasterizer more
-- accurately measures pixel coverage for anti-aliasing, except in the case
-- where multiple shapes overlap, in which case it overestimates the AA pixel
-- coverage. Thus, anti-aliasing of intersecting shapes may look wrong.
-- (This port only implements the new rasterizer.)
--
-- ADDITIONAL DOCUMENTATION
--
-- Some important concepts to understand to use this library:
--
-- Codepoint
-- Characters are defined by unicode codepoints, e.g. 65 is
-- uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is
-- the hiragana for "ma".
--
-- Glyph
-- A visual character shape (every codepoint is rendered as
-- some glyph)
--
-- Glyph index
-- A font-specific integer ID representing a glyph
--
-- Baseline
-- Glyph shapes are defined relative to a baseline, which is the
-- bottom of uppercase characters. Characters extend both above
-- and below the baseline.
--
-- Current Point
-- As you draw text to the screen, you keep track of a "current point"
-- which is the origin of each character. The current point's vertical
-- position is the baseline. Even "baked fonts" use this model.
--
-- Vertical Font Metrics
-- The vertical qualities of the font, used to vertically position
-- and space the characters. See docs for stbtt_GetFontVMetrics.
--
-- Font Size in Pixels or Points
-- The preferred interface for specifying font sizes in stb_truetype
-- is to specify how tall the font's vertical extent should be in pixels.
--
-- DETAILED USAGE:
--
-- Scale:
-- Select how high you want the font to be, in pixels.
-- Call ScaleForPixelHeight to compute a scale factor SF that will be
-- used by all other functions.
--
-- Current point:
-- Set the current point where the first character will appear. The
-- first character could extend left of the current point; this is font
-- dependent. You can either choose a current point that is the leftmost
-- point and hope, or add some padding, or check the bounding box or
-- left-side-bearing of the first character to be displayed and set
-- the current point based on that.
--
-- Displaying a character:
-- Compute the bounding box of the character. It will contain signed values
-- relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,
-- then the character should be displayed in the rectangle from
-- <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).
--
-- Advancing for the next character:
-- Call GlyphHMetrics, and compute 'current_point += SF * advance'.
--
--
-- ADVANCED USAGE
--
-- Quality:
--
-- - Use the functions with Subpixel at the end to allow your characters
-- to have subpixel positioning. Since the font is anti-aliased, not
-- hinted, this is very import for quality. (This is not possible with
-- baked fonts.)
--
-- Performance:
--
-- - Convert Unicode codepoints to glyph indexes and operate on the glyphs;
-- if you don't do this, stb_truetype is forced to do the conversion on
-- every call.
--
-- NOTES
--
-- The system uses the raw data found in the .ttf file without changing it
-- and without building auxiliary data structures. This is a bit inefficient
-- on little-endian systems (the data is big-endian), but assuming you're
-- caching the bitmaps or glyph shapes this shouldn't be a big deal.
--- STBTT_RASTERIZER_VERSION == 2 --- STBTT_RASTERIZER_VERSION == 2
local stbtt_fontinfo local stbtt_fontinfo
@@ -21,7 +179,7 @@ local stbtt_FindGlyphIndex
local STBTT_MAX_OVERSAMPLE = 8 local STBTT_MAX_OVERSAMPLE = 8
--- @enum STBTT_PLATFORM_ID --- @enum STBTT_PLATFORM_ID
local STBTT_PLATFORM_ID = { local STBTT_PLATFORM_ID = { -- platformID
UNICODE = 0, UNICODE = 0,
MAC = 1, MAC = 1,
ISO = 2, ISO = 2,
@@ -29,7 +187,7 @@ local STBTT_PLATFORM_ID = {
} }
--- @enum STBTT_MS_EID --- @enum STBTT_MS_EID
local STBTT_MS_EID = { local STBTT_MS_EID = { -- encodingID for STBTT_PLATFORM_ID_MICROSOFT
SYMBOL = 0, SYMBOL = 0,
UNICODE_BMP = 1, UNICODE_BMP = 1,
SHIFTJIS = 2, SHIFTJIS = 2,
@@ -75,6 +233,7 @@ local stbtt_int8 = ImS8
local stbtt_uint8 = ImU8 local stbtt_uint8 = ImU8
local unsigned_char = stbtt_uint8 local unsigned_char = stbtt_uint8
-- private structure
--- @class stbtt__buf --- @class stbtt__buf
--- @field data? table # 1-based byte table --- @field data? table # 1-based byte table
--- @field offset int # Every access to `data` will be offset by this --- @field offset int # Every access to `data` will be offset by this
@@ -93,6 +252,8 @@ local function stbtt__buf(initial_offset)
} }
end end
-- The following structure is defined publicly so you can declare one on
-- the stack or as a global or etc, but you should treat it as opaque.
--- @class stbtt_fontinfo --- @class stbtt_fontinfo
--- @field userdata any --- @field userdata any
--- @field data table # pointer to .ttf file --- @field data table # pointer to .ttf file
@@ -283,6 +444,7 @@ local function stbtt__new_active(e, off_x, start_point)
return z return z
end end
-- @TODO: don't expose this structure
--- @class stbtt__bitmap --- @class stbtt__bitmap
--- @field w int --- @field w int
--- @field h int --- @field h int
@@ -496,7 +658,7 @@ end
------------------------------------- -------------------------------------
------------------------------------- -------------------------------------
--- ---
--- Accessors to parse data from file --- accessors to parse data from file
--- ---
--- ---
@@ -535,14 +697,16 @@ local function stbtt_tag4(p, c0, c1, c2, c3, offset) offset = offset or 0; retur
local function stbtt_tag(p, str, offset) local c0, c1, c2, c3 = string.byte(str, 1, 4); return stbtt_tag4(p, c0, c1, c2, c3, offset) end local function stbtt_tag(p, str, offset) local c0, c1, c2, c3 = string.byte(str, 1, 4); return stbtt_tag4(p, c0, c1, c2, c3, offset) end
local function stbtt__isfont(font) local function stbtt__isfont(font)
if stbtt_tag4(font, 49, 0, 0, 0) then return true end -- check the version number
if stbtt_tag(font, "typ1") then return true end if stbtt_tag4(font, 49, 0, 0, 0) then return true end -- TrueType 1
if stbtt_tag(font, "OTTO") then return true end if stbtt_tag(font, "typ1") then return true end -- TrueType with type 1 font -- we don't support this!
if stbtt_tag4(font, 0, 1, 0, 0) then return true end if stbtt_tag(font, "OTTO") then return true end -- OpenType with CFF
if stbtt_tag(font, "true") then return true end if stbtt_tag4(font, 0, 1, 0, 0) then return true end -- OpenType 1.0
if stbtt_tag(font, "true") then return true end -- Apple specification for TrueType fonts
return false return false
end end
-- @OPTIMIZE: binary search
local function stbtt__find_table(data, font_start, tag) local function stbtt__find_table(data, font_start, tag)
local num_tables = ttUSHORT(data, font_start + 4) local num_tables = ttUSHORT(data, font_start + 4)
local tabledir = font_start + 12 local tabledir = font_start + 12
@@ -556,11 +720,14 @@ local function stbtt__find_table(data, font_start, tag)
end end
local function stbtt_GetFontOffsetForIndex_internal(font_collection, index) local function stbtt_GetFontOffsetForIndex_internal(font_collection, index)
-- if it's just a font, there's only one valid index
if stbtt__isfont(font_collection) then if stbtt__isfont(font_collection) then
if index == 0 then return 0 else return -1 end if index == 0 then return 0 else return -1 end
end end
-- check if it's a TTC
if stbtt_tag(font_collection, "ttcf") then if stbtt_tag(font_collection, "ttcf") then
-- version 1?
if ttULONG(font_collection, 4) == 0x00010000 or ttULONG(font_collection, 4) == 0x00020000 then if ttULONG(font_collection, 4) == 0x00010000 or ttULONG(font_collection, 4) == 0x00020000 then
local n = ttLONG(font_collection, 8) local n = ttLONG(font_collection, 8)
if index >= n then if index >= n then
@@ -598,23 +765,25 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.fontstart = fontstart info.fontstart = fontstart
info.cff = stbtt__new_buf(nil, 0) info.cff = stbtt__new_buf(nil, 0)
cmap = stbtt__find_table(data, fontstart, "cmap") cmap = stbtt__find_table(data, fontstart, "cmap") -- required
info.loca = stbtt__find_table(data, fontstart, "loca") info.loca = stbtt__find_table(data, fontstart, "loca") -- required
info.head = stbtt__find_table(data, fontstart, "head") info.head = stbtt__find_table(data, fontstart, "head") -- required
info.glyf = stbtt__find_table(data, fontstart, "glyf") info.glyf = stbtt__find_table(data, fontstart, "glyf") -- required
info.hhea = stbtt__find_table(data, fontstart, "hhea") info.hhea = stbtt__find_table(data, fontstart, "hhea") -- required
info.hmtx = stbtt__find_table(data, fontstart, "hmtx") info.hmtx = stbtt__find_table(data, fontstart, "hmtx") -- required
info.kern = stbtt__find_table(data, fontstart, "kern") info.kern = stbtt__find_table(data, fontstart, "kern") -- not required
info.gpos = stbtt__find_table(data, fontstart, "GPOS") info.gpos = stbtt__find_table(data, fontstart, "GPOS") -- not required
if (cmap == 0 or info.head == 0 or info.hhea == 0 or info.hmtx == 0) then if (cmap == 0 or info.head == 0 or info.hhea == 0 or info.hmtx == 0) then
return false return false
end end
if info.glyf ~= 0 then if info.glyf ~= 0 then
-- required for truetype
if info.loca == 0 then if info.loca == 0 then
return false return false
end end
else else
-- initialization for CFF / Type2 fonts (OTF)
local cff = stbtt__find_table(data, fontstart, "CFF ") local cff = stbtt__find_table(data, fontstart, "CFF ")
if cff == 0 then if cff == 0 then
return false return false
@@ -623,14 +792,16 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.fontdicts = stbtt__new_buf(nil, 0) info.fontdicts = stbtt__new_buf(nil, 0)
info.fdselect = stbtt__new_buf(nil, 0) info.fdselect = stbtt__new_buf(nil, 0)
info.cff = stbtt__new_buf(data, 64 * 1024 * 1024, cff) -- TODO: this should use size from table -- @TODO this should use size from table (not 512MB)
info.cff = stbtt__new_buf(data, 64 * 1024 * 1024, cff) -- PORT: 64MB here instead of 512MB
local b = info.cff local b = info.cff
-- read the header -- read the header
stbtt__buf_skip(b, 2) stbtt__buf_skip(b, 2)
stbtt__buf_seek(b, stbtt__buf_get8(b)) stbtt__buf_seek(b, stbtt__buf_get8(b)) -- hdrsize
-- TODO: the name INDEX could list multiple fonts, but we just use the first one. -- @TODO the name INDEX could list multiple fonts,
-- but we just use the first one.
stbtt__cff_get_index(b) -- name INDEX stbtt__cff_get_index(b) -- name INDEX
local topdictidx = stbtt__cff_get_index(b) local topdictidx = stbtt__cff_get_index(b)
local topdict = stbtt__cff_index_get(topdictidx, 0) local topdict = stbtt__cff_index_get(topdictidx, 0)
@@ -643,6 +814,7 @@ local function stbtt_InitFont_internal(info, data, fontstart)
local fdselectoff = stbtt__dict_get_ints(topdict, bit32.bor(0x100, 37), 1) local fdselectoff = stbtt__dict_get_ints(topdict, bit32.bor(0x100, 37), 1)
info.subrs = stbtt__get_subrs(b, topdict) info.subrs = stbtt__get_subrs(b, topdict)
-- we only support Type 2 charstrings
if cstype[1] ~= 2 then if cstype[1] ~= 2 then
return false return false
end end
@@ -651,6 +823,7 @@ local function stbtt_InitFont_internal(info, data, fontstart)
end end
if fdarrayoff[1] ~= 0 then if fdarrayoff[1] ~= 0 then
-- looks like a CID font
if fdselectoff[1] == 0 then if fdselectoff[1] == 0 then
return false return false
end end
@@ -673,19 +846,26 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.svg = -1 info.svg = -1
-- find a cmap encoding table we understand *now* to avoid searching
-- later. (todo: could make this installable)
-- the same regardless of glyph.
numTables = ttUSHORT(data, cmap + 2) numTables = ttUSHORT(data, cmap + 2)
info.index_map = 0 info.index_map = 0
for i = 0, numTables - 1 do for i = 0, numTables - 1 do
local encoding_record = cmap + 4 + 8 * i local encoding_record = cmap + 4 + 8 * i
-- find an encoding we understand:
local platform_id = ttUSHORT(data, encoding_record) local platform_id = ttUSHORT(data, encoding_record)
if platform_id == STBTT_PLATFORM_ID.MICROSOFT then if platform_id == STBTT_PLATFORM_ID.MICROSOFT then
local ms_eid = ttUSHORT(data, encoding_record + 2) local ms_eid = ttUSHORT(data, encoding_record + 2)
if ms_eid == STBTT_MS_EID.UNICODE_BMP or ms_eid == STBTT_MS_EID.UNICODE_FULL then if ms_eid == STBTT_MS_EID.UNICODE_BMP or ms_eid == STBTT_MS_EID.UNICODE_FULL then
-- MS/Unicode
info.index_map = cmap + ttULONG(data, encoding_record + 4) info.index_map = cmap + ttULONG(data, encoding_record + 4)
end end
elseif platform_id == STBTT_PLATFORM_ID.UNICODE then elseif platform_id == STBTT_PLATFORM_ID.UNICODE then
-- Mac/iOS has these
-- all the encodingIDs are unicode, so we don't bother to check it
info.index_map = cmap + ttULONG(data, encoding_record + 4) info.index_map = cmap + ttULONG(data, encoding_record + 4)
end end
end end
@@ -697,6 +877,11 @@ local function stbtt_InitFont_internal(info, data, fontstart)
return true return true
end end
-- If you're going to perform multiple operations on the same character
-- and you want a speed-up, call this function with the character you're
-- going to process, then use glyph-based functions instead of the
-- codepoint-based functions.
-- Returns 0 if the character codepoint is not defined in the font.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param unicode_codepoint int --- @param unicode_codepoint int
function stbtt_FindGlyphIndex(info, unicode_codepoint) function stbtt_FindGlyphIndex(info, unicode_codepoint)
@@ -704,7 +889,7 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
local index_map = info.index_map local index_map = info.index_map
local format = ttUSHORT(data, index_map + 0) local format = ttUSHORT(data, index_map + 0)
if format == 0 then if format == 0 then -- apple byte encoding
local bytes = ttUSHORT(data, index_map + 2) local bytes = ttUSHORT(data, index_map + 2)
if unicode_codepoint < bytes - 6 then if unicode_codepoint < bytes - 6 then
return ttBYTE(data, index_map + 6 + unicode_codepoint) return ttBYTE(data, index_map + 6 + unicode_codepoint)
@@ -718,14 +903,15 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
end end
return 0 return 0
elseif format == 2 then elseif format == 2 then
STBTT_assert(false) -- TODO: high-byte mapping for japanese/chinese/korean STBTT_assert(false) -- @TODO: high-byte mapping for japanese/chinese/korean
return 0 return 0
elseif format == 4 then elseif format == 4 then -- standard mapping for windows fonts: binary search collection of ranges
local segcount = bit32.rshift(ttUSHORT(data, index_map + 6), 1) local segcount = bit32.rshift(ttUSHORT(data, index_map + 6), 1)
local searchRange = bit32.rshift(ttUSHORT(data, index_map + 8), 1) local searchRange = bit32.rshift(ttUSHORT(data, index_map + 8), 1)
local entrySelector = ttUSHORT(data, index_map + 10) local entrySelector = ttUSHORT(data, index_map + 10)
local rangeShift = bit32.rshift(ttUSHORT(data, index_map + 12), 1) local rangeShift = bit32.rshift(ttUSHORT(data, index_map + 12), 1)
-- do a binary search of the segments
local endCount = index_map + 14 local endCount = index_map + 14
local search = endCount local search = endCount
@@ -733,10 +919,13 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
return 0 return 0
end end
-- they lie from endCount .. endCount + segCount
-- but searchRange is the nearest power of two, so...
if unicode_codepoint >= ttUSHORT(data, search + rangeShift * 2) then if unicode_codepoint >= ttUSHORT(data, search + rangeShift * 2) then
search = search + rangeShift * 2 search = search + rangeShift * 2
end end
-- now decrement to bias correctly to find smallest
search = search - 2 search = search - 2
while entrySelector ~= 0 do while entrySelector ~= 0 do
searchRange = bit32.rshift(searchRange, 1) searchRange = bit32.rshift(searchRange, 1)
@@ -768,8 +957,9 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
local ngroups = ttULONG(data, index_map + 12) local ngroups = ttULONG(data, index_map + 12)
local low = 0 local low = 0
local high = ngroups local high = ngroups
-- Binary search the right group.
while low < high do while low < high do
local mid = low + bit32.rshift(high - low, 1) local mid = low + bit32.rshift(high - low, 1) -- rounds down, so low <= mid < high
local start_char = ttULONG(data, index_map + 16 + mid * 12) local start_char = ttULONG(data, index_map + 16 + mid * 12)
local end_char = ttULONG(data, index_map + 16 + mid * 12 + 4) local end_char = ttULONG(data, index_map + 16 + mid * 12 + 4)
if unicode_codepoint < start_char then if unicode_codepoint < start_char then
@@ -785,10 +975,10 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
end end
end end
end end
return 0 return 0 -- not found
end end
-- TODO: -- @TODO
STBTT_assert(false) STBTT_assert(false)
return 0 return 0
end end
@@ -810,8 +1000,8 @@ end
local function stbtt__GetGlyfOffset(info, glyph_index) local function stbtt__GetGlyfOffset(info, glyph_index)
STBTT_assert(info.cff.size == 0) STBTT_assert(info.cff.size == 0)
if glyph_index >= info.numGlyphs then return -1 end if glyph_index >= info.numGlyphs then return -1 end -- glyph index out of range
if info.indexToLocFormat >= 2 then return -1 end if info.indexToLocFormat >= 2 then return -1 end -- unknown index->glyph map format
local g1, g2 local g1, g2
if info.indexToLocFormat == 0 then if info.indexToLocFormat == 0 then
@@ -822,9 +1012,10 @@ local function stbtt__GetGlyfOffset(info, glyph_index)
g2 = info.glyf + ttULONG(info.data, info.loca + glyph_index * 4 + 4) g2 = info.glyf + ttULONG(info.data, info.loca + glyph_index * 4 + 4)
end end
if g1 == g2 then return -1 else return g1 end if g1 == g2 then return -1 else return g1 end -- if length is 0, return -1
end end
-- Gets the bounding box of the visible part of the glyph, in unscaled coordinates
--- @return integer, integer?, integer?, integer?, integer? --- @return integer, integer?, integer?, integer?, integer?
local function stbtt_GetGlyphBox(info, glyph_index) local function stbtt_GetGlyphBox(info, glyph_index)
local n, x0, y0, x1, y1 local n, x0, y0, x1, y1
@@ -896,7 +1087,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
local points = (endPtsOfContours + numberOfContours * 2 + 2 + ins) + 1 -- 1-based idx local points = (endPtsOfContours + numberOfContours * 2 + 2 + ins) + 1 -- 1-based idx
local n = 1 + ttUSHORT(data, endPtsOfContours + numberOfContours * 2 - 2) local n = 1 + ttUSHORT(data, endPtsOfContours + numberOfContours * 2 - 2)
local m = n + 2 * numberOfContours local m = n + 2 * numberOfContours -- a loose bound on how many vertices we might need
vertices = {} vertices = {}
for i = 1, m do vertices[i] = stbtt_vertex() end for i = 1, m do vertices[i] = stbtt_vertex() end
@@ -907,7 +1098,11 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
local next_move = 0 local next_move = 0
local flagcount = 0 local flagcount = 0
local off = m - n -- in first pass, we load uninterpreted data into the allocated array
-- above, shifted to the end of the array so we won't overwrite it when
-- we create our final data starting from the front
local off = m - n -- starting offset for uninterpreted data, regardless of how m ends up being calculated
-- first load flags -- first load flags
local flags = 0 local flags = 0
@@ -934,7 +1129,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
if bit32.band(flags, 2) ~= 0 then if bit32.band(flags, 2) ~= 0 then
local dx = data[points] local dx = data[points]
points = points + 1 points = points + 1
if bit32.band(flags, 16) ~= 0 then if bit32.band(flags, 16) ~= 0 then -- ???
x = x + dx x = x + dx
else else
x = x - dx x = x - dx
@@ -956,7 +1151,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
if bit32.band(flags, 4) ~= 0 then if bit32.band(flags, 4) ~= 0 then
local dy = data[points] local dy = data[points]
points = points + 1 points = points + 1
if bit32.band(flags, 32) ~= 0 then if bit32.band(flags, 32) ~= 0 then -- ???
y = y + dy y = y + dy
else else
y = y - dy y = y - dy
@@ -987,15 +1182,19 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
-- now start the new one -- now start the new one
start_off = (bit32.band(flags, 1) == 0) start_off = (bit32.band(flags, 1) == 0)
if start_off then if start_off then
-- if we start off with an off-curve point, then when we need to find a point on the curve
-- where we can start, and we need to save some state for when we wraparound.
scx = x scx = x
scy = y scy = y
if bit32.band(vertices[off + i + 1].type, 1) == 0 then if bit32.band(vertices[off + i + 1].type, 1) == 0 then
-- next point is also a curve point, so interpolate an on-point curve
sx = bit32.rshift(x + stbtt_int32(vertices[off + i + 1].x), 1) sx = bit32.rshift(x + stbtt_int32(vertices[off + i + 1].x), 1)
sy = bit32.rshift(y + stbtt_int32(vertices[off + i + 1].y), 1) sy = bit32.rshift(y + stbtt_int32(vertices[off + i + 1].y), 1)
else else
-- otherwise just use the next point as our start point
sx = stbtt_int32(vertices[off + i + 1].x) sx = stbtt_int32(vertices[off + i + 1].x)
sy = stbtt_int32(vertices[off + i + 1].y) sy = stbtt_int32(vertices[off + i + 1].y)
i = i + 1 i = i + 1 -- we're using point i+1 as the starting point, so skip it
end end
else else
sx = x sx = x
@@ -1007,8 +1206,8 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
next_move = 1 + ttUSHORT(data, endPtsOfContours + j * 2) next_move = 1 + ttUSHORT(data, endPtsOfContours + j * 2)
j = j + 1 j = j + 1
else else
if bit32.band(flags, 1) == 0 then if bit32.band(flags, 1) == 0 then -- if it's a curve
if was_off then if was_off then -- two off-curve control points in a row means interpolate an on-curve midpoint
stbtt_setvertex(vertices[num_vertices + 1], STBTT_VTX.CURVE, bit32.rshift(cx + x, 1), bit32.rshift(cy + y, 1), cx, cy) stbtt_setvertex(vertices[num_vertices + 1], STBTT_VTX.CURVE, bit32.rshift(cx + x, 1), bit32.rshift(cy + y, 1), cx, cy)
num_vertices = num_vertices + 1 num_vertices = num_vertices + 1
end end
@@ -1032,6 +1231,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy)
elseif numberOfContours < 0 then elseif numberOfContours < 0 then
-- Compound shapes.
local more = 1 local more = 1
local comp = g + 10 local comp = g + 10
num_vertices = 0 num_vertices = 0
@@ -1047,8 +1247,8 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
flags = ttSHORT(data, comp) comp = comp + 2 flags = ttSHORT(data, comp) comp = comp + 2
gidx = ttSHORT(data, comp) comp = comp + 2 gidx = ttSHORT(data, comp) comp = comp + 2
if bit32.band(flags, 2) ~= 0 then if bit32.band(flags, 2) ~= 0 then -- XY values
if bit32.band(flags, 1) ~= 0 then if bit32.band(flags, 1) ~= 0 then -- shorts
mtx[5] = ttSHORT(data, comp) comp = comp + 2 mtx[5] = ttSHORT(data, comp) comp = comp + 2
mtx[6] = ttSHORT(data, comp) comp = comp + 2 mtx[6] = ttSHORT(data, comp) comp = comp + 2
else else
@@ -1056,7 +1256,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
mtx[6] = ttCHAR(data, comp) comp = comp + 1 mtx[6] = ttCHAR(data, comp) comp = comp + 1
end end
else else
-- TODO: handle matching point -- @TODO handle matching point
STBTT_assert(false) STBTT_assert(false)
end end
@@ -1083,11 +1283,14 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
mtx[4] = ttSHORT(data, comp) / 16384.0 comp = comp + 2 mtx[4] = ttSHORT(data, comp) / 16384.0 comp = comp + 2
end end
-- Find transformation scales.
m = STBTT_sqrt(mtx[1] * mtx[1] + mtx[2] * mtx[2]) m = STBTT_sqrt(mtx[1] * mtx[1] + mtx[2] * mtx[2])
n = STBTT_sqrt(mtx[3] * mtx[3] + mtx[4] * mtx[4]) n = STBTT_sqrt(mtx[3] * mtx[3] + mtx[4] * mtx[4])
-- Get indexed glyph.
comp_num_vertices, comp_verts = stbtt_GetGlyphShape(info, gidx) comp_num_vertices, comp_verts = stbtt_GetGlyphShape(info, gidx)
if comp_num_vertices > 0 then if comp_num_vertices > 0 then
-- Transform vertices.
for i = 1, comp_num_vertices do for i = 1, comp_num_vertices do
local v = comp_verts[i] local v = comp_verts[i]
local x = v.x local x = v.x
@@ -1100,6 +1303,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
v.cy = stbtt_int16(n * (mtx[2] * x + mtx[4] * y + mtx[6])) v.cy = stbtt_int16(n * (mtx[2] * x + mtx[4] * y + mtx[6]))
end end
-- Append vertices.
if num_vertices == 0 then -- LUA: No malloc if num_vertices == 0 then -- LUA: No malloc
vertices = comp_verts vertices = comp_verts
else else
@@ -1111,6 +1315,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
num_vertices = num_vertices + comp_num_vertices num_vertices = num_vertices + comp_num_vertices
end end
-- More components ?
more = bit32.band(flags, bit32.lshift(1, 5)) more = bit32.band(flags, bit32.lshift(1, 5))
end end
end end
@@ -1266,7 +1471,7 @@ local function stbtt__run_charstring(info, glyph_index, c)
clear_stack = 1 clear_stack = 1
b0 = stbtt__buf_get8(b) b0 = stbtt__buf_get8(b)
-- TODO: implement hinting -- @TODO implement hinting
if b0 == 0x13 or b0 == 0x14 then -- hintmask or cntrmask if b0 == 0x13 or b0 == 0x14 then -- hintmask or cntrmask
if in_header then if in_header then
maskbits = maskbits + trunc(sp / 2) -- implicit "vstem" maskbits = maskbits + trunc(sp / 2) -- implicit "vstem"
@@ -1415,6 +1620,8 @@ local function stbtt__run_charstring(info, glyph_index, c)
elseif b0 == 0x0C then -- two-byte escape elseif b0 == 0x0C then -- two-byte escape
local b1 = stbtt__buf_get8(b) local b1 = stbtt__buf_get8(b)
-- @TODO These "flex" implementations ignore the flex-depth and resolution,
-- and always draw beziers.
if b1 == 0x22 then -- hflex if b1 == 0x22 then -- hflex
if sp < 7 then return STBTT__CSERR("hflex stack") end if sp < 7 then return STBTT__CSERR("hflex stack") end
local dx1 = s[0] local dx1 = s[0]
@@ -1547,6 +1754,15 @@ function stbtt__GetGlyphInfoT2(info, glyph_index)
return ((r ~= 0) and c.num_vertices or 0), x0, y0, x1, y1 return ((r ~= 0) and c.num_vertices or 0), x0, y0, x1, y1
end end
-- returns # of vertices and fills *vertices with the pointer to them
-- these are expressed in "unscaled" coordinates
--
-- The shape is a series of contours. Each one starts with
-- a STBTT_moveto, then consists of a series of mixed
-- STBTT_lineto and STBTT_curveto segments. A lineto
-- draws a line from previous endpoint to its x,y; a curveto
-- draws a quadratic bezier from previous endpoint to
-- its x,y, using cx,cy as the bezier control point.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param glyph_index int --- @param glyph_index int
function stbtt_GetGlyphShape(info, glyph_index) function stbtt_GetGlyphShape(info, glyph_index)
@@ -1557,6 +1773,9 @@ function stbtt_GetGlyphShape(info, glyph_index)
end end
end end
-- leftSideBearing is the offset from the current horizontal position to the left edge of the character
-- advanceWidth is the offset from the current horizontal position to the next horizontal position
-- these are expressed in unscaled coordinates
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param glyph_index int --- @param glyph_index int
function stbtt_GetGlyphHMetrics(info, glyph_index) function stbtt_GetGlyphHMetrics(info, glyph_index)
@@ -1574,6 +1793,12 @@ function stbtt_GetGlyphHMetrics(info, glyph_index)
return advanceWidth, leftSideBearing return advanceWidth, leftSideBearing
end end
-- ascent is the coordinate above the baseline the font extends; descent
-- is the coordinate below the baseline the font extends (i.e. it is typically negative)
-- lineGap is the spacing between one row's descent and the next row's ascent...
-- so you should advance the vertical position by "ascent - descent + lineGap"
-- these are expressed in unscaled coordinates, so you must multiply by
-- the scale factor for a given size
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
function stbtt_GetFontVMetrics(info) function stbtt_GetFontVMetrics(info)
local ascent = ttSHORT(info.data, info.hhea + 4) local ascent = ttSHORT(info.data, info.hhea + 4)
@@ -1583,6 +1808,12 @@ function stbtt_GetFontVMetrics(info)
return ascent, descent, lineGap return ascent, descent, lineGap
end end
-- computes a scale factor to produce a font whose "height" is 'pixels' tall.
-- Height is measured as the distance from the highest ascender to the lowest
-- descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
-- and computing:
-- scale = pixels / (ascent - descent)
-- so if you prefer to measure height by the ascent only, use a similar calculation.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param height float --- @param height float
function stbtt_ScaleForPixelHeight(info, height) function stbtt_ScaleForPixelHeight(info, height)
@@ -1593,10 +1824,12 @@ end
------------------------------------ ------------------------------------
------------------------------------ ------------------------------------
--- ---
--- Antialiasing software rasterizer --- antialiasing software rasterizer
--- ---
--- ---
-- same as stbtt_GetGlyphBitmapBox, but you can specify a subpixel
-- shift for the character
--- @param font stbtt_fontinfo --- @param font stbtt_fontinfo
--- @param glyph int --- @param glyph int
--- @param scale_x float --- @param scale_x float
@@ -1624,6 +1857,11 @@ function stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y, shift_x,
return ix0, iy0, ix1, iy1 return ix0, iy0, ix1, iy1
end end
-- get the bbox of the bitmap centered around the glyph origin; so the
-- bitmap width is ix1-ix0, height is iy1-iy0, and location to place
-- the bitmap top left is (leftSideBearing*scale,iy0).
-- (Note that the bitmap uses y-increases-down, but the shape uses
-- y-increases-up, so GlyphBitmapBox and GlyphBox are inverted.)
--- @param font stbtt_fontinfo --- @param font stbtt_fontinfo
--- @param glyph int --- @param glyph int
--- @param scale_x float --- @param scale_x float
@@ -1646,6 +1884,8 @@ end
--- stbtt_MakeGlyphBitmap --- stbtt_MakeGlyphBitmap
--- stbtt_GetGlyphBitmapSubpixel --- stbtt_GetGlyphBitmapSubpixel
-- the edge passed in here does not cross the vertical line at x or the vertical line at x+1
-- (i.e. it has already been clipped to those)
--- @param scanline float[] --- @param scanline float[]
--- @param x int --- @param x int
--- @param e stbtt__active_edge --- @param e stbtt__active_edge
@@ -1687,7 +1927,7 @@ local function stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1)
-- do nothing -- do nothing
else else
STBTT_assert(x0 >= x and x0 <= x + 1 and x1 >= x and x1 <= x + 1) STBTT_assert(x0 >= x and x0 <= x + 1 and x1 >= x and x1 <= x + 1)
scanline[x] = scanline[x] + e.direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2) scanline[x] = scanline[x] + e.direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2) -- coverage = 1 - average x position
end end
end end
@@ -1725,6 +1965,7 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
while e do while e do
-- brute force every pixel -- brute force every pixel
-- compute intersection points with top & bottom -- compute intersection points with top & bottom
STBTT_assert(e.ey >= y_top) STBTT_assert(e.ey >= y_top)
@@ -1768,8 +2009,8 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
if x_top >= 0 and x_bottom >= 0 and x_top < len and x_bottom < len then if x_top >= 0 and x_bottom >= 0 and x_top < len and x_bottom < len then
-- from here on, we don't have to range check x values -- from here on, we don't have to range check x values
if trunc(x_top) == trunc(x_bottom) then if trunc(x_top) == trunc(x_bottom) then
-- simple case, only spans one pixel
local height local height
-- simple case, only spans one pixel
local x = trunc(x_top) local x = trunc(x_top)
height = (sy1 - sy0) * e.direction height = (sy1 - sy0) * e.direction
STBTT_assert(x >= 0 and x < len) STBTT_assert(x >= 0 and x < len)
@@ -1801,8 +2042,25 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
-- compute intersection with y axis at x2 -- compute intersection with y axis at x2
y_final = y_top + dy * (x2 - x0) y_final = y_top + dy * (x2 - x0)
-- x1 x_top x2 x_bottom
-- y_top +------|-----+------------+------------+--------|---+------------+
-- | | | | | |
-- | | | | | |
-- sy0 | Txxxxx|............|............|............|............|
-- y_crossing | *xxxxx.......|............|............|............|
-- | | xxxxx..|............|............|............|
-- | | /- xx*xxxx........|............|............|
-- | | dy < | xxxxxx..|............|............|
-- y_final | | \- | xx*xxx.........|............|
-- sy1 | | | | xxxxxB...|............|
-- | | | | | |
-- | | | | | |
-- y_bottom +------------+------------+------------+------------+------------+
--
-- goal is to measure the area covered by '.' in each pixel
-- if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 -- if x2 is right at the right edge of x1, y_crossing can blow up, github #1057
-- TODO: maybe test against sy1 rather than y_bottom? -- @TODO: maybe test against sy1 rather than y_bottom?
if y_crossing > y_bottom then if y_crossing > y_bottom then
y_crossing = y_bottom y_crossing = y_bottom
end end
@@ -1824,6 +2082,16 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
end end
end end
-- in second pixel, area covered by line segment found in first pixel
-- is always a rectangle 1 wide * the height of that line segment; this
-- is exactly what the variable 'area' stores. it also gets a contribution
-- from the line segment within it. the THIRD pixel will get the first
-- pixel's rectangle contribution, the second pixel's rectangle contribution,
-- and its own contribution. the 'own contribution' is the same in every pixel except
-- the leftmost and rightmost, a trapezoid that slides down in each pixel.
-- the second pixel's contribution to the third pixel will be the
-- rectangle 1 wide times the height change in the second pixel, which is dy.
step = sign * dy * 1 -- dy is dy/dx, change in y for every 1 change in x, step = sign * dy * 1 -- dy is dy/dx, change in y for every 1 change in x,
-- which multiplied by 1-pixel-width is how much pixel area changes for each step in x -- which multiplied by 1-pixel-width is how much pixel area changes for each step in x
-- so the area advances by 'step' every time -- so the area advances by 'step' every time
@@ -1843,7 +2111,27 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
scanline_fill[x2 + 1] = scanline_fill[x2 + 1] + sign * (sy1 - sy0) scanline_fill[x2 + 1] = scanline_fill[x2 + 1] + sign * (sy1 - sy0)
end end
else else
-- if edge goes outside of box we're drawing, we require
-- clipping logic. since this does not match the intended use
-- of this library, we use a different, very slow brute
-- force implementation
-- note though that this does happen some of the time because
-- x_top and x_bottom can be extrapolated at the top & bottom of
-- the shape and actually lie outside the bounding box
for x = 0, len - 1 do for x = 0, len - 1 do
-- cases:
--
-- there can be up to two intersections with the pixel. any intersection
-- with left or right edges can be handled by splitting into two (or three)
-- regions. intersections with top & bottom do not necessitate case-wise logic.
--
-- the old way of doing this found the intersections with the left & right edges,
-- then used some simple logic to produce up to three segments in sorted order
-- from top-to-bottom. however, this had a problem: if an x edge was epsilon
-- across the x border, then the corresponding y position might not be distinct
-- from the other y segment, and it might ignored as an empty segment. to avoid
-- that, we need to explicitly produce segments based on x positions.
-- rename variables to clearly-defined pairs -- rename variables to clearly-defined pairs
local y0 = y_top local y0 = y_top
local x1 = x local x1 = x
@@ -1887,6 +2175,7 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
end end
end end
-- directly AA rasterize edges w/o supersampling
--- @param result stbtt__bitmap --- @param result stbtt__bitmap
--- @param e stbtt__edge --- @param e stbtt__edge
--- @param n int --- @param n int
@@ -1914,28 +2203,33 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
for i = 0, result.w - 1 do scanline[i] = 0 end for i = 0, result.w - 1 do scanline[i] = 0 end
for i = 0, result.w do scanline2[i] = 0 end for i = 0, result.w do scanline2[i] = 0 end
-- update all active edges;
-- remove all active edges that terminate before the top of this scanline
local prev = nil local prev = nil
local curr = active local curr = active
while curr do while curr do
local next = curr.next local next = curr.next
if curr.ey <= scan_y_top then if curr.ey <= scan_y_top then
if prev then prev.next = next if prev then prev.next = next -- delete from list
else active = next end else active = next end
curr.direction = 0 curr.direction = 0
else else
prev = curr prev = curr -- advance through list
end end
curr = next curr = next
end end
-- insert all edges that start before the bottom of this scanline
local edge = e[p] local edge = e[p]
while edge.y0 <= scan_y_bottom do while edge.y0 <= scan_y_bottom do
if edge.y0 ~= edge.y1 then if edge.y0 ~= edge.y1 then
local z = stbtt__new_active(edge, off_x, scan_y_top) local z = stbtt__new_active(edge, off_x, scan_y_top)
if j == 0 and off_y ~= 0 and z.ey < scan_y_top then if j == 0 and off_y ~= 0 and z.ey < scan_y_top then
-- this can happen due to subpixel positioning and some kind of fp rounding error i think
z.ey = scan_y_top z.ey = scan_y_top
end end
STBTT_assert(z.ey >= scan_y_top) STBTT_assert(z.ey >= scan_y_top) -- if we get really unlucky a tiny bit of an edge can be out of bounds
-- insert at front
z.next = active z.next = active
active = z active = z
end end
@@ -1943,6 +2237,7 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
edge = e[p] edge = e[p]
end end
-- now process all active edges
if active then if active then
stbtt__fill_active_edges_new(scanline, scanline2, result.w, active, scan_y_top) stbtt__fill_active_edges_new(scanline, scanline2, result.w, active, scan_y_top)
end end
@@ -1961,10 +2256,11 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
end end
end end
-- advance all the edges
step = active step = active
while step do while step do
step.fx = step.fx + step.fdx step.fx = step.fx + step.fdx -- advance to position for current scanline
step = step.next step = step.next -- advance through list
end end
y = y + 1 y = y + 1
@@ -1999,6 +2295,7 @@ end
local function stbtt__rasterize(result, pts, wcount, windings, scale_x, scale_y, shift_x, shift_y, off_x, off_y, invert) local function stbtt__rasterize(result, pts, wcount, windings, scale_x, scale_y, shift_x, shift_y, off_x, off_y, invert)
local y_scale_inv = (invert) and -scale_y or scale_y local y_scale_inv = (invert) and -scale_y or scale_y
local vsubsample = 1 -- STBTT_RASTERIZER_VERSION == 2 local vsubsample = 1 -- STBTT_RASTERIZER_VERSION == 2
-- vsubsample should divide 255 evenly; otherwise we won't reach full opacity
-- now we have to blow out the windings into explicit edge lists -- now we have to blow out the windings into explicit edge lists
local n = 0 local n = 0
@@ -2066,8 +2363,7 @@ local function stbtt__add_point(points, n, x, y)
points[n + 1].y = y points[n + 1].y = y
end end
-- tessellate until threshold p is happy... -- tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching
-- TODO: warped to compensate for non-linear stretching
--- @param points? stbtt__point[] --- @param points? stbtt__point[]
--- @param num_points int --- @param num_points int
--- @param x0 float --- @param x0 float
@@ -2116,7 +2412,7 @@ end
--- @param n int --- @param n int
--- @return int num_points # updated num_points passed in --- @return int num_points # updated num_points passed in
local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2, x3, y3, objspace_flatness_squared, n) local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2, x3, y3, objspace_flatness_squared, n)
-- TODO: this "flatness" calculation is just made-up nonsense that seems to work well enough -- @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough
local dx0 = x1 - x0 local dx0 = x1 - x0
local dy0 = y1 - y0 local dy0 = y1 - y0
local dx1 = x2 - x1 local dx1 = x2 - x1
@@ -2159,6 +2455,7 @@ local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2
return num_points return num_points
end end
-- returns number of contours
--- @param vertices stbtt_vertex[] --- @param vertices stbtt_vertex[]
--- @param num_verts int --- @param num_verts int
--- @param objspace_flatness float --- @param objspace_flatness float
@@ -2235,6 +2532,7 @@ local function stbtt_FlattenCurves(vertices, num_verts, objspace_flatness)
return points, num_contours, contour_lengths return points, num_contours, contour_lengths
end end
-- rasterize a shape with quadratic beziers into a bitmap
--- @param result stbtt__bitmap --- @param result stbtt__bitmap
--- @param flatness_in_pixels float --- @param flatness_in_pixels float
--- @param vertices stbtt_vertex[] --- @param vertices stbtt_vertex[]
@@ -2258,6 +2556,11 @@ local function stbtt_Rasterize(result, flatness_in_pixels, vertices, num_verts,
end end
end end
-- renders the glyph into storage you pass in for the bitmap,
-- in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
-- is clipped to out_w/out_h bytes. Call stbtt_GetGlyphBitmapBox to get the
-- width and height and positioning info for it first. You can also specify
-- a subpixel shift for the character.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param output unsigned_char[] --- @param output unsigned_char[]
--- @param out_w int --- @param out_w int
@@ -2286,7 +2589,7 @@ end
----------------- -----------------
----------------- -----------------
--- ---
--- Bitmap baking --- bitmap baking
--- ---
--- ---
@@ -2298,7 +2601,7 @@ end
--------------------------- ---------------------------
--------------------------- ---------------------------
--- ---
--- Bitmap baking / packing --- bitmap baking / packing
--- ---
--- ---
@@ -2380,9 +2683,16 @@ local function stbtt__oversample_shift(oversample)
if oversample == 0 then if oversample == 0 then
return 0.0 return 0.0
end end
-- The prefilter is a box filter of width "oversample",
-- which shifts phase by (oversample - 1)/2 pixels in
-- oversampled space. We want to shift in the opposite
-- direction to counter this.
return -(oversample - 1) / (2.0 * oversample) return -(oversample - 1) / (2.0 * oversample)
end end
-- same as stbtt_MakeGlyphBitmapSubpixel, but prefiltering
-- is performed. Returns (sub_x, sub_y).
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param output unsigned_char[] --- @param output unsigned_char[]
--- @param out_w int --- @param out_w int
@@ -2415,7 +2725,7 @@ end
------------------- -------------------
------------------- -------------------
--- ---
--- SDF computation --- sdf computation
--- ---
--- ---
@@ -2441,10 +2751,20 @@ end
--- stbtt__matchpair --- stbtt__matchpair
--- stbtt__matches --- stbtt__matches
-- Each .ttf/.ttc file may have more than one font. Each font has a sequential
-- index number starting from 0. Call this function to get the font offset for
-- a given index; it returns -1 if the index is out of range. A regular .ttf
-- file will only define one font and it always be at offset 0, so it will
-- return '0' for index 0, and -1 for all other indices.
--- @param data table --- @param data table
--- @param index int --- @param index int
function stbtt_GetFontOffsetForIndex(data, index) return stbtt_GetFontOffsetForIndex_internal(data, index) end function stbtt_GetFontOffsetForIndex(data, index) return stbtt_GetFontOffsetForIndex_internal(data, index) end
-- Given an offset into the file that defines a font, this function builds
-- the necessary cached info for the rest of the system. You must allocate
-- the stbtt_fontinfo yourself (see stbtt_fontinfo()), and stbtt_InitFont will fill it out. You don't
-- need to do anything special to free it, because the contents are pure
-- value data with no additional data structures. Returns false on failure.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param data table --- @param data table
--- @param offset int --- @param offset int
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Minifies a Lua/Luau file: drops comments, indentation, blank lines and spaces that don't separate tokens.
# Kept: strings (verbatim), "--!" directive lines (--!optimize, --!native, ...), @native attributes, and one newline
# wherever the source had one (so no ambiguous-call issues, and error line numbers stay roughly meaningful).
# python3 tools/minify.py IN OUT # write minified IN to OUT
# python3 tools/minify.py --same-code F.. # check F.. have the same code tokens as `git show HEAD:F` (comment-only edits)
# python3 tools/minify.py --same-code-as OLD NEW # same check between two files
# python3 tools/minify.py --test # self-check
import re, subprocess, sys
from functools import lru_cache
TOKEN = re.compile(r"""
(?P<ws>[ \t\r\n\f\v]+)
| (?P<lcom>--\[(?P<ceq>=*)\[.*?\](?P=ceq)\])
| (?P<com>--[^\n]*)
| (?P<lstr>\[(?P<seq>=*)\[.*?\](?P=seq)\])
| (?P<str>"(?:\\.|\\\n|[^"\\\n])*"|'(?:\\.|\\\n|[^'\\\n])*'|`(?:\\.|\\\n|[^`\\])*`)
| (?P<num>0[xX][0-9a-fA-F_]*(?:\.[0-9a-fA-F_]*)?(?:[pP][+-]?\d+)?[uUlLi]*|0[bB][01_]+|(?:\d[\d_]*(?:\.[\d_]*)?|\.\d[\d_]*)(?:[eE][+-]?\d+)?[uUlLi]*)
| (?P<name>@?[A-Za-z_]\w*)
| (?P<op>\.\.\.|\.\.=|//=|\.\.|==|~=|<=|>=|//|::|->|[-+*/%^]=|.)
""", re.S | re.X)
def tokenize(src):
"""[(kind, text, newline_before)]; comments/whitespace folded into newline_before ("--!" lines kept as 'dir')."""
out, nl, pos = [], False, 0
while pos < len(src):
m = TOKEN.match(src, pos)
kind, text = m.lastgroup, m.group(0)
if kind in ("ceq", "seq"): kind = "lcom" if text.startswith("--") else "lstr"
pos = m.end()
if kind == "ws" or kind == "lcom" or (kind == "com" and not text.startswith("--!")):
nl = nl or "\n" in text
continue
if kind == "com": kind = "dir"
if kind == "lstr" or kind == "str": kind = "str"
out.append((kind, text, nl))
nl = False
return out
@lru_cache(maxsize=None)
def glue_ok(a, b):
"""True if `a..b` with no space still tokenizes as exactly a, b."""
if re.match(r"\w", a[-1]) and re.match(r"\w", b[0]): return False # real lexers read `0then`/`x1` as one token
return [t for _, t, _ in tokenize(a + b)] == [a, b]
def minify(src):
toks, out = tokenize(src), []
prev = None
for kind, text, nl in toks:
if prev is not None:
if nl or prev[0] == "dir" or kind == "dir": out.append("\n")
elif kind == "str" or prev[0] == "str": # strings are self-delimiting; only `[` + `[[` would fuse
if kind == "str" and text[0] == "[" and prev[1].endswith("["): out.append(" ")
elif not glue_ok(prev[1], text):
out.append(" ")
out.append(text)
prev = (kind, text)
return "".join(out) + "\n"
def code(src):
return [(k, t) for k, t, _ in tokenize(src)]
def test():
cases = {
"local a = 1 -- c\nlocal b = a - -1\n": "local a=1\nlocal b=a- -1\n",
"x = 1 .. 2": "x=1 ..2\n",
"x = t[ [[s]] ]": "x=t[ [[s]]]\n",
"x = t[ [==[s]==] ]": "x=t[ [==[s]==]]\n",
"--!optimize 2\n--[[ long\ncomment ]] local s = 'a -- b' .. \"c\\\"d\"": "--!optimize 2\nlocal s='a -- b'..\"c\\\"d\"\n",
"@native\nfunction f(...) return ... end": "@native\nfunction f(...)return...end\n",
"a = b == c and d ~= e or f <= g": "a=b==c and d~=e or f<=g\n",
"x = 0x10 + 1e-5 + .5": "x=0x10+1e-5+.5\n",
"f(a)\n\n\n(g)(b)": "f(a)\n(g)(b)\n",
"s = [[a\n-- not a comment\n]]": "s=[[a\n-- not a comment\n]]\n",
"x = a / / b": "x=a/ /b\n",
"x = a . . b": "x=a. .b\n",
"if a == 0 then return 1 end": "if a==0 then return 1 end\n",
"x = 1 .. y": "x=1 ..y\n",
}
for src, want in cases.items():
got = minify(src)
assert got == want, (src, got, want)
assert code(minify(src)) == code(src), src
print("minify OK")
if __name__ == "__main__":
if sys.argv[1] == "--test":
test()
elif sys.argv[1] in ("--same-code", "--same-code-as"):
bad = 0
pairs = [(sys.argv[2], sys.argv[3])] if sys.argv[1] == "--same-code-as" else [(None, f) for f in sys.argv[2:]]
for ref, f in pairs:
old = open(ref).read() if ref else subprocess.run(["git", "show", "HEAD:" + f], capture_output=True, text=True, check=True).stdout
a, b = code(old), code(open(f).read())
if a != b:
bad += 1
i = next((i for i, (x, y) in enumerate(zip(a, b)) if x != y), min(len(a), len(b)))
print("%s: CODE CHANGED at token %d: %r -> %r" % (f, i, a[i:i + 6], b[i:i + 6]))
else:
print("%s: same code" % f)
sys.exit(1 if bad else 0)
else:
src = open(sys.argv[1], encoding="utf-8").read()
dst = minify(src)
assert code(dst) == code(src), "minify changed code tokens"
open(sys.argv[2], "w", encoding="utf-8").write(dst)
print("wrote %s (%d -> %d bytes, %.0f%%)" % (sys.argv[2], len(src.encode()), len(dst.encode()), 100.0 * len(dst) / len(src)))
+2
View File
@@ -16,6 +16,8 @@ G = "game, workspace, Instance, Enum, Vector2, Vector3, UDim2, Color3, Content,
def build_run(args, flags): def build_run(args, flags):
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP] + (["--no-demo"] if os.environ.get("NO_DEMO") else []), check=True, stdout=subprocess.DEVNULL) subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP] + (["--no-demo"] if os.environ.get("NO_DEMO") else []), check=True, stdout=subprocess.DEVNULL)
if os.environ.get("MINIFY"): # test the minified bundle instead (error line numbers won't map back to sources)
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "minify.py"), os.path.join(TMP, "ImGui.luau"), os.path.join(TMP, "ImGui.luau")], check=True, stdout=subprocess.DEVNULL)
out = ["--" if args.roblox else "--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"] out = ["--" if args.roblox else "--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"]
def add(key, path): def add(key, path):
s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!")) s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!"))
+68
View File
@@ -0,0 +1,68 @@
-- Docked windows (floating split node) stop being submitted: the host lingers one frame, its bodies must be filled
-- with the windows' bg colour (node.LastBgColor), not white, and everything must be gone within a few frames.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local frame, closed_at, done = 0, 31, false
RunService.Stepped:Connect(function()
if done then return end
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
if frame == 2 then
local id = ImGui.GetID("floating")
ImGui.DockBuilderRemoveNode(id)
ImGui.DockBuilderAddNode(id, 0)
ImGui.DockBuilderSetNodePos(id, ImVec2(100, 100))
ImGui.DockBuilderSetNodeSize(id, ImVec2(600, 400))
local left, right = ImGui.DockBuilderSplitNode(id, G.ImGuiDir.Left, 0.5)
ImGui.DockBuilderDockWindow("A", left)
ImGui.DockBuilderDockWindow("B", right)
ImGui.DockBuilderFinish(id)
end
if frame < closed_at then
for _, n in { "A", "B" } do
if ImGui.Begin(n) then ImGui.Text("window " .. n) end
ImGui.End()
end
end
if frame == closed_at - 1 then
local a, b = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B")
assert(a.DockNode and b.DockNode and a.DockNode ~= b.DockNode and a.DockNode.HostWindow, "A, B docked side by side")
end
ImGui.Render()
local dd = ImGui.GetDrawData()
if frame >= closed_at then
for _, dl in dd.CmdLists:iter() do
-- white vertices spanning a big area = a white body fill (white text glyphs are small)
local vb, x0, y0, x1, y1 = dl.VtxBuffer.Buf, math.huge, math.huge, -math.huge, -math.huge
for _, pcmd in dl.CmdBuffer:iter() do
for i = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount do
local o = (pcmd.VtxOffset + dl.IdxBuffer.Data[i] - 1) * G.VTX_STRIDE
if buffer.readu32(vb, o + 32) == 0xFFFFFFFF then
local x, y = buffer.readf64(vb, o), buffer.readf64(vb, o + 8)
x0, y0, x1, y1 = math.min(x0, x), math.min(y0, y), math.max(x1, x), math.max(y1, y)
end
end
end
assert(not (x1 - x0 > 100 and y1 - y0 > 100), "white fill on frame " .. frame)
end
if dd.TotalVtxCount == 0 then
G.ImGui_ImplRoblox.RenderDrawData(dd)
for _, set in io.BackendPlatformUserData.Layers do
for kind = 1, 3 do assert(not (set[kind] and set[kind].Visible), "backend layer still visible after close") end
end
print("DOCK CLOSE OK after", frame - closed_at + 1, "frames")
done = true
return
else
assert(frame < closed_at + 3, "docked windows still drawn " .. (frame - closed_at + 1) .. " frames after close")
end
end
G.ImGui_ImplRoblox.RenderDrawData(dd)
end)
+57
View File
@@ -0,0 +1,57 @@
-- Regressions vs upstream: clipboard paste filter (InputTextEx), ImParseFormatTrimDecorations (Ctrl+click temp input), ShrinkWidths tie order
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- ShrinkWidths: equal widths sort by Index descending (upstream ShrinkWidthItemComparer), so the rounding remainder goes to the highest Index first
local items = {}
for i = 1, 4 do local it = G.ImGuiShrinkWidthItem(); it.Index = i - 1; it.Width = 100; it.InitialWidth = 100; items[i] = it end
ImGui.ShrinkWidths(items, 1, 4, 2.0, 10.0)
local w = {}
for _, it in items do w[it.Index] = it.Width end
check(w[3] == 100 and w[2] == 100 and w[1] == 99 and w[0] == 99, ("ShrinkWidths widths %s %s %s %s"):format(w[0], w[1], w[2], w[3]))
local function edit_text()
local st = ImGui.GetCurrentContext().InputTextState
return G.ImGui._ByteArrayToString(st.TextA.Data, 1, st.TextLen + 1)
end
local buf = ImGui.NewTextBuffer("", 64)
local f, sf, di = 0, 45.0, 50
local rects = {}
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end -- copies: GetItemRectMin() returns the live LastItemData.Rect.Min
local function click(name, ctrl)
local r = rects[name]
io:AddMousePosEvent((r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2)
if ctrl then io:AddKeyEvent(G.ImGuiMod_Ctrl, true) end
io:AddMouseButtonEvent(0, true)
end
local function release() io:AddMouseButtonEvent(0, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
if f == 3 then click("t") elseif f == 4 then release() end
-- clipboard: tab is filtered out (single line, no AllowTabInput), é is 2 bytes
if f == 6 then ImGui.SetClipboardText("ab\tc\u{e9}"); io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.V, true) end
if f == 7 then io:AddKeyEvent(G.ImGuiKey.V, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 9 then key(G.ImGuiKey.Escape) end
if f == 11 then click("s", true) elseif f == 12 then release() end
if f == 14 then key(G.ImGuiKey.Escape) end
if f == 16 then click("d", true) elseif f == 17 then release() end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
ImGui.Begin("T")
ImGui.InputText("##t", buf, 64); rects.t = rect()
sf = ImGui.SliderFloat("##s", sf, 0.0, 100.0, "%.0f deg"); rects.s = rect()
di = ImGui.DragInt("##d", di, 1, 0, 100, "%d%%"); rects.d = rect()
ImGui.End()
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 8 then check(edit_text() == "abc\u{e9}", "paste: '" .. edit_text() .. "'") end
if f == 13 then check(edit_text() == "45", "SliderFloat temp input: '" .. edit_text() .. "'") end
if f == 18 then check(edit_text() == "50", "DragInt temp input: '" .. edit_text() .. "'") end
if f == 20 and #failed == 0 then print("FIXES OK") end
end)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "4dbug/imgui-luau" name = "4dbug/imgui-luau"
version = "0.1.2" version = "0.1.4"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"