Releases: developerakshat12/FluidCorePDF
Release list
v1.1.8
FluidCore v1.1.8 — Aspect-Correct Crop Tiles & Stable Excerpt Caching
FluidCore v1.1.8 is an excerpt-card rendering correctness release. Diagram crops dropped onto the infinite synthesis workspace are no longer distorted, and cards no longer flash the "Visual Diagram Crop" placeholder while their real content is already cached. It also cancels raster work for zoom tiers the user has already zoomed past, and collapses four disagreeing copies of the card geometry constants into one definition.
Both defects had a single origin: the crop-tile pipeline derived tile pixel dimensions from card geometry and then constrained them with per-axis, independent clamps, while the production path never enforced the per-tile byte ceiling that the test path did. The aspect skew was baked into the cached raster at creation time and was therefore irreversible; the oversized tiles made those skewed surfaces big enough that the cache could only hold two, so the rest of the screen had nothing to draw.
- Crops Are No Longer Squashed: A single uniform scale is derived from the authoritative PDF crop box, and every ceiling is applied as a uniform reduction of that one scale. No clamp can stretch one axis independently.
- The 4 MiB Tile Ceiling Now Applies in Production: It was previously enforced only by
renderCropSync, the synchronous path used by tests and benchmarks. A 200%-zoom card produced an 8.14 MiB tile against a 24 MiB slice. - Cards Stop Flashing the Placeholder: The cache pins every crop visible in the viewport and retains one lightweight lower-resolution tier, so there is always a real image to blit while a sharper tile renders.
- Obsolete Zoom Tiers Are Cancelled Before Rendering: Crossing a resolution boundary queued a render per visible card at the intermediate tier. Those rasters ran under the global Poppler lock and were then immediately superseded.
- One Definition of Card Geometry: Layout, rendering, and tile requests now share a single body-rect definition instead of four literals that disagreed.
- Fixed: The Windows executable's version resource was not kept in step with the release; v1.1.7 shipped reporting
1.1.6.0in Windows file properties.
Aspect Distortion
The reported bug
With the canvas at 200% zoom, crops of 4:3 slide pages (CS F301-L2-3(Introduction).pdf, 720×540 pt) rendered visibly narrower than the corresponding figure on the source page, with boxes inside the diagram condensed and text appearing vertically stretched. The skew reached ≈17% horizontally.
Root cause
Three independent per-axis decisions stacked:
- The requested box came from the card, not the crop.
drawExcerptCardrequestedbounds().w - 20.0bybounds().h - 36.0. Substituting the layout engine's own formula (cardW = imgW + 28,cardH = imgH + 46) givesimgW + 8byimgH + 10— already a skewed aspect, at every zoom including 100%. For the 4:3 card in the report session (imgW = 450,imgH = 337.5) that is a −1.15% baseline error. Small enough to go unnoticed; the same error that the next step amplified. - Each axis was clamped to 1536 px independently.
requestCropAsyncdidclamp(cardW * lodScale, 16, 1536)andclamp(cardH * lodScale, 16, 1536)separately. - The raster transform was per-axis.
renderBackgroundCropcalledcairo_scale(cr, targetW / cropW, targetH / cropH).
For the reported card (cardW = 478, cardH = 383.5, so a base request of 458×347.5), the Retina tier asked for 1832×1390. The width saturated at 1536; the height did not. The result was a 1536×1390 tile carrying an aspect of 1.1050 instead of 1.3333 — −17.12% — with the squeeze already rasterized into the surface.
This was unrecoverable. The blit in drawExcerptCard is deliberately uniform (scale = min(bodyW/surfW, bodyH/surfH)), which is correct, and it faithfully reproduces whatever aspect the tile already had. Re-blitting at a different zoom, tier, or card size could not undo it, so the skewed surface was served for the lifetime of the cache entry.
The fix
renderBackgroundCrop is the only place that calls poppler_page_get_size, so it is the only place that can know the crop's true point dimensions. All sizing now happens there, and the caller's two numbers are treated as a bounding box constraint rather than a pixel size:
// src/app/services/TileSizing.h
double s = std::min(static_cast<double>(targetW) / cropW,
static_cast<double>(targetH) / cropH);
s = std::min(s, static_cast<double>(kMaxTileDimension) / cropW);
s = std::min(s, static_cast<double>(kMaxTileDimension) / cropH);There is deliberately no min(1.0, …) on that expression. Capping the scale at 1 px/pt would pin every tile to 72 DPI and destroy Retina supersampling on high-DPI displays; the whole point of the tier ladder is to render above 1 px/pt. The byte ceiling is then a further uniform reduction:
const double maxPixelArea = static_cast<double>(kMaxTileBytes) / 4.0;
const double pixelArea = (cropW * s) * (cropH * s);
if (pixelArea > maxPixelArea) {
s *= std::sqrt(maxPixelArea / pixelArea);
}Two details that are easy to get wrong and were both handled:
- The floor is 1 px, not 16 px. A per-axis minimum re-introduces exactly the defect this change removes. On a 5×700 pt sliver crop, a 16 px floor yields a 16×338 tile — a 6.6× distortion — where a 1 px floor preserves the true ratio.
- The byte ceiling is re-checked after rounding. Rounding
cropW * sandcropH * sto integers can push the product back over the limit (a 1449×724 tile is 4,196,304 B, over the 4,194,304 B ceiling). A guard decrements the longer axis until the tile fits.
For the reported card, the Retina tile is now 1155×866 = 3.81 MiB, down from 8.14 MiB, at an aspect of 1.3333.
renderCropSync was routed through the same policy, so the test and benchmark paths can no longer diverge from production.
Placeholder Flicker
The reported bug
Cards repeatedly flashed the dashed "Visual Diagram Crop" placeholder while their content was already cached, on every zoom gesture, and worst immediately after the 20-second idle trim.
Root cause
The 4 MiB per-tile ceiling lived in renderCropSync only. The asynchronous path — the one the renderer actually uses — had no byte ceiling at all, so a Retina tile was 8,540,160 B against a Slice::ExcerptTiles budget of 24 MiB working / 6 MiB idle. Three such tiles did not fit; at idle, a single one exceeded the entire budget. With three or four cards on screen, at least one was guaranteed to be missing at any instant.
That alone would make the placeholder likely. Three mechanisms made it unavoidable:
evict()was blind. It popped the LRU tail with no knowledge of what was on screen, so it took visible cards first.evictSiblingTiers()removed the safety net. On inserting a higher tier it purged every other tier of the same crop.getBestAvailableSurface()falls back to another resident tier — and the eviction policy had just guaranteed there wasn't one. A card could reach zero resident surfaces, at which point the placeholder is the only correct output.- The idle trim cleared everything.
main.cppcalls bothsetMaxBytes(6 MiB)andtrimToBytes(6 MiB).trimToByteshad no pinning either, so the working set was dropped wholesale and the next repaint began blank for every visible card.
The result was a closed loop: card 1's high tier completes → its fallback is purged → card 2's render completes → card 1 is evicted → card 1 shows the placeholder → card 1 redraws and re-dispatches. Repeating at the render-completion rate.
The fix
Viewport pinning. WorkspaceRenderer::drawExcerptCard records a CropIdentity — (docId, pageNo, xNorm, yNorm, wNorm, hNorm), deliberately tier-independent — for every visible crop, and commits the set at frame end. evict() and trimToBytes() search for an unpinned entry and halt if every resident tile belongs to an on-screen card.
Pinning by crop identity rather than by exact cache key is load-bearing. A CropCacheKey includes the tier, so pinning it would pin nothing at exactly the moment of a tier boundary — the instant the flicker happens — while leaving evictable the one lower tier that was preventing the placeholder.
The pin set is collected inside drawExcerptCard rather than at the top of the paint pass, because stacked excerpt cards are drawn via drawCardStack → drawExcerptCard and their children are not returned by the model's visibleIn() query. Collecting inside the function covers both paths.
Fallback retention. evictSiblingTiers() now keeps one Overview or Standard tile per crop, so getBestAvailableSurface() always resolves during a zoom transition. This is what converts the hard oscillation into a brief softening.
With the byte ceiling now enforced in production, the Retina tile dropped from 8.14 MiB to 3.81 MiB, which alone moves working-budget residency from two cards to six.
Obsolete Zoom Tiers Were Rasterized Before Being Discarded
Crossing a resolution boundary (computeLodTierFromZoom has hard edges at 0.35 / 0.85 / 1.75) invalidates every visible card's key at once. Each card then queues a render at the tier it is leaving. Those tasks sat in a two-worker GThreadPool and were picked up regardless of whether they were still wanted.
Cancellation is performed at dispatch, on the main thread, inside requestCropAsync: any in-flight request for the same crop at a different tier has its per-task cancelToken set, and its in-flight bookkeeping unmapped synchronously. The worker checks that token immediately before renderBackgroundCrop and returns without touching Poppler.
Two placement details decide whether this is correct or a memory leak:
- The token is
std::shared_ptr<std::atomic<bool>>, mirroring the existingaliveToken. `o...
v1.1.7
FluidCore v1.1.7 — Stylus Tool Honesty & Interaction State Recovery
FluidCore v1.1.7 is a stylus-input correctness release. It fixes a digitizer rewriting the user's selected tool, recovers gestures that were being stranded mid-flight, and repairs the pen barrel button's width bookkeeping. It also removes one feature that was only reachable through the behaviour being fixed.
This release reverses a documented v1.1.6 behaviour (see Breaking Change below). Upgrading users who annotate with a pen will notice the difference immediately.
- Stylus Selects Instead of Inking: A pen now honours the selected tool exactly as a mouse does. Select and Crop work with a digitizer instead of laying down ink.
- Stuck Interaction States: Crop, text-selection, and inking gestures are torn down correctly when a release event is lost, instead of silently swallowing all later input.
- Barrel Button Restores Pen Width: Holding a pen's barrel button no longer pins the ink width at the eraser radius for the rest of the session.
- Removed: Shift-for-highlighter on stylus input.
Breaking Change: Stylus No Longer Rewrites the Selected Tool
The reported bug
With the Crop tool selected, dragging in the document pane intermittently drew a pen stroke instead of selecting a region to crop. The same gesture sometimes worked perfectly. The Select tool was worse: it consistently drew ink whenever a pen was used.
Root cause
StylusButtonRouter::resolveInkTool() existed for exactly one purpose — to rewrite the selected tool to "pen" whenever a stylus was the input device. v1.1.6 shipped it covering select, text, crop, and rect_select, on the reasoning that a pen press in a selection mode starts a selection and consumes the press, so annotating would otherwise require first switching tools with a mouse.
Two independent mechanisms then combined badly:
- The crop gate also excluded styluses.
InkOverlay::onButtonPresstestedif (!isStylus && (effectiveTool == "crop" ...)). Even with the tool rewrite removed, a pen could not enter the crop branch at all; it fell through to the inking path and committed a realAddStrokeCommandwithstroke.tool == "pen"to the undo stack and the annotation store. - The outcome depended on driver-level device reporting. The rewrite was keyed on
gdk_device_get_source(). A Wintab digitizer that GDK reported asGDK_SOURCE_PENinked; the same tablet reported asGDK_SOURCE_MOUSE(mouse mode, hover-state transitions, driver quirks) cropped. Identical gesture, two behaviours — which is exactly the intermittency that was reported.
The fix
The tool-rewriting layer is deleted rather than extended with another exclusion. The rule is now stated once, in the StylusButtonRouter class comment:
A stylus is a pointing device, not an inking device: it selects text, drags crop rectangles, and moves cards, exactly as a mouse does.
StylusButtonRouter::resolveInkTool()and itsisMouseOnlyTool()helper are removed. The class now owns only barrel buttons, device classification, and stylus attributes.InkOverlay::resolveToolForDevice()and the write-onlym_stylusHighlighterlatch are removed.onButtonPressreadsm_currentTooldirectly.- The
!isStylusguard is removed from both the crop and text-selection branches inonButtonPress, so a pen genuinely enters those paths. WorkspaceView's per-stroke stylus tool override and itsToolOverrideGuardunwind are removed. Leaving them would have kept the pen inking over card selection on the infinite canvas, contradicting the rule on the other pane.
The accepted trade-off
To ink with a pen you must now select the Pen, Highlighter, or Eraser tool first. v1.1.6 deliberately avoided that by making the pen always ink.
This is a considered reversal, not an oversight. A digitizer is the most precise pointing device available, which makes it the best tool for selecting a region and the worst tool for a gesture that a mouse already does adequately. The v1.1.6 trade-off optimised for "never reach for the mouse" and paid for it by making two tools unusable with the hardware they suit most.
Removed: Shift-for-highlighter
v1.1.6 supported holding Shift with a stylus press to get the highlighter nib while a selection tool was active. That path existed only inside resolveInkTool and had nowhere to live once the rewrite was deleted. There is no replacement behaviour — select the Highlighter tool instead.
StylusButtonRouterTest's testPenOverridesMouseOrientedTools was deleted rather than rewritten. It tested a function that no longer exists, and the protection is now structural: there is no code left that can rewrite a tool. A vacuous replacement asserting the new behaviour would have been worse than no test.
Stuck Interaction States
InkOverlay tracks four independent interaction flags — m_isDrawing, m_isSelectingCrop, m_isSelectingText, and m_isPotentialExcerptDrag — and both the motion and release handlers test them in a fixed priority order (crop → text → drawing). Nothing guaranteed they were mutually exclusive, and several teardown paths were missing.
How a gesture got stranded
GDK_LEAVE_NOTIFY_MASK was requested in the constructor but no leave-notify-event handler was ever connected. When a release was not delivered to the overlay, onButtonRelease never ran and the flag stayed true for the rest of the session:
- A stranded
m_isSelectingCropswallowed every subsequent release at the crop branch, which returns early and never reaches the line clearingm_isDrawing. The half-finished pen stroke stayed live, kept rendering, and was committed against a later release. InkOverlay::setTool()cancelled nothing, so switching tools mid-gesture preserved the stale flag: crop → pen mid-drag meant the pen received zero samples; pen → crop mid-stroke meant the pen kept drawing while the Crop tool was highlighted, and the release committed a stroke the user believed was a crop.Esccleared the crop selection but not the crop drag flag.onButtonPresshad no "interaction already in flight" guard, so a second device or a duplicate press could set one flag while another was still set.
The fix
InkOverlay::cancelCurrentInteraction() is a single teardown routine, mirroring the existing WorkspaceView::cancelCurrentInteraction. It clears all four flags, discards any in-progress stroke, and unwinds a barrel borrow. It is now called from:
setTool()— so switching tools always ends the previous gestureonButtonPress()— a press implies the previous gesture is overleave-notify-event— a genuine pointer exitfocus-out-event(new) — how a broken pointer grab usually surfacesproximity-out-event— the pen leaving digitizer range, which sends no release and was the commoner stranding case- palm-rejection cancellation —
cancelActiveTouches()now delegates instead of duplicating the teardown, which had already drifted and missed the barrel borrow
Grab-mode leaves are deliberately skipped. GDK holds an implicit grab for the duration of a button press, so a release outside the widget still reaches onButtonRelease; cancelling on a GDK_CROSSING_GRAB crossing would discard a live stroke every time the pointer merely drifted over the scrollbar. That assumption is now documented at the call site.
Related: crop motion could fall through into inking
onMotionNotify's crop branch tested m_dragStartPageIndex < pages.size() and returned TRUE only inside that guard. A relayout between press and motion — zoom, reload, or any of the ~20 updateLayoutDimensions() call sites — invalidated the index and let the event fall through into the text and then the ink branch, feeding a crop drag into a pen stroke. The branch now returns unconditionally.
Barrel Button Restores Pen Width
A barrel press wrote m_currentTool = "eraser" and m_currentWidth = kEraserWidth directly, bypassing setTool() and therefore never setting m_widthForcedByTool. The release path restored only the tool. After any barrel press/release the pen stayed pinned at 20.0 pt for the rest of the session.
Two teardown paths also disagreed: the new cancelCurrentInteraction() restored tool and width, while the ordinary release restored only the tool — so behaviour depended on which path happened to run.
The fix
InkOverlay::restoreBorrowedBarrelTool()is one implementation shared by the release path, the tool-change teardown, and the pointer-leave teardown.- The barrel press now sets
m_widthForcedByTool = true, so it owns the width it forces. - The width restore is guarded by
m_widthForcedByTool, preserving the existing invariant that a width the user chose is never clobbered. (An earlier draft of this fix reset the width unconditionally and would have silently discarded a slider-chosen width on any barrel press.)
Related: a latched eraser blocked all later borrows
StylusButtonRouter::m_barrelLatched was never cleared anywhere in the app. One long barrel press made endBarrel() return false permanently, so the borrow could never unwind again. This became load-bearing with the shared restore above, which would have been a permanent no-op. clearBarrelLatch() is now called when the user explicitly selects a non-eraser tool — an explicit tool choice is the user saying the eraser is no longer wanted. Covered by testClearBarrelLatchAllowsAFurtherBorrow.
Files Changed
| Area | Change |
|---|---|
src/app/services/StylusButtonRouter.{h,cpp} |
Removed resolveInkTool() and isMouseOnlyTool(). Added clearBarrelLatch(). Class comment now records the stylus-is-a-pointing-device rule and the reversal. |
src/app/document/InkOverlay.{h,cpp} |
Added cancelCurrentInteraction(),... |
v1.1.6
FluidCore v1.1.6 — Constant-Width Ink, Unified Stroke Renderer, Memory Budget, & Stylus Barrel Buttons
FluidCore v1.1.6 is an ink-fidelity, memory-governance, and stylus-input release. It also hardens the Linux release pipeline, which had been structurally unable to produce a complete release.
- Constant-Width Ink: Stroke geometry no longer tapers with pressure. A stroke is drawn at exactly the width the user selected, along its entire length.
- Unified Stroke Renderer: All four ink renderers (document pane, infinite canvas, PDF export, excerpt crop tiles) now share a single Cairo rasterizer instead of four disagreeing copies.
- Memory Budget: A single authority partitions the cache budget across page tiles, excerpt tiles, and undo history, sized from physical RAM and trimmed when idle.
- Stylus Barrel Buttons: Barrel presses on a pen now erase instead of panning the canvas or opening a context menu.
- Release Pipeline Repair: The Linux AppImage step could not succeed — its required tools were never installed — and two jobs raced to publish half-releases. Both are fixed.
🌟 What's New & Fixed in v1.1.6
1. Constant-Width Ink — StrokeWidthModel
New canonical header src/libfluidcore/geometry/StrokeWidthModel.h is now the single source of truth for ink width, derived by every renderer and by the eraser hit-test.
- Removed the pressure taper. Width was previously
baseWidth * (0.25 + 0.75 * p), so the selected width meant something different at every point along the same stroke. - Fixes a detached blob at pen lift. On lift, a stroke's tail collapsed toward the
0.25xfloor while the round end cap kept its full radius, leaving what looked like a rendering fault at the end of otherwise clean lines. - Fixes save/reload weight drift. Pressure is still captured and persisted with each stroke but no longer modulates geometry, so a reloaded stroke re-renders at the same apparent weight the user drew.
- Fixes eraser hit-radius mismatch. The eraser sized its hit radius from a pressure the ink no longer used.
- Shared highlighter opacity.
kHighlighterAlpha = 0.5is defined once, so a highlight reads identically in the pane, on the canvas, and in exported PDF. - NaN-safe clamping.
renderedWidth()andclampPressure()reject NaN, and a floor ofkMinStrokeWidthPx = 0.5keeps antialiasing from degenerating into a dotted line.
2. Unified Stroke Renderer — StrokeRenderer
New src/app/services/StrokeRenderer.{h,cpp} is the single Cairo entry point for ink geometry. The document pane overlay, the infinite canvas, PDF export, and excerpt crop tiles all call it.
- Fixes polygonal ink at high zoom. The old code flattened every span into a fixed 3–4 chords and stroked each chord separately. A fixed chord count is resolution dependent, so ink looked smooth at 50% zoom and visibly faceted at 200%.
- Fixes beaded outlines. Chained round caps at stepping widths produced a dashed-looking edge. The Centripetal Catmull-Rom spline is now emitted as
cairo_curve_tospans and stroked once, so a single stroke is a single composite and Cairo's stroker subdivides adaptively at any zoom. - Fixes export/display drift. The four renderers had independent copies of the rasterizer that disagreed on both flattening density and the pressure-to-width equation, so exported ink was narrower than what was on screen.
- Reflection end conditions. Phantom points reflect past each end of the polyline, so the spline is defined across the whole stroke rather than only between interior samples.
3. Memory Budget — MemoryBudget
New src/app/services/MemoryBudget.{h,cpp} arbitrates every byte-budgeted consumer in the app.
- Fixes a 2x budget overrun. Four independent caches each declared
kDefaultMaxBytes = 64 MBand each enforced it only against its own surfaces, so a session could legitimately authorize 256 MB against a documented idle target of 120 MB (TRD.md §4). Nothing arbitrated between them and nothing ever calledsetMaxBytes(). - RAM-derived total.
recommendedTotalBytes()derives the pool from physical RAM, clamped to[16 MB, 512 MB], falling back to 64 MB when RAM cannot be determined. - Named slices.
PageTiles,ExcerptTiles, andUndoStackeach receive an explicit share. The partition is deliberately page-tile heavy, because page tiles back the reading experience the user is looking at right now, excerpt tiles are card thumbnails that re-render cheaply, and undo history is depth-bounded in practice. - The two UndoStacks split their slice. There is an independent stack for the document pane and the workspace canvas, so
undoBytesPerStack()divides evenly.UndoStacklives inlibfluidcoreand must not depend on the GUI (ADR-0001), so the frontend sets each stack's budget explicitly at construction rather than the engine reading the pool. - Idle trim and restore.
trimToIdle()lowers every slice to 25% of its configured size, releasing cache surfaces back to the allocator instead of holding them until exit;restoreFromIdle()puts them back. Both are safe to call repeatedly.
4. Stylus Barrel Buttons — StylusButtonRouter
New src/app/services/StylusButtonRouter.{h,cpp} maps GDK stylus buttons and device events onto ink intents.
- Fixes barrel press panning the canvas. GDK's Win32 backend already surfaces Wintab hardware as ordinary GDK input — a pen arrives as
GDK_SOURCE_PENwith aGDK_AXIS_PRESSUREaxis, the eraser end asGDK_SOURCE_ERASER, and barrel buttons as plainGdkEventButtonpresses on that pen device. Left alone, a barrel press was indistinguishable from a middle click, so it panned the canvas or opened a context menu instead of erasing. - Stylus overrides mouse-oriented tools.
select,text,crop, andrect_selectare mouse modes that start a selection and consume the press. A stylus now overrides them; otherwise touching the page with a pen while the select tool was active selected text instead of drawing, forcing a mouse tool switch before every annotation. - Eraser is honoured as selected. An earlier revision overrode unconditionally, which meant a stylus press with the eraser selected drew a stroke at the eraser's forced width instead of erasing.
- Barrel-button state machine.
isBarrelButton()lets the eraser be held for the duration of a press. - No Windows Ink required. This is plain GDK input routing, the layer the rest of the app is already built on.
5. Release Pipeline Repair
The Linux release could not have succeeded, and this is the direct cause of repeated re-release attempts.
- AppImage step was guaranteed to fail.
appimagetoolhard-die()s with "mksquashfs command is missing but required" and "desktop-file-validate command is missing but required" before doing any work. Neithersquashfs-toolsnordesktop-file-utilswas in the runner's package list, so no AppImage was ever produced. Both are now installed inci.ymlandrelease.yml. - Two jobs raced to publish.
release-windowsandrelease-linuxeach calledsoftprops/action-gh-releasefor the same tag with no ordering. The first to finish published a live release containing only 3 of 6 assets, and a simultaneous create could return422 already_exists. Release is now a singlepublishjob gated onneeds:, withfail_on_unmatched_files: trueso a missing asset fails loudly instead of shipping a partial release. - Node 20 actions can no longer run. GitHub removed Node 20 from hosted runners on 2026-09-23 with no opt-out, which stranded
actions/checkout@v4,actions/upload-artifact@v4, andsoftprops/action-gh-release@v2. All are on Node 24 majors now. ubuntu-22.04is deprecated. The image entered deprecation on 2026-09-17 with hard brownouts scheduled before its 2027-04-17 retirement. Both workflows are pinned toubuntu-24.04;ubuntu-latestis also pinned because it migrates toubuntu-26.04between 2026-10-19 and 2026-11-19.- Portable zip used Windows path separators.
Compress-Archiveunder Windows PowerShell 5.1 writesshare\glib-2.0\schemas\gschemas.compiledinto the archive, which violates the ZIP specification. Non-Windows extractors materialised those as single flat files instead of a directory tree. The packager now writes entries by hand and then verifies that no entry contains a backslash and that the entry count matches. - Windows checksums were a truncated table.
Get-FileHash | Out-Fileemits a formatted table whosePathcolumn is ellipsis-truncated at console width, and it is not parseable bysha256sum -c. Both platforms now emit a standard<hash> <bare-filename>file. - Inno Setup could not accept a path with spaces.
/DSourceDistDir=and/DOutputDir=were passed unquoted through a space-joining argument list, andfluidcore.issused an unquotedOutputDir=. Both are now quoted. - Cairo pin now fails loudly.
curl -sSLhad no--fail, so a 404 body was written to disk with exit 0 andpacmanthen failed on a non-zstd file. The pin is-fsSLand short-circuits when the installed Cairo is already the pinned version. - Added the missing MSYS2 dependency.
mingw-w64-ucrt-x86_64-gtk-update-icon-cachewas required byCLAUDE.mdandops/CONTEXT.mdbut absent from both workflows, leaving the bundledshare\icons\hicolortree without a cache. - Added
.gitattributes. Without it, Windows checkouts silently rewrite LF to CRLF, which breaksops/scripts/*.sh(bad interpreter:bash^M) and makesops/patches/*.patchunapplyable because its context lines no longer match. Line endings are now pinned per file type. - Added
concurrency,timeout-minutes, and least-privilegepermissionsto both workflows, andworkflow_dispatchtorelease.ymlso a release can be re-run without deleting and re-pushing a tag.
📦 Platform Binaries & Pac...
v1.1.5
FluidCore v1.1.5 — Linux Touchpad Smooth Scrolling, Scrollbar Drag Performance, & Persistence Hardening
FluidCore v1.1.5 is a dedicated performance and bug fix release focused on reading fluidity, input responsiveness, and storage reliability across Linux and Windows:
-
Linux Touchpad Two-Finger Scrolling: Full support for continuous
GDK_SCROLL_SMOOTHtouchpad gestures under X11/libinput and Wayland. Two-finger scrolling now uses native GTK kinetic acceleration ($\Delta y \cdot \text{pageSize}^{2/3}$ ), while discrete mouse wheel scrolling continues to operate with native physical precision. - Buttery-Smooth Scrollbar Dragging: Completely eliminated scrollbar stutter and UI lag when rapidly browsing documents. Active viewport pinning preserves on-screen pages in memory without eviction thrashing, while interactive drag throttling keeps the GTK main loop executing at a silky 60 FPS.
-
Viewport Pinning Cache Eviction Fix: Separated partial damage clip rectangles from the active reading viewport in
DocumentPane::draw(). Visible pages are now protected from premature cache eviction during small scroll increments. -
Concurrency & Crop Tile Cache Stability: Hardened background crop rasterization in
PdfDocumentServiceand excerpt card rendering inExcerptTileCache. -
Project Store Legacy Schema Migration Hardening: Verified and hardened automated SQLite migrations for legacy projects containing external PDF paths (
external_path) and last viewed pages (last_viewed_page). -
Air-Gapped Zero-Telemetry Verification: Verified that all 39 CTest test suites pass with zero network calls and strict memory limits (
$\le 1.2\text{ GB}$ ).
🌟 What's New & Fixed in v1.1.5
1. Linux Touchpad Two-Finger Scrolling & Smooth Deltas
-
Smooth Scroll Event Mask Registration:
- Registered
GDK_SMOOTH_SCROLL_MASK | GDK_SCROLL_MASKon all viewport and overlay widgets:m_scroller(GtkScrolledWindow),m_overlay(GtkOverlay),m_area(GtkDrawingArea), andm_inkOverlay->widget(). - Connected
scroll-eventonm_inkOverlay->widget()to ensure gestures over active inking canvases are properly intercepted and processed.
- Registered
-
Continuous Kinetic Scrolling:
- In
DocumentPane::onScroll(), handledGDK_SCROLL_SMOOTHviagdk_event_get_scroll_deltas. - Updated vertical and horizontal adjustments (
vadj/hadj) using standard GTK smooth kinetics:
$$\Delta = \text{deltaY} \cdot \text{pageSize}^{2/3}$$ - Preserved discrete mouse wheel fallback (
return FALSE;) soGtkScrolledWindowcontinues handling physical scroll wheels natively.
- In
-
Clamped Trackpad Zoom & Squeeze Gestures:
- Clamped sub-pixel trackpad deltas during Ctrl+scroll zoom and Shift+scroll squeeze to
$[-2.5, 2.5]$ , preventing erratic zoom jumps when swiping on high-precision trackpads.
- Clamped sub-pixel trackpad deltas during Ctrl+scroll zoom and Shift+scroll squeeze to
2. Elimination of Scrollbar Dragging Lag & Cache Thrashing
- True Active Viewport Pinning:
- In
DocumentPane::draw(), separated the Cairo damage rectangle (clip) from the active viewport ([viewYStart, viewYEnd]). - All pages currently overlapping the on-screen viewport (plus prefetch margin) are pinned in
PageTileCache. - Eliminates the severe cache thrashing loop where partial-clip redraws unpinned visible pages, forcing repeated synchronous Poppler rasterization on every scroll tick.
- In
- Interactive Scrollbar Drag Throttling:
- Connected
button-press-eventandbutton-release-eventtogtk_scrolled_window_get_vscrollbar(), trackingm_isScrollbarDragging. - Added a 100ms debounce timer on
vadj'svalue-changedsignal. - During rapid scrollbar dragging across uncached pages,
draw()renders clean white page placeholder rectangles at 60 FPS without synchronously lockingglobalPopplerMutex()or blocking the UI thread. - As soon as dragging settles or the mouse button is released, the settled viewport page is immediately rasterized and cached.
- Connected
3. Storage & Migration Hardening
- Schema Migration Conformity in
ProjectStoreTest:- Fixed legacy DDL migration test in
ProjectStoreTest.cppto use the canonicalprojectstable andfile_path_relativecolumn name. - Ensured seamless rehydration of external PDF document paths (
external_path) and last reading position across legacy.ltprojbundles.
- Fixed legacy DDL migration test in
📦 Platform Binaries & Packaging
- Windows 11 (Native):
- Inno Setup installer:
FluidCore-Setup-x64.exe(v1.1.5) - Portable zip:
fluidcore-windows-x64.zip(v1.1.5) - Resources:
src/app/fluidcore.rcversion set to1.1.5.0
- Inno Setup installer:
- Linux (Ubuntu / Debian / Fedora / Arch):
- AppImage:
FluidCore-1.1.5-x86_64.AppImage - Debian package:
fluidcore_1.1.5_amd64.deb - AppStream metainfo:
resources/linux/org.fluidcore.platform.metainfo.xmlupdated with v1.1.5 release metadata
- AppImage:
🧪 Verification & Test Results
-
WSL Linux Build: Clean build with
make -j4(0 warnings, 0 errors). -
Windows Ninja Build: Clean build with
ops\scripts\build-win.ps1(0 warnings, 0 errors). - CTest Suite: 39/39 tests passed (100% pass rate).
-
Memory & Scalability: Verified with
ScalabilityBenchmarkTest($\le 1.2\text{ GB}$ ceiling preserved).
Full Changelog: v1.1.4...v1.1.5
v1.1.4
FluidCore v1.1.4 — Spacebar Minimap Peeking, Clipboard Image Pasting, R-Tree Robustness, & Persistence Hardening
FluidCore v1.1.4 is a major stability, usability, and bug fix release focused on canvas workflow speed, multimodal content import, spatial query correctness, and project persistence guarantees:
-
Temporary Spacebar Minimap Peeking: Hold down
Spacebarto temporarily reveal and inspect the minimap radar overlay at any time; releasingSpacebarautomatically restores your canvas view without disrupting your active tool or workflow. -
Native Clipboard Image Pasting (
Ctrl+V) & Drag-and-Drop Raster Images: Paste screenshots and images directly from your system clipboard or drag-and-drop image files (PNG, JPG, WEBP, etc.) straight onto the canvas. Images are saved into project assets, automatically scaled with aspect ratio preservation, and registered as excerpt cards with full Undo/Redo support. -
External PDF Path Retention & Robust Document Resolution: Projects now persist and restore absolute external paths (
external_path) alongside bundle-relative paths for referenced documents. Multi-strategy document resolution ensures annotations and excerpt crops never detach even across directory renames or different mount points. -
R-Tree Spatial Index Geometric Correctness & Node Lifecycle Hardening*: Upgraded intersection tests to inclusive bounds (
$\le$ ), added handling for zero-area degenerate boxes (point strokes), tightened parent bounds correctly during deep recursive splits, and implemented clean root collapsing when elements are deleted and re-inserted. -
Atomic SQLite Transaction Rollback Protection: Project saves now enforce strict transactional rollback (
ROLLBACK;) if any statement fails during differential upserts, preventing partial or corrupted database commits. - Zero-Length Click Stroke Clutter Prevention: Accidental single clicks or micro-displacement taps (< 1.0 px) on both the PDF document reader and the canvas inking surface are filtered out, preventing stray dot strokes or unintentional connector line conversions.
-
Refined Keyboard Shortcuts & Accelerators: Unified
DeleteandBackspacehandling for selected cards and connector lines,Ctrl+Mpersistent minimap toggle,Alt+1..Alt+6andF1..F6tool accelerators, andEsctool reset.
🌟 What's New & Fixed in v1.1.4
1. Spacebar Minimap Peeking & Synchronized Viewport Navigation
- Temporary Radar Peeking via
Spacebar:- In addition to canvas panning (
Space + Drag), pressing and holdingSpacebarnow activates a live peek of the minimap navigation widget (isSpacePeekingMinimap = true). - Upon releasing
Spacebar, the minimap gracefully dismisses if persistent minimap mode was turned off.
- In addition to canvas panning (
- Bi-Directional State Synchronization:
- The top toolbar minimap button and
Ctrl+Mkeyboard shortcut stay strictly in sync withWorkspaceViewstate viasetOnMinimapVisibilityChanged. - Added re-entrancy protection (
m_updatingMinimapState) preventing cascading toggle signal loops.
- The top toolbar minimap button and
- High-Performance Minimap Rendering:
- Optimized minimap rendering loop to filter out micro canvas ink strokes (
CanvasStrokeNode), drastically reducing Cairo drawing overhead when hundreds of handwriting strokes are present while maintaining full visibility for cards, decks, and structural groups.
- Optimized minimap rendering loop to filter out micro canvas ink strokes (
2. Clipboard Image Pasting (Ctrl+V) & Raster Drag-and-Drop
-
Direct Clipboard Paste (
Ctrl+V):- Paste screenshots or copied images directly from the OS clipboard into the infinite canvas.
- Automatically discriminates between text and image clipboard payloads, preserving normal text entry if a text entry box is focused.
- Generates unique timestamped PNG assets in
assets/images/inside the project bundle (or session assets for unsaved workspaces).
-
Drag-and-Drop Image Files:
- Dragging PNG, JPEG, JPG, BMP, WEBP, GIF, TIFF, or ICO files into the workspace window seamlessly ingests and embeds them into the workspace.
-
Aspect-Ratio-Preserving Geometry:
- Image cards calculate optimal viewport dimensions based on native image aspect ratio:
$$W = 320\text{ px}, \quad H = \text{clamp}\left(\text{header} + \text{padding} + (W - 32) \cdot \frac{\text{imgH}}{\text{imgW}},, 120,, 800\right)$$ - Placed directly at current viewport center or drop coordinates, and pushed onto the undo stack via
InsertNodeCommand.
- Image cards calculate optimal viewport dimensions based on native image aspect ratio:
-
High-Fidelity Raster Rendering:
-
WorkspaceRendererincludes dedicated raster image caching (s_imgSurfaceCache) and header title formatting, cleanly rendering image crops alongside PDF page excerpts.
-
3. External Document Persistence & Resolution Fallback Engine
- Persistent External PDF Paths:
- Added
external_path TEXTcolumn todocumentsSQLite table schema inProjectStore. - When referencing external documents without copying them into the bundle, FluidCore permanently remembers the source filesystem location.
- Added
- Companion Path Tracking in
DocumentPane:- Decoupled document viewing path from annotation storage path via
m_companionPath, allowing annotations to be loaded and saved to dedicated companion files or directly to external PDFs.
- Decoupled document viewing path from annotation storage path via
- Multi-Tiered Document Resolution:
- Enhanced
PdfDocumentService::resolveEntryLockedwith multi-strategy resolution: exact docId match, normalized canonical path matching, filename-only matching, filesystem existence fallback, and single-document project fallback. - Robust URI encoding and file path normalization prevent malformed file URI errors on Windows drive letters and Linux paths.
- Enhanced
4. R*-Tree Spatial Index Geometric Robustness
-
Inclusive Boundary Overlaps:
- Changed box intersection from strict inequality (
<) to inclusive inequality (<=):
$$\text{intersects}(A, B) \iff A.x \le B.x + B.w ;\land; B.x \le A.x + A.w ;\land; A.y \le B.y + B.h ;\land; B.y \le A.y + A.h$$ - Resolves edge-touching query misses where cards or strokes sharing boundary coordinates were erroneously excluded from viewport queries.
- Changed box intersection from strict inequality (
-
Degenerate Point Rectangles:
- Fixed
unionOfto allow zero-width and zero-height bounding boxes (a.w < 0 || a.h < 0), properly supporting point hits and zero-size ink stubs.
- Fixed
-
Deep Split Bounds Propagation:
- Ensured
tightenUp(nodeIdx)is systematically invoked after multi-level node splits, guaranteeing that parent bounding boxes strictly enclose all child nodes up to the root.
- Ensured
-
Root Collapse & Re-Insertion Safety:
- Implemented
collapseRootIfNeeded(), properly recycling empty roots and collapsing single-child interior roots when nodes are deleted or moved, preventing tree corruption when clearing a canvas and inserting new nodes.
- Implemented
5. Project Persistence & Transaction Integrity
- Atomic Rollback on SQLite Statement Failure:
- Introduced
executeOrRollbackinProjectStore::saveProject. - Any error encountered during metadata update, document registration, node upsert, edge upsert, or deletion immediately executes
ROLLBACK;, preventing partial database writes.
- Introduced
- Project Dirty Tracking:
UndoStackchange listeners automatically mark the project dirty (viewCtx->isProjectDirty = true) when uncommitted actions exist, preventing accidental lost work.
6. Micro-Tap Ink Clutter Prevention & Usability Polish
-
Accidental Click / Micro-Tap Filtering:
- Ink strokes with
$\le 1$ sample or displacement$|\Delta \vec{p}| < 1.0\text{ px}$ are filtered out in bothInkOverlayandWorkspaceView. - Eliminates accidental dot marks and false-positive connector stroke conversions when clicking to focus.
- Ink strokes with
-
Unified Deletion:
- Pressing
DeleteorBackspacecleanly removes whichever entity is selected (card, stack, or relational link connector) viadeleteSelected().
- Pressing
-
Keyboard Accelerators:
-
Ctrl + M: Toggle minimap -
Ctrl + V: Paste image from clipboard -
Alt + 1..Alt + 6&F1..F6: Direct tool selection (Pen, Highlighter, Eraser, Select, Crop, Connector) -
Esc: Cancel active interaction, clear text/crop selections, and return tool to Select
-
📦 Downloads & Binaries
| Platform | Package | Format | File Name | Description |
|---|---|---|---|---|
| Windows | Native Installer | .exe |
FluidCore-Setup-x64.exe |
Recommended for Windows. Inno Setup 64-bit installer with Start Menu integration, desktop shortcut, uninstaller, and .ltproj project associations. |
| Windows | Portable Zip | .zip |
fluidcore-windows-x64.zip |
Standalone portable archive containing all required UCRT64 runtime DLLs, GLib schemas, GDK-Pixbuf loaders, and Adwaita icons. Run anywhere without admin privileges. |
| Linux | AppImage Bundle | .AppImage |
FluidCore-1.1.4-x86_64.AppImage |
Recommended for Linux. Standalone portable executable with bundled dependencies and Wayland/X11 support. Run anywhere (chmod +x). |
| Linux | Debian Package | .deb |
fluidcore_1.1.4_amd64.deb |
Native Debian/Ubuntu package (apt install ./fluidcore_1.1.4_amd64.deb) with system dependency management and FreeDesktop menu entry. |
| Linux | Flatpak Manifest | .yml |
ops/flatpak/org.fluidcore.platform.yml |
Sandboxed Flathub distribution manifest with zero-network isolation (--unshare=network). |
🧪 Verification & Test Suite Summary
- 100% CTest Suite Passing: All 39/39 test suites passing with zero regressions:
RTreeIndexTest: Validates inclusive boundary intersection, degenerate point queries, deep-split recursive bounds tightening, and empty root collapse/re-insertion.ProjectStoreTest: Validatesexternal_pathround-trip persistence, idempotent schema upgrades, and transactional rollback on failures.WorkspaceInteractionTest: Validates `isS...
FluidCore v1.1.3 — Excerpt Card Resizing, Smooth Bézier Inking, Layer Stacking, & Memory Remediation
FluidCore v1.1.3 — Excerpt Card Resizing, Smooth Bézier Pen Inking, Layer Stacking, & Comprehensive Memory Remediation
FluidCore v1.1.3 delivers high-impact slice-of-life usability enhancements, inking rendering fidelity upgrades, and deep architectural memory leak remediations:
- Interactive Excerpt Card Resizing: Resize text and visual crop excerpt cards with live high-resolution rasterization, strict aspect-ratio locking for visual crops, and full Undo/Redo integration.
- Silky-Smooth Canvas Pen Inking: Eliminates low-pixel faceting, jagged lines, and polygonal clipping on tight circles and loops via curvature-adaptive deadzone scaling and centripetal Catmull–Rom Bézier spline interpolation.
- Highlighter Layer Stacking: Highlighter strokes always render cleanly beneath solid pen strokes across both the PDF Document Reader and the Infinite Spatial Workspace canvas.
- PDF Page Position Persistence: Projects now remember and automatically restore the active PDF reading page across saves and reloads, backed by an idempotent SQLite schema migration.
- Comprehensive Memory Leak Remediation: Root-cause resolution of MinGW
winpthreadmutex retention via native Win32CRITICAL_SECTIONpatching, ephemeral background search workers, andmimallocheap slack reclamation. - Linux Distribution Packaging & ABI Portability Fix: Re-targeted Linux CI and release packagers to Ubuntu 22.04 LTS (GLIBC 2.35), statically linked C++ runtime (
-static-libgcc -static-libstdc++), and refined library bundling in AppImage/.deb to eliminateGLIBC_2.38andGLIBCXX_3.4.31launch errors.
🌟 What's New in v1.1.3
1. Interactive Excerpt Card Resizing
-
Intuitive Visual Grab Handles:
- Hovering over or selecting any excerpt card reveals a 14 px circular grab handle at the bottom-right corner.
- Contextual cursor updates to
se-resize(GDK_BOTTOM_RIGHT_CORNER) upon hover.
-
Strict Aspect-Ratio Locking for Visual Crop Cards:
- Image crop cards automatically lock and preserve their original
$(W:H)$ aspect ratio:
$$\text{scale} = \max\left(\frac{W_0 + \Delta x}{W_0},, \frac{H_0 + \Delta y}{H_0}\right), \quad W = \text{clamp}(W_0 \cdot \text{scale}, 160, 1600), \quad H = \frac{W}{AR}$$ - As visual cards enlarge or shrink, the excerpt image dynamically scales to fit the card geometry without any distortion or letterboxing.
- Image crop cards automatically lock and preserve their original
-
Full Undo / Redo Support (
Ctrl+Z/Ctrl+Y):- Backed by the newly implemented
ResizeNodeCommandinlibfluidcore. - Seamlessly pushes commands to the undo stack upon mouse release, with automatic R-Tree spatial index recalculation.
- Backed by the newly implemented
-
Dynamic Tile Cache Invalidation:
- Upon completing a card resize, the excerpt tile cache automatically invalidates and re-renders the underlying PDF region at the new scale, ensuring vector-sharp clarity even at large expansions.
2. Silky-Smooth Canvas Pen Inking & Anti-Faceting Engine
-
Curvature-Adaptive Deadzone Filtering:
- Previous fixed-distance stabilization deadzones (
$1.5$ –$2.0$ px) truncated sharp directional changes, turning small loops and circles into faceted polygons at low pixel widths (1–2 px). - The new
StrokeStabilizermeasures the turning angle between successive stroke vectors:
$$\cos \theta = \frac{\Delta \vec{v}{i} \cdot \Delta \vec{v}{i-1}}{|\Delta \vec{v}{i}| , |\Delta \vec{v}{i-1}|}$$ - When a sharp turn or circle is detected (
$\cos \theta < 0.95$ ), the deadzone automatically scales down to$0.15$ px, preserving tight curves and circular handwriting with zero polygonal flattening.
- Previous fixed-distance stabilization deadzones (
-
Centripetal Catmull–Rom Bézier Splines:
- Replaced linear segment chaining with centripetal Catmull–Rom cubic Bézier splining across both committed canvas strokes and live in-flight wet ink.
- Live wet ink and committed nodes evaluate 4-step sub-segment Bézier curves with smooth pressure interpolation via
evalCubicBezier, eliminating visual "popping" or snapping upon pen-up.
3. True Layer Stacking: Highlighter Sits Beneath Pen
- PDF Document Overlay (
InkOverlay):- Split rendering into a two-pass compositor: Pass 1 renders all highlighter strokes in semi-transparent mode (
CAIRO_OPERATOR_OVER); Pass 2 renders solid ink pens directly on top.
- Split rendering into a two-pass compositor: Pass 1 renders all highlighter strokes in semi-transparent mode (
- Infinite Spatial Workspace Canvas (
WorkspaceRenderer):- Re-architected canvas layer stacking order:
- Excerpt Cards, Stacks, and Structural Elements
- Committed Highlighter Strokes
- Active Wet Inking Highlighter Stroke
- Committed Pen Strokes
- Active Wet Inking Pen Stroke
- Selection Rings, Grab Handles, and Connectors
- Highlighting over existing writing or sketching never dims, occludes, or muddies pen strokes.
- Re-architected canvas layer stacking order:
4. PDF Reading Position Persistence Across Project Reloads
- Zero-Friction Context Restoration:
- Saving a project (
Ctrl+S) records the exact PDF page currently visible in the document reader viewport. - Reopening the
.ltprojproject bundle automatically scrolls the document reader directly to that page once dimensions and layout are computed.
- Saving a project (
- Idempotent SQLite Schema Migration:
- Added
last_viewed_page INTEGER NOT NULL DEFAULT 0to thedocumentstable inProjectStore. - Uses
PRAGMA table_info(documents)inspection prior to runningALTER TABLE, guaranteeing seamless, idempotent upgrades of existing project databases without throwing duplicate column errors.
- Added
5. Deep Memory Leak Remediation & Architecture Hardening
- Native Win32 CriticalSection Remediations:
- Diagnosed and resolved a subtle memory retention bug in MinGW-w64's
winpthreadlibrary, wherestd::mutexallocations bypassed standard CRT cleanup upon thread termination. - Applied native Win32
CRITICAL_SECTIONfallbacks inops/patches/poppler_win32_critical_section.patchfor rock-solid concurrency stability.
- Diagnosed and resolved a subtle memory retention bug in MinGW-w64's
- Ephemeral Background Search Document Lifecycle:
- Document search workers now utilize an ephemeral
PopplerDocumentlifecycle and scheduled worker thread recycling (every 8 searches), releasing page descriptor caches immediately upon search completion.
- Document search workers now utilize an ephemeral
mimallocHardened Heap Management:- Linked
mimallocacrossfluidcore_appwithPurgeDelay=0and active decommit flags to aggressively return unmapped virtual memory slack directly to Windows. - Added internal live memory telemetry (
MemoryTelemetry.h) and telemetry monitoring scripts indebug/scripts/.
- Linked
6. Linux Distribution Packaging & ABI Portability Hardening
- Universal GLIBC 2.35+ Compatibility:
- Re-targeted GitHub Actions release and packaging runners from
ubuntu-24.04toubuntu-22.04. - Releases are now built against GLIBC 2.35, resolving runtime failures (
version GLIBC_2.38 not found) on Ubuntu 22.04 LTS, Debian 12, Linux Mint 21, and other enterprise distributions, while maintaining forward compatibility with Ubuntu 24.04+.
- Re-targeted GitHub Actions release and packaging runners from
- Static C++ Runtime Linking (
-static-libgcc -static-libstdc++):- Statically linked
libstdc++andlibgccintofluidcore_appon Linux, eliminatingGLIBCXX_3.4.xxruntime version mismatches.
- Statically linked
- AppImage & Debian Package Refinements:
- Updated
package-appimage.shexcludelist to prevent bundlinglibstdc++/libgcc_swhich could shadow newer host GPU display drivers. - Enhanced
package-deb.shdependencies withlibpoppler-glib8 (>= 20.0) | libpoppler-glib8t64for seamless installation across both Ubuntu 22.04 and Ubuntu 24.04 (t64 transition).
- Updated
📦 Downloads & Binaries
| Platform | Package | Format | File Name | Description |
|---|---|---|---|---|
| Windows | Native Installer | .exe |
FluidCore-Setup-x64.exe |
Recommended for Windows. Inno Setup 64-bit installer with Start Menu integration, desktop shortcut, uninstaller, and .ltproj project associations. |
| Windows | Portable Zip | .zip |
fluidcore-windows-x64.zip |
Standalone portable archive containing all required UCRT64 runtime DLLs, GLib schemas, GDK-Pixbuf loaders, and Adwaita icons. Run anywhere without admin privileges. |
| Linux | Debian Package | .deb |
fluidcore_1.1.3_amd64.deb |
Native Debian/Ubuntu package (apt install ./fluidcore_1.1.3_amd64.deb) with system dependency management and FreeDesktop menu entry. |
| Linux | AppImage Bundle | .AppImage |
FluidCore-1.1.3-x86_64.AppImage |
Recommended for Linux. Standalone portable executable with bundled dependencies and Wayland/X11 support. Run anywhere (chmod +x). |
🧪 Verification & Test Suite Summary
- 100% CTest Suite Passing: All 39/39 unit and integration test suites pass with zero failures:
ProjectStoreTest: ValidateslastViewedPagepersistence round-trip and idempotent schema migration.UndoStackTest: ValidatesResizeNodeCommandexecution, undo, redo, and spatial R-Tree index bounding box updates.StrokeStabilizerTest: Validates adaptive curvature deadzone scaling on high-frequency directional turns.PopplerMalformedRegressionTest&PopplerConcurrencyTest: Validates thread safety and memory bounds.
- Invariants Gate:
python ops/scripts/check_invariants.pypassed (zero runtime network calls, strictlibfluidcoreengine boundary). - Code Style Compliance: 100% compliant with
clang-format --dry-run --Werror.
v1.0.2 — Custom Pen & Highlighter Controls, Smooth Inking Ribbon, & CI Hardening
FluidCore v1.0.2 introduces user-configurable stroke weights and colors for both the Pen and Highlighter tools, an interactive contextual toolbar UI with per-tool memory, and a complete rendering overhaul of the infinite canvas highlighter engine for silky-smooth, bead-free translucent highlighting.
🌟 What's New in v1.0.2
1. Custom Inking Properties with Independent Tool Memory
- Per-Tool Memory: Pen and Highlighter now independently remember their custom color and thickness. Switching tools or toggling the Eraser (
E) preserves your exact settings. - Architectural Grounding:
ToolManagerserves as the single source of truth, synchronizing properties in real time across the PDF Reader (DocumentPane/InkOverlay) and Infinite Spatial Workspace (WorkspaceView/WorkspaceRenderer).
2. Modern Contextual Toolbar UI (TopToolbarWidget)
- Interactive Color Pill (
fc-color-pill):- Displays the active tool's color swatch.
- Clicking opens the Color Popover with curated high-contrast palettes tailored for Pen (8 rich tones) and Highlighter (6 vibrant translucent tones).
- Includes a direct Hex code input (
#RRGGBB) with real-time validation and a native system color dialog launcher.
- Interactive Weight Pill (
fc-weight-pill):- Live thickness dot and numeric label (e.g.,
2.0 pt/14.0 pt). - Clicking opens the Weight Popover featuring:
- Live Anti-Aliased Preview Canvas: Visualizes stroke thickness, round caps, and exact alpha compositing before you draw.
- Quick Preset Pills: One-click thickness options (
1px,2px,4px,8pxfor Pen;8px,14px,22px,32pxfor Highlighter). - Continuous Slider & Stepper Buttons: Smooth
GtkScaleadjustments with[-]and[+]nudge controls.
- Live thickness dot and numeric label (e.g.,
- Context-Aware Sensitivity: Inking pills automatically enter a subtle dimmed state when non-drawing tools (Select, Eraser, Crop, Connector) are active.
3. Keyboard Shortcuts for Inking
- Press
[to decrement active tool stroke thickness (by 0.5 pt for Pen, 2.0 pt for Highlighter). - Press
]to increment active tool stroke thickness (by 0.5 pt for Pen, 2.0 pt for Highlighter).
4. Silky-Smooth Translucent Highlighter Rendering
- Eliminated Overlapping "Beads" / "Caterpillar" Artifact: Replaced per-segment direct canvas drawing with isolated offscreen group compositing (
cairo_push_group/cairo_pop_group_to_source). Overlapping segment round caps now fuse at 100% opacity into a unified mask before being stamped onto the canvas in a single pass at uniform 45% alpha. - Centripetal Catmull–Rom Bézier Splines: Raw, faceted polygonal line segments are now interpolated with smooth cubic Bézier curves (
cairo_curve_to), matching the PDF document overlay standard. - Consistent In-Flight vs. Committed Rendering: Live in-flight strokes and committed workspace nodes use identical Bézier mathematics, eliminating visual popping or shape warping upon stylus/mouse release.
- Preserved Pen Dynamics: Fountain pen pressure variation and calligraphic stroke dynamics remain 100% untouched.
5. Build System & CI Hardening
- Added
libsqlite3-dev,zlib1g-dev, anddpkg-devpackages to Ubuntu CI jobs to ensure zero packaging failures on GitHub Actions runners. - Hardened CMake SQLite target resolution in
src/libfluidcorewith seamless fallback betweenSQLite::SQLite3andSQLite3::SQLite3. - Expanded unit test coverage with
ToolManagerTest(38/38 CTest suites passing with 100% success on both Windows and Linux).
📦 Downloads & Binaries
| Platform | Package | Format | File Name | Size | SHA-256 Checksum |
|---|---|---|---|---|---|
| Windows | Native Installer | .exe |
FluidCore-Setup-x64.exe |
30.0 MB | 8BA8767A3BC66AC4F9A9E115DF1EB01F227A00FB93D59F5B105FCD66FBA60BA5 |
| Windows | Portable Zip | .zip |
fluidcore-windows-x64.zip |
41.8 MB | DDD1B73015E5A36677BE99D92B354FFDCF17A852AAACE6246EE615C7257F17A5 |
| Linux | Debian Package | .deb |
fluidcore_1.0.2_amd64.deb |
6.9 MB | D94F26F06F3B389259BCA876B275A05131B69279809E210B6F5C65D334D15AB8 |
| Linux | AppImage Bundle | .AppImage |
FluidCore-1.0.2-x86_64.AppImage |
34.0 MB | 9D087337161848A5B34AF5D386F1B6342C781F4DC4F7C7CC65FC43AB7A694938 |
🚀 Verification & Integrity
To verify package integrity after download:
Windows (PowerShell):
Get-FileHash FluidCore-Setup-x64.exe -Algorithm SHA256
Get-FileHash fluidcore-windows-x64.zip -Algorithm SHA256
**Full Changelog**: https://github.com/developerakshat12/FluidCorePDF/compare/v1.0.1...v1.0.2
**Full Changelog**: https://github.com/developerakshat12/FluidCorePDF/compare/v1.0.1...v1.0.2# FluidCore v1.0.1 — Bug Fixes & Stability Release
FluidCore v1.0.1 — Bug Fixes & Stability Release
FluidCore is an offline-first, high-performance fluid document synthesis platform built with C++20, Cairo, and GTK3. This release resolves coordinate-space mapping issues during excerpt cropping, stabilizes cursor-anchored focal zoom, adds bi-directional card navigation, and updates official distribution packages for Windows and Linux.
🚀 Key Highlights & Fixes
1. Authoritative Annotation Capture on Excerpt Crops
- Fixed Missing Annotations on Arbitrary Page Sizes: Fixed an issue where pen and highlighter annotations rendered on non-standard PDF page geometries (e.g. 459×666 vs. standard 612×792) failed bounding-box intersection tests during cropping.
-
Normalized Spatial Contract (
$[0.0 \dots 1.0]$ ):ExcerptTileCachenow strictly operates on normalized crop coordinates. Guessing or falling back to default page sizes has been eliminated. -
Authoritative Geometry Querying:
DocumentPanequeries the actual vector page dimensions (getPageDimensions()) and uses a strict 4-tier document ID match to eliminate cross-document annotation leakage.
2. Focal Zoom Invariance (ViewportZoomAnchor)
- Cursor-Anchored Zooming: Introduced
ViewportZoomAnchor, a dedicated mathematical solver that preserves the exact document point under the mouse cursor or viewport center during zoom transitions. - Deep Document Navigation: Eliminated visual jumps and drift when zooming in deep documents (e.g., page 87+).
- Invariance Test Suite: Verified via
ViewportZoomAnchorTest, covering zoom-in, zoom-out, centered toolbar zoom, and scroll boundary clamping.
3. Enhanced Card Navigation & Anchor Return
- Interactive Return Anchor: Added a clickable left-edge anchor pill (
◀) on excerpt cards. Clicking the pill jumps the document reader back to the exact source page and excerpt location. - Refined Hit-Testing: Updated card selection and bounding-box hit testing in
CardLayoutEnginefor smooth dragging and resizing.
📦 Downloads & Official Distribution Assets
| Platform | Format | Asset Name | Description |
|---|---|---|---|
| Windows | Native Installer | FluidCore-Setup-x64.exe |
Recommended for Windows. Inno Setup 64-bit installer with Start Menu integration, desktop shortcut, uninstaller, and .ltproj file associations. |
| Windows | Portable Zip | fluidcore-windows-x64.zip |
Standalone portable archive with bundled UCRT64 runtime DLLs, GLib schemas, GDK-Pixbuf loaders, and Adwaita icons. Run without admin privileges. |
| Linux | AppImage Bundle | FluidCore-1.0.1-x86_64.AppImage |
Recommended for Linux. Standalone portable executable with bundled dependencies, desktop integration, and Wayland/X11 support. (chmod +x && ./FluidCore-1.0.1-x86_64.AppImage) |
| Linux | Debian Package | fluidcore_1.0.1_amd64.deb |
Native Debian/Ubuntu package with system dependency management and FreeDesktop menu integration. (sudo apt install ./fluidcore_1.0.1_amd64.deb) |
| Linux | Flatpak Manifest | ops/flatpak/org.fluidcore.platform.yml |
Sandboxed distribution manifest for Flathub with zero-network isolation (--unshare=network). |
⚡ Verification & Invariants
- Unit & Integration Tests: 37/37 test suites passing (100%) on Windows (MSYS2 UCRT64) and Linux (Ubuntu 24.04).
- Scalability: Cold load duration 0.02s across 50 PDFs (5,000 vector pages) with bounded peak working set (62.6 MB vs. 1.2 GB budget).
- Code Quality: Formatted according to repository
.clang-formatrules.
Full Changelog: v1.0.0...v1.0.1
Full Changelog: v1.0.0...v1.0.1
FluidCore v1.0.0 — Official Full Release
FluidCore v1.0.0 — Official Full Release 🚀
FluidCore v1.0.0 is officially released! FluidCore is an open-source, offline-first alternative to LiquidText designed for active reading, deep comprehension, and complex knowledge synthesis. It fuses a malleable PDF reader with an infinite 2D thinking canvas.
Available natively for Windows 11 and Linux (Ubuntu, Debian, Fedora, Arch).
What's New in v1.0.0
-
Accordion Squeeze: Dynamically collapse non-adjacent pages with pinch or
Ctrl+Shift+Scrollto view distant paragraphs, formulas, or figures side-by-side. -
Search-Driven Squeeze: Press
Ctrl+ForCtrl+Shift+Sto automatically fold intermediate material and bring all search hits into view with fold crease markers. -
Direct PDF Vector Inking: Annotate PDFs with pressure-sensitive stylus strokes, translucent highlighter overlays, Catmull-Rom curve stabilization (
$\le 8.05\text{ ms}$ latency), and two-phase whole-stroke eraser. -
Infinite 2D Thinking Canvas: Drag excerpts and image crops onto an unbounded canvas indexed with an
$O(\log N)$ R*-tree spatial index. - Freehand Stylus Notes & Palm Rejection: Sketch freehand diagrams and handwriting notes directly on the canvas with dynamic hardware profiles (Wacom EMR/AES, Microsoft Surface, HP MPP).
- Magnetic Card Stacking: Snapping cards together (16pt threshold) merges them into reorderable decks with accordion cascade previews and inline rename popovers.
-
Relational Bezier Ink Connectors: Draw lines between thoughts with the connector tool (
Alt+6/F6/A/L) to create persistent Bezier edges (GraphEdge) that route around obstacles. -
Bi-Directional Anchors: Click
↗ Anchoron any excerpt to glide smoothly to the source passage in the PDF; click↶ Returnto glide back to your canvas thought. - Synthesis Export: Export flattened PDFs with burnt annotations or clean Markdown outlines complete with Mermaid flowcharts.
-
100% Offline & Private: Self-contained
.ltprojdirectory bundles backed by SQLite WAL with atomic temp-swap commits. Zero telemetry, zero cloud calls, completely air-gapped.
Integrity & Checksums
SHA256 checksums are attached in checksums-windows-sha256.txt and checksums-linux-sha256.txt. Verify via:
# Windows
Get-FileHash <filename> -Algorithm SHA256
# Linux
sha256sum <filename>
**Full Changelog**: https://github.com/developerakshat12/FluidCorePDF/compare/v0.9.0...v1.0.0
**Full Changelog**: https://github.com/developerakshat12/FluidCorePDF/compare/v0.9.0...v1.0.0FluidCore v0.9.0 — Windows Native Pre-Release
FluidCore Platform v0.9.0 (Windows Native Pre-Release)
An open-source, offline-first alternative to LiquidText designed for active reading and synthesis: a fluid, malleable PDF reader fused with an infinite 2D thinking canvas.
This pre-release introduces native Windows 11 support, standalone installation packages, and the core active-reading toolset.
✨ What's in this Pre-Release
- Accordion Squeeze: Pinch or
Ctrl+Shift+Scrollto collapse non-adjacent pages and compare distant sections side-by-side. - Infinite 2D Canvas & Excerpts: Drag text or visual image crops from your documents onto an unbounded workspace with R*-tree spatial indexing.
- Bi-Directional Anchors: Click
↗ Anchoron any card to glide to the original source location in the document; click↶ Returnto jump back to your canvas thought. - Relational Ink Connectors: Draw lines between cards with the connector tool (
Alt+6/F6) to create persistent Bezier relational links (GraphEdge). - Magnetic Card Stacking: Drop cards within 16pt proximity to snap them into collapsible, reorderable topic decks.
- Full-Text & Tag Search: Fast in-memory search across document text, canvas snippets, and tags (
#tag,tag:xyz) with camera glide navigation. - Synthesis Export: Flatten annotated vector PDFs or export Markdown outlines with embedded Mermaid flowcharts.
- 100% Offline & Private: Stored in
.ltprojbundles (SQLite WAL format) with atomic temp-swap commits. Zero telemetry, zero cloud calls, air-gapped security.
📦 Downloads for Windows (x64)
| Asset | Description |
|---|---|
FluidCore-Setup-x64.exe |
Recommended. Native 64-bit installer with Start Menu integration, desktop shortcut, uninstaller, and .ltproj file associations. |
fluidcore-windows-x64.zip |
Portable standalone zip containing all bundled UCRT64 runtime DLLs, GLib schemas, and icons. Run anywhere without administrator rights. |
Checksums (SHA-256)
FluidCore-Setup-x64.exe:3470501C5159393E3AA572335E44815C95017909A880B043B2F3C4F5716582DDfluidcore-windows-x64.zip:EC9731B54B25F4DB60DECD751260009ECBE910EC71E897D4790EE93DBA8E034C
Note: Hardening tasks (keyboard accessibility and i18n) and Linux package bundles remain in active progress toward the v1.0 release.
Full Changelog: https://github.com/developerakshat12/FluidCorePDF/commits/v0.9.0