Loss-aware by design
Anything the model can't yet represent — a chart, a pivot cache, a rare style bit — is reproduced verbatim or reported. Never silently discarded.
OpenCalc reads and writes real .xlsx, keeps the workbook in a
normalized editable model, recalculates it, and renders a virtualized grid —
the same Rust core on the desktop, in the browser through WebAssembly, and
headless on a server.
Nothing is uploaded — the engine runs on your machine. Apache-2.0.
Why OpenCalc
Every layer is testable in isolation, and every byte it writes is reproducible. That is what makes it safe to embed in something you ship.
Anything the model can't yet represent — a chart, a pivot cache, a rare style bit — is reproduced verbatim or reported. Never silently discarded.
The same input and engine version produce the same model, values, layout, and bytes — so both calc and render are regression-tested against golden files.
No mandatory DOM, server, UI framework, or collaboration provider. Rust hosts, wasm32, desktop, and headless services all consume the same core.
A canvas grid with real selection, drag-fill, merges, freeze panes, number formats and keyboard navigation — drawn from the engine's own layout, not the DOM.
Packages are admitted under explicit entry, path, size, expansion and resource limits. No macro execution, no automatic external fetches.
A layered crate DAG from model and parser up to layout and render, unsafe forbidden, Apache-2.0. Read every line before you depend on it.
Shipping now
A verified list, not a roadmap — everything here is exercised by the editor you just used. What is still pending is in the roadmap below.
Reads and writes .xlsx · imports and exports CSV, TSV, PSV · formatting, tables, charts, validation, protection, print setup and defined names survive the round-trip — and anything not yet modelled is carried through byte for byte rather than dropped.
Three products, one engine
The engine above is the same in all three — the same import, the same 347 functions, the same writer. What differs is where it runs and what it is allowed to reach.
| SDK @opencalc/sheet · 0.0.1 |
Desktop app 0.0.3 |
Self‑hosted three images · 0.0.1 |
|
|---|---|---|---|
| Where it runs | Your web page, as WebAssembly | macOS (Apple Silicon), Windows x64, Linux x64 — the engine as native Rust | Your servers — amd64 and arm64 |
| How you get it | npm i @opencalc/sheet |
.dmg · .exe · .deb · .AppImage |
docker run casualoffice/calc |
| The whole engine — .xlsx, formulas, charts, pivots | Yes | Yes | Yes |
| Opening a file from the operating system | Through your own code — you hand it the bytes | Native dialogs, and double‑click on .xlsx .xlsm .ods .csv .tsv |
Through the document host, or your own storage |
| Several people in one sheet at once | No — a single‑user editing surface | Yes, as a participant | Yes — this is the server that orders and relays |
| Fits into | A web component, or the React wrapper | The file types above | Nextcloud, ownCloud, SharePoint, Moodle, Alfresco — one discovery URL |
| Signed by the operating system | Not applicable | Not yet — the bypass is two clicks, and the download page walks through it | Not applicable |
| Worth knowing | Each element carries its own engine, so page a long list in and out rather than mounting fifty. A pivot built here exports as its cells, not as a live Excel pivot. | Pasting from another application arrives as values without formatting. The .deb needs Ubuntu 23.04 or newer; the AppImage runs on 22.04. |
No Helm chart yet, and Redis is a single node. Prometheus metrics are on /metrics. |
Not yet, in any of the three. PDF export is written and tested in the engine but is not reachable from a published product — it ships when the Rust SDK does. .xls (the pre‑2007 binary format) is refused rather than half‑read; .xlsm opens, and its macros survive a round‑trip byte for byte, but nothing here executes them. Custom worksheet functions written in JavaScript are deliberately absent — they would make a recalculation unrepeatable. Menus and toolbar tooltips are localized; panels and dialogs are still English.
Under the hood
Parsing sits below evaluation, so import and editing use the formula AST without pulling in the calc engine at all.
A Tauri host where the whole engine — including calc — runs as native Rust at full speed.
The same core compiled to wasm32, driving a canvas grid entirely client-side. The editor above is the proof.
No UI at all — batch convert, recalculate, and rasterize a viewport to a deterministic PNG on a server.
// One session composes every layer: open, edit, recalc, render, save.
let mut session = WorkbookSession::open(bytes)?;
session.edit(Operation::SetCell { sheet: 0, at: a1("B2"), cell })?;
let png = session.render_png(0, &viewport)?; // virtualized grid → PNG
let xlsx = session.save()?; // deterministic bytes
Embed it
One custom element, one line of markup, and your users get the editor
above — .xlsx in and out, 347 functions, pivot tables and
charts, all client-side. It works in React, Next.js, Vue, Svelte or
plain HTML.
The element and, if you use React, its wrapper. No peer dependency on a UI framework.
# the editor as a custom element
npm install @opencalc/sheet
The WebAssembly binary is copied into your build and served from your origin — never from a CDN we run.
# package.json → postinstall
opencalc-assets ./public/opencalc
Give it a height and it boots. Open a workbook, get bytes back, listen for changes.
import "@opencalc/sheet"; <opencalc-sheet style="height:600px">
const sheet = document.querySelector("opencalc-sheet");
sheet.theme({ light: { accentColor: "#7c3aed" } }); // CSS tokens, scoped by a shadow root
sheet.chrome({ statusbar: false }); // hide whole regions…
sheet.commands({ hidden: ["file.open"] }); // …or single controls, by id
await sheet.configure({ access: "view" }); // "edit" | "view" | "preview"
await sheet.open(bytes, "budget.xlsx");
sheet.on("cellsChanged", (e) => {
if (e.source !== "api") persist(await sheet.save()); // deterministic .xlsx
});
Try it
Co-editing is not a feature you should have to take on trust. Two containers, one command, and you are sharing a link with a colleague and typing into the same sheet.
The editor, a collaboration server and a reference host. Nothing else to install.
# change OPENCALC_SHARED_SECRET in .env
cp .env.example .env
docker compose up
Open localhost:8080, make a spreadsheet, press Share, send the link. You will both see each other's cursors and each other's edits.
# and a cluster, when one node is not enough
docker compose -f docker-compose.cluster.yml up
Download gives you an .xlsx with what you both typed — the server hands the finished bytes back to the host rather than keeping them.
The images are published, so that pulls rather than builds:
docker pull casualoffice/calc # the collaboration server docker pull casualoffice/calc-host # the editor, and your documents docker pull casualoffice/wopi # the WOPI adapter, if you need one
Each is linux/amd64 and linux/arm64, and each ships an
SBOM and a build-provenance attestation naming the repository, workflow and
commit it came from — so what you pull can be traced back to what is here.
Add --build to any compose command to build from your own checkout
instead.
The two containers are the integration boundary, not a demo artefact. The collaboration server is what OpenCalc supplies — ordering, presence, the socket. The host is what your product supplies — documents, identity, tokens, saving. It holds no per-document state and cannot mint tokens, so it is told, per join, by a party that already knows. That is four endpoints and a signature, and the host you just ran is the worked example of the one you would write.
The self-hosting guide covers reverse proxies, TLS, every setting, monitoring, backup and what the demo defaults are not safe for.
The trade-off everyone else makes
Most options force a compromise: a suite you can't embed, a converter that quietly drops what it doesn't understand, or a web grid that treats the DOM as the source of truth and calculates on a server.
Categories, not products — Excel and LibreOffice Calc remain our semantics and fidelity oracles, and we study IronCalc, calamine and Univer openly.
Honest status
Delivered in capability-gated phases. The workbook has to be read, modeled, preserved, written back, laid out and rendered before a single cell is evaluated.
No sign-up, no upload, no build step. Bring an .xlsx and watch it round-trip — then put the same thing in your own product.
Apache-2.0, CI-gated, fuzzed and benchmarked. Fidelity bugs with a fixture attached are the most valuable thing you can send.