Repository navigation
Releases: bisand/denise
Release list
v0.31.0
The core depends on nothing, a font needs no parser on the panel, and CI notices when either stops being true.
This release is about who else's code runs on a panel, which for a device that boots from flash and runs unattended for a year is a security question before it is a tidiness one. Four things changed, and one of them is a new tier of text.
The libraries depend on less
denise, denise-render, denise-layout, and — with the built-in font — denise-text and denise-ui now pull in no external crate at all, and neither do the C ABI, the keyboard or the arranger. Two crates went: thiserror, whose derives are now hand-written Display and Error impls with the same messages and the same source(); and slotmap, whose place in the widget tree is taken by a generational arena of the tree's own, in denise-ui/src/arena.rs. The u64 that carries a NodeId across the C ABI keeps its layout — generation high, slot counted from one low, never 0 — and a test pins the first id at the value C callers have always seen.
One break. NodeId no longer implements slotmap::Key, so null(), is_null() and data() are gone. Default stays, as an id no tree issues; as_ffi and from_ffi are unchanged. Nothing in the workspace used the three.
The truetype tier reads with skrifa
ttf-parser, which ab_glyph read fonts with, has no maintainer and no maintained release (RUSTSEC-2026-0192), and a parser nobody patches is the wrong thing to hand a font file on a panel. denise-text's truetype tier now reads with skrifa — what Chrome reads fonts with, forbid(unsafe) throughout — and, since skrifa reads and does not draw, fills the outline itself: fill.rs, a page of signed-area accumulation in place of ab_glyph_rasterizer.
The drawing is closer to right than it was. Against a near-exact rendering of 770 glyphs at seven sizes on five faces, the new fill is off by 0.2 levels of coverage on average and never by more than 4; the old one was off by 1.7 and by as much as 49, having flattened its curves more coarsely. Advances, line metrics and glyph ids are identical on every face tried, San Francisco among them — the variable face 0.30.1 was about, which skrifa reads at its default instance with nothing asked for and no 32-tuple stack. gvar-alloc and variable-fonts are gone because there is nothing for them to switch. An uncached glyph takes about 1.25× as long, paid once per glyph and size by the atlas; an ASCII character's glyph is a table lookup.
The cost is size, weighed before it was written: a stripped static aarch64-musl binary grows by about 270 KB for the tier where it grew by about 107 — some 48 KB of that a TrueType hinting interpreter nothing here calls and the linker cannot prove it, the rest CFF, variations and variable composites. The docs said 65 KB and now say 270. The tier is twelve external crates where it was six, one of them a derive macro; all twelve were already in the tree under shaping. ttf-parser is not out of the lockfile — cosmic-text's fontdb and winit's Wayland title bar still bring it, which deny.toml records — but no truetype panel reads a font with it.
No API change: TrueTypeSource::from_bytes, from_vec and from_static are what they were. A new test feeds four hundred truncated and corrupted copies of a real font through load, metrics, lookup and rasterise: a damaged face is refused or drawn wrong, and never panics.
A face baked at build time draws with no parser at all
Most of what that parser does on a shipped product is recompute answers fixed on the day of the build: the font is compiled in, the sizes are the layout's. The new baked tier does the work once. denise_text::bake, behind a bake feature that is for a build.rs and never a panel, rasterises any GlyphSource at the sizes and characters a program names and writes the result as Rust — a static BakedFont of plain tables, every glyph's metrics and its coverage at each size. BakedSource draws from it with a binary search and a slice. It is always compiled, needs no feature, no external crate and no std, and does nothing at boot; a test bakes a real face at three sizes and finds the tables draw byte for byte what the truetype tier draws.
What the panel did not know at build time it does not get: an unbaked size snaps to the nearest that was, downwards on a tie, and an unbaked character draws as the box. A font the user brings at run time is still truetype's business, and the two coexist in one binary — TextEngine::add_font takes either — so a product that lets its user pick a font keeps the parser, and one that ships a face does not. examples/baked is such a program: denise-text with no features, so it could not read a font file if handed one; Arial at 14, 20 and 32 px for Latin-1 is 114 KB of coverage and 12 KB of tables in place of a megabyte of font, and the whole binary is 572 KB stripped and static on aarch64 musl.
CI notices
Every action in every workflow is pinned to a commit SHA with its version beside it, and Dependabot moves both together, weekly; ci.yml runs with permissions: contents: read and --locked on every cargo call; a scheduled job runs the advisory check every Monday whether or not anything was pushed. deny.toml fails on an unmaintained crate anywhere in the tree rather than only at the top, with the one accepted exception written down beside its reason. And a new dependency-budget.txt names every external crate each library is allowed to pull in, every feature and every target, so a tree that grows does it in a diff somebody reads rather than on the back of a minor version bump — that is what turned "the core depends on nothing" from a sentence into a check.
Fifty-three crates were out of date when this started. All are current but windows-core, whose 0.100 has no windows release to go with it yet and is declined for that reason, in writing. No known vulnerability was found in any of them.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.31.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.31.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.31.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.31.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.30.1
A variable face is read, instead of being drawn blank.
denise-text's truetype feature asked ab_glyph for libm and nothing else, and the dependency is declared with no default features, so variable-fonts was off. A variable face read that way does not fail. It parses, it answers for every character it has, and it rasterises all of them to an empty mask. Nothing returns an error and nothing logs; the first anybody knows of it is a panel whose lines are the right height with no words in any of them, which is a long way from the cause.
macOS's own UI face, /System/Library/Fonts/SFNS.ttf, is a variable face, so on a Mac this was the default outcome for the system face rather than a corner case. squint draws its tabs, its status line and the whole of its settings window in it, and 0.30.0 emptied all three — the release that said its window was pixel for pixel what it was but for antialiasing nobody can see. That was true of the document and not of the chrome.
Reading a glyph carrying more than 32 variation tuples needs a heap as well, ttf-parser keeping them in a fixed array on the stack otherwise, and San Francisco is such a face. That is gvar-alloc, which turns on ttf-parser's std, so it goes under this crate's std rather than under truetype. A std build — every desktop, and the case this was reported for — reads any variable face. A build without std reads the ones whose glyphs fit in 32 tuples and still draws a more complex one blank: the price of a target with no allocator, which is the reason this crate builds without one, and better than the nothing it read before.
No API change. 0.30.0 is the only release affected, variable faces having been unreadable for the whole of the ab_glyph tier's life, which is one release long.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.30.1-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.30.1-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.30.1-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.30.1-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.30.0
An idle window costs nothing, and a face costs what its file does.
- TrueType faces are read glyph by glyph.
denise-text'struetypetier moves fromfontduetoab_glyph: an outline is read when its glyph is first drawn rather than every outline when the face is opened. FiraCode Nerd Font's twelve thousand glyphs came to 60 MB and most of a second on an Atom before anything was drawn, DejaVu Sans to 20 MB; both now cost about their file, opened in under a millisecond. The drawing is the same — over printable ASCII and a few non-Latin glyphs at seven sizes, one glyph in 728 differs, by one pixel, and total ink by half a percent — and the tier is 65 KB of static binary rather than 145. Rasterising one uncached glyph is about 1.4× slower, which the atlas pays once per glyph and size. One break:TrueTypeSource::font(), which handed out thefontdue::Font, is gone;from_vectakes bytes without a copy andfrom_staticaninclude_bytes!. - A caret nobody moves rests lit.
TextInputandTextAreablinked for as long as they had focus — two wakes and two frames a second in a window left open. They now blink for ten seconds after the last keystroke or caret move, GTK's default, then stay lit and ask for no wakes until the next one. Wakerwakes the loop from another thread. For work arriving where the loop cannot see it — a socket, a file watcher — which until now meant answeringnext_frame_inwith a short wait forever just to look. Every application is handed one throughDeniseApp::set_wakeras its window opens;wake()makes every window due a frame, as input does. Clone, Send and Sync.- An application is told how its window draws.
DeniseApp::presentingsaysPresent::GpuorPresent::Softwareonce the window is open, so a machine wherePresent::GpuOrSoftwarefound no GPU can be remembered: the attempt loads LLVM and Mesa, gives up, and leaves them mapped, which is tens of megabytes and a slower start on every run.
Both trait methods have defaults, so nothing implementing DeniseApp has to change.
Found in squint, which held 138 MB with no document open on an Atom running Alpine and Hyprland, started on 1.9 s of CPU and woke eight times a second doing nothing. With these it holds 18 MB, starts on 0.13 s, and wakes eight times per thirty seconds.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.30.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.30.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.30.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.30.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.29.1
A long dropdown fits the surface and scrolls. open_select sized its popup to every option, so a list longer than the window ran off it: the wheel had no viewport to scroll and the arrow keys moved the highlight to rows nobody could see. The popup is now no taller than the room beside the control, its panel is a scrollable viewport, and it opens scrolled to the current choice. Found in Alpymist Settings, whose keyboard layout list has about a hundred entries. No API change.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.29.1-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.29.1-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.29.1-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.29.1-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.29.0
Touchpads. A touchpad reports the same multitouch slots as a touchscreen and was read as one, so a laptop's pad touched the corner of the screen its finger was in the corner of and the pointer never moved. A device with a finger tool the kernel has not marked INPUT_PROP_DIRECT, and no pen, is now a touchpad, and Capabilities::touchpad says so: one finger moves the pointer by its travel with sub-pixel carry, two fingers scroll like a wheel, the pad's button is a right button with two fingers down, and a short still tap clicks (right with two fingers). Translator::feed_at takes the kernel's timestamp for taps, which InputBackend passes; feed without one never taps. Finger tools no longer come out as unnamed key presses. Found in Alpymist, whose installer had no pointer on an Asus E200HA. Additive except the new Capabilities field, which a struct literal must name.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.29.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.29.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.29.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.29.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.28.0
A window says which application it belongs to. WindowConfig::app_id names the application to the desktop — Wayland's app_id and X11's WM_CLASS — which is what ties a window to its desktop entry, so a launcher and a task bar show its name and icon, and what a window manager's rules match. Without it a Wayland compositor has nothing to go on. Ignored on macOS and Windows. Found in squint, whose windows Hyprland listed with an empty class. Additive except the new WindowConfig field, which a struct literal without ..Default::default() must name.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.28.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.28.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.28.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.28.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.27.0
A window that asks for the GPU and draws in software where there is none. Present::GpuOrSoftware opens a swapchain where a GPU can present to the window and the software surface where none can — a virtual machine, a remote display, a missing driver — decided for each window as it opens, with the reason on stderr. The fallback cannot be done around run_with: winit makes one event loop per process, so a second run in software fails with EventLoop(RecreationAttempt) before it opens anything. Found in squint, which never opened on a virtual machine with no adapter. Additive, except to code that matches Present exhaustively, which must name the new variant.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.27.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.27.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.27.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.27.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.26.0
A text area that scrolls by the pixel. TextArea::with_smooth_scroll (and set_smooth_scroll, and a smooth-scroll form property) moves the text as far as the wheel says, a pixel at a time, instead of a line once a line's worth has built up. A fast wheel goes exactly as far as it does by lines, both ends stop on a whole line, and anything that moves the view by lines — a jump, a page, the thumb, the caret — lands on one. Off unless asked for, so existing text areas scroll as they did. All additive. Found in squint.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.26.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.26.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.26.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.26.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.25.0 — a window opens where it was closed
A window could not open the way it was closed. Its size was a constant handed
to WindowConfig once, and where the window had got to was never reported at
all, so every run started at whatever the application had compiled in.
WindowConfig gains position and maximized, and InputEvent gains
SurfaceMoved — the other half of SurfaceResized: that one says how big the
window is, this one says where it is and what the window manager has done with
it. Keep what the last one told you, hand it back to the next, and the window
opens where it was.
The units are the ones each fact is really kept in. The size stays logical,
because it says how much of the desk a window covers and that should survive
moving to a display of another DPI. The corner is physical, because a desk
spanning displays of different DPI has no single logical grid to name a point
in, and because physical is what the window system reports and takes back.
Two things that would otherwise make this quietly wrong:
- On macOS the position attribute places the content below the title bar,
whileouter_position— whatSurfaceMovedreports — is the frame's own
corner. Remembered through that pair, a window climbs its own title bar's
height every single time it opens. The corner is set again with
set_outer_positiononce the window exists, in the units it will be read
back in, so the round trip is exact. - A position on a display that has since been unplugged is ignored rather
than honoured, which is a window opening somewhere nobody can reach it. A
machine reporting no displays at all is taken at its word.
Maximising arrives as a resize and a move together, and on some window
managers as only the resize, so the placement is looked at after both.
Nothing that built against 0.24.1 changes behaviour: both new fields default
to what happened before, and InputEvent is #[non_exhaustive]. A
WindowConfig written as a struct literal without ..Default::default()
needs the two new fields, which is why this is the minor and not a patch.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.25.0-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.25.0-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.25.0-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.25.0-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.
v0.24.1
A text area handed a line far longer than the view — a binary file opened as
text, a minified document with no line breaks in it — built geometry for every
glyph of that line and not just the ones on screen. On a painter that makes a
quad per glyph that is six vertices apiece: a 587 KB line came to a 321 MB
vertex buffer, past what wgpu will take in one, and the frame died with a
validation error.
TextEngine::draw_line now rasterises and hands over only the glyphs the
canvas would keep anything of, horizontally and vertically, with a four-em
margin for what a glyph reaches outside its own advance. The pixels are the
same. So is the width it returns — of the whole line, not the part drawn,
which is what a text area measures to know how far it scrolls sideways.
Laying the line out is still the whole of it, and that is the cheap half: an
advance apiece out of the glyph cache, against rasterising and uploading.
Download the designer
A visual form designer for DeniseUI, and the .dform command line tool. No
Rust toolchain needed.
| macOS (Intel and Apple silicon) | denise-0.24.1-universal-apple-darwin.dmg |
| Windows x86-64 | denise-0.24.1-x86_64-pc-windows-msvc.zip |
| Linux x86-64 | denise-0.24.1-x86_64-unknown-linux-gnu.tar.gz |
| Linux aarch64 | denise-0.24.1-aarch64-unknown-linux-gnu.tar.gz |
Each has a .sha256 beside it. Every archive carries the reference form, so
there is something to open.
These are not signed. There is no Apple Developer account behind this
project and no Windows code-signing certificate, so the first launch needs one
extra step: on macOS, right-click the app and choose Open; on Windows,
More info then Run anyway. Linux does not care.