Code Scrambler
BlogBlogHire meHire meCVCV
Sign InGet a Quote
NavigationGet a Quote

Apps

TikZpenCashCraftSubSync
BlogHire meCV

Account

Sign In
Code Scrambler Productions

I'm Zsombor, an independent software developer and founder of Code Scrambler Productions. Games, full-stack web apps, mobile apps and backend systems, built end to end.

Available for remote work

Remote · Flexible · Asynchronous · Project-based. Looking for a developer for a role, a project or a contract? Let's talk.

Hire MeRemote from Hungary, roughly 5–20 hrs/week, async-friendly.

Built with Next.js, Firebase, and a bias toward useful software.

Explore

  • Home
  • Work
  • About
  • Apps
  • Librarian
  • Blog

Tools

  • CashCraft
  • CashCraft Rates
  • SubSync
  • TikZPen
  • Bug Reports

Work With Me

  • CV
  • Contact
  • Hire Me
  • For Business

Legal

  • Privacy Policy
  • Delete CashCraft Data
  • Terms of Service
© 2026 Code Scrambler Productions·Budapest, Hungary

tikzpen v260911aB · cashcraft v260422a

Blog

Rebuilding a visual TikZ editor around a TikZ-shaped document model

Why I rebuilt TikZpen from scratch: a data model shaped like TikZ, one renderer shared by the canvas and the export, and a compile-based check that proves the exported code matches the drawing.

October 12, 20262 min read1 viewZsombor Rózsa
Contents
  • The old model fought the output
  • Make the model TikZ-shaped
  • One renderer, no drift
  • Proving it with a fidelity check
  • Product details that mattered
  • Stack
  • Takeaway

TikZpen is a free browser editor where you draw shapes, text and Bézier paths on a canvas and get ready-to-paste LaTeX TikZ code in real time. In September 2026 I rebuilt it from scratch. This is why, and what I learned about building a tool where correctness is the whole point.

TikZpen with a few shapes drawn and the generated TikZ code in the side panel.

The old model fought the output#

The first version worked, but its internal model was a generic canvas scene graph, and TikZ is not a canvas. Every feature meant translating between two ideas of what a drawing is, and the translation was where the bugs lived: a shape could look right on screen and export subtly different TikZ.

TikZ source
% libraries: arrows.meta, positioning, decorations.pathmorphing
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=12mm,
  box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.5cm, align=center}]
  \node[box, fill=red!10] (canvas) {Canvas\\scene graph};
  \node[box, right=36mm of canvas, fill=red!10] (tikz) {TikZ};
  \draw[->, thick, red!60!black, decorate, decoration={snake, amplitude=0.6mm, segment length=4mm}] (canvas) -- node[above=2mm, red!60!black] {lossy translation} (tikz);
  \node[below=8mm of canvas, align=center, font=\sffamily\footnotesize, text=red!60!black] {two models of "a drawing"};
\end{tikzpicture}

Make the model TikZ-shaped#

In the rebuild the native document format is TikZ-shaped: layers, nodes, paths and styles map straight onto what TikZ can say. Export stops being a conversion and becomes a per-node emit. The rule I wrote down for myself: if a field doesn't correspond to something TikZ can express, it doesn't belong in the model.

TikZ source
% libraries: arrows.meta, positioning
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=10mm,
  box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.6cm, align=center},
  doc/.style={box, fill=orange!25, minimum width=3.4cm, minimum height=12mm}]
  \node[doc] (doc) {Document\\layers, nodes, paths, styles};
  \node[box, above right=2mm and 20mm of doc] (canvas) {Live canvas};
  \node[box, right=20mm of doc] (svg) {SVG export};
  \node[box, below right=2mm and 20mm of doc] (tikz) {TikZ export};
  \node[box, below=10mm of doc] (imp) {Import\\TikZ and SVG};
  \draw[->] (doc) -- node[above, sloped, font=\sffamily\scriptsize] {one renderer} (canvas);
  \draw[->] (doc) -- node[above, font=\sffamily\scriptsize] {same renderer} (svg);
  \draw[->] (doc) -- node[below, sloped, font=\sffamily\scriptsize] {per-node emit} (tikz);
  \draw[->] (imp) -- (doc);
\end{tikzpicture}

One renderer, no drift#

The live canvas and the SVG export use the same renderer: a single function turns a document into a React SVG tree. If the viewport and the export can't disagree about what a shape looks like, a whole class of "looks different once exported" bugs simply can't exist.

Proving it with a fidelity check#

"The export looks right" isn't a test. So the exported TikZ is sent to a compile service and the compiled result is compared with the editor's own rendering of the document, a check that the TikZ really reproduces the drawing one to one. I run the same harness in development, and it exposed real renderer bugs that looking at the screen had missed.

TikZ source
% libraries: arrows.meta, positioning
\begin{tikzpicture}[font=\sffamily\small, >={Stealth}, node distance=9mm,
  box/.style={draw, rounded corners, minimum height=9mm, minimum width=2.4cm, align=center}]
  \node[box] (doc) {Document};
  \node[box, right=14mm of doc] (tikz) {Exported\\TikZ};
  \node[box, right=14mm of tikz] (comp) {LaTeX compile\\service};
  \node[box, right=14mm of comp] (img) {Compiled\\image};
  \node[box, below=12mm of doc] (ren) {Editor's own\\rendering};
  \node[box, fill=orange!25, below=12mm of img] (cmp) {Compare};
  \draw[->] (doc) -- (tikz); \draw[->] (tikz) -- (comp); \draw[->] (comp) -- (img);
  \draw[->] (doc) -- (ren);
  \draw[->] (img) -- (cmp);
  \draw[->] (ren) -- (cmp);
  \node[right=3mm of cmp, font=\sffamily\footnotesize, align=left] {match = export is faithful\\mismatch = a bug to fix};
\end{tikzpicture}

Around that check:

  • Golden fixtures. Export output is pinned with .tikz and .svg fixtures, and the pure logic (model, geometry, commands, export, import) is covered by Jest tests.
  • Undo and redo as commands. Every edit is an invertible command on a bounded history, which keeps undo reliable across multi-select, groups and path editing.
  • Import as well as export. TikZ and SVG importers let people bring existing work in, with warnings for anything that can't be represented rather than silently dropping it.

Product details that mattered#

Autosave to the browser for everyone, optional cloud documents with version history for signed-in users, snapping, node editing, layers and groups, and a mobile layout that stacks the panels. The old editor is still reachable (noindexed) for old bookmarks, but all new work goes into the rebuild.

Stack#

TypeScript, React, Next.js, a Zustand store, IndexedDB and Firestore for persistence, a containerised LaTeX compile service, Jest.

Takeaway#

When correctness is the product, shape the data model after the output format, share one renderer between everything that draws, and let an automated check tell you when you're wrong. You can try the editor at TikZpen, and if you're new to TikZ, this tutorial covers the language itself.

typescriptreactlatexarchitecture
MarkdownRSSXLinkedInBluesky
…
Zsombor Rózsa
Zsombor Rózsa

I’m Zsombor, an independent software developer and founder of Code Scrambler Productions.

← PreviousMaking a finance PWA feel instant: snapshot-first startup in CashCraftNext →TikZ Tutorial: Draw Shapes, Arrows and Curved Paths in LaTeX

Related

TikZ Tutorial: Draw Shapes, Arrows and Curved Paths in LaTeX

A practical TikZ tutorial for beginners: set up a standalone figure, then draw lines, rectangles, circles, arrows, nodes and Bézier curves with copy-paste code and the result next to it.

October 12, 2026 · 2 min read
How to Export TikZ to PNG, SVG and PDF (Command Line and Online)

Turn a TikZ figure into a cropped PDF, a PNG for slides or the web, or a clean SVG, using standalone, pdflatex, pdftocairo and dvisvgm.

October 12, 2026 · 1 min read

Comments

Want to join the conversation?

Log in to comment

Contents

  • The old model fought the output
  • Make the model TikZ-shaped
  • One renderer, no drift
  • Proving it with a fidelity check
  • Product details that mattered
  • Stack
  • Takeaway