LibreCAD in the Browser

devttyeu1 pts0 comments

LibreCAD in your browser — magik.net

magik.net

LibreCAD in your browser

Posted June 29, 2026 · updated July 1, 2026

Every now and then I need to sketch a quick 2D drawing like a floor plan.<br>I'm not a CAD user, and I don't want to install one just to draw five<br>lines and a circle. The free options are all desktop apps: you download<br>something, you install it, you launch it, you work locally. That's fine<br>when you're at your own desk, but it's 2026 and it felt a little silly that<br>I couldn't just open a tab and draw, especially without logging in.

I had never actually used LibreCAD<br>before nor heard of it and I'm not its target user. I just wanted something<br>for quick sketches. So instead of trying yet another desktop install, I<br>thought: find an open-source app, and port to WebAssembly by just prompting GLM-5.2<br>in OpenCode to do so.

It is worth pointing out that the whole process was very hands off and "easy",<br>but this is only thanks to huge efforts of the Qt team which seems to be investing<br>into Wasm support quite seriously, and the entire Wasm ecosystem which seems at<br>this point to be really quite mature.

It turned out to be at the edge of what this model can do, but with some<br>guidance it worked out. Native vision support with some computer-use abilities would<br>hugely help the model debug issues autonomously, GLM-5.2 lacks that capability.<br>The result is below: the whole application (not a viewer, not a subset) compiled to<br>WebAssembly and running right here<br>on this site. Click and it loads (source:<br>github.com/magik6k/LibreCAD-Web):

Launch LibreCAD &rarr;

First load is ~18 MB compressed (Brotli). After that your browser<br>caches it. Needs a recent Chromium-based browser (Chrome or Edge 137+)<br>— the port relies on WebAssembly JSPI, which Firefox and Safari don't<br>ship yet. More on why below.

Content below is entirely written by the LLM, but it does appear roughly technically accurate, just like the app appears to roughly run in browsers but probably contains horrible bugs upon closer inspection. This is purely a FAFO style no effort project, what do you expect?

What it is

LibreCAD is a free, GPL-licensed 2D CAD application. It reads and writes<br>DXF and DWG files, supports layers, blocks, dimensions, hatching, and most<br>of the things you'd expect from a 2D CAD tool. It's built in C++ on top of<br>Qt and has been around since the QCad days.

This port compiles the exact same C++ source code to WebAssembly via<br>Emscripten and Qt's official<br>WebAssembly platform support. There is no JavaScript reimplementation, no<br>web-native fork, no server-side rendering—the real desktop application<br>is running in your browser tab.

How it was done

The first 90% was mechanical: get the toolchain up, compile, boot the GUI,<br>wire up files. The last 10% — making modal dialogs actually work<br>— is where the interesting problems were, and it's what forced a<br>rebuild of the whole toolchain. Here's the honest version.

Toolchain and compilation

A Docker image with Ubuntu 24.04, Emscripten, and Qt. The full LibreCAD<br>source compiles and links to a .wasm binary using Qt's own<br>qt.toolchain.cmake (the raw Emscripten toolchain file makes<br>find_package(Qt6) fail). Desktop-only startup paths — CLI<br>argument parsing, splash screen, first-run dialog, version-check networking<br>— are guarded with #ifndef Q_OS_WASM so the desktop build<br>is untouched.

Booting the GUI

Qt for WebAssembly renders through WebGL and delivers browser events through<br>its platform plugin. The main window boots, toolbars and docks appear, the<br>canvas takes mouse and keyboard input. So far, so good.

The hard part: nested dialogs and exec()

LibreCAD is a proper desktop app, and desktop apps re-enter the event loop<br>constantly: QDialog::exec() blocks until you close the dialog, a<br>combo-box drop-down spins its own loop, a colour picker opened from<br>a preferences dialog nests another loop on top. On the web you<br>cannot block the main thread — there is no way to<br>"wait here" without freezing the page. So exec() simply doesn't<br>return.

Emscripten's answer is Asyncify:<br>it rewrites the binary so a blocking call can unwind to the browser and<br>resume later. Qt supports it, and it's what most Qt-WASM apps use. It works<br>— for one level. Asyncify can only suspend a single call depth<br>at a time. So a dialog opens fine, but the moment you click a combo-box<br>inside that dialog, or open the colour picker from Application Preferences,<br>the second suspend has nowhere to go and the whole app wedges. For a CAD<br>program whose preferences are wall-to-wall drop-downs and colour buttons,<br>that's not a rough edge, it's unusable.

The fix is JSPI (WebAssembly<br>JavaScript Promise Integration): a native browser suspend mechanism that,<br>unlike Asyncify, nests arbitrarily. Qt 6.9 can target it<br>(-device-option QT_EMSCRIPTEN_ASYNCIFY=2), but it requires<br>native WebAssembly exceptions (-fwasm-exceptions), and the<br>prebuilt Qt packages ship neither. So the port now builds Qt 6.9<br>from source for WebAssembly with JSPI + Wasm...

librecad browser webassembly desktop source wasm

Related Articles