Turn a D&D battle-map image into a printable PDF or ODT, split across pages so every grid square prints at true miniature size (2.54 cm / 1″ by default).
Arranger is a static web app — there is no server and no upload. Your maps are read, tiled and exported entirely inside your browser, so nothing ever leaves your machine.
cd frontend
npm install
npm run dev # http://localhost:5173To produce the deployable site:
npm run build # writes frontend/dist/
npm run preview # serve that build locallyThe build uses relative asset paths, so dist/ works at any URL — GitHub
Pages, a subdirectory, Netlify, or a plain python -m http.server.
.github/workflows/pages.yml builds the site and
deploys it to GitHub Pages on every push to main (and on manual dispatch).
One-time setup: in the repo's Settings → Pages, set Source to
GitHub Actions.
- Upload one or more map images (drag & drop). Files named
<name>_<width>_<height>.pngget their grid preset automatically (width/height in cells). - Calibrate the grid with the select a square tool: drag a box around exactly one grid square. A loupe magnifies the pixels under the cursor (adjustable — see Settings), the eight handles fine-tune the box, and the floating bar takes exact pixel width and height (□ makes it a perfect square). Press Use and the app derives the number of columns and rows; Delete, the ✕ on the bar, or the right-click menu removes the selection again. Or just type cols × rows manually — the 🔗 button between the two fields keeps them in step with the map's aspect ratio so cells stay square; click it to unlink and set the two independently.
- Grid: toggles an overlay — blue lines are the cells, orange dashed lines are where page cuts will fall. Changing the grid size switches the overlay on by itself and blinks the cell lines a few times, so you can see straight away whether they line up with the squares on the map; Blink in the toolbar replays that at any time.
- Export…: previews every page. Click pages to exclude them, pick PDF or ODT, download.
The dock on the canvas holds the three tools and two toggles, each with a shortcut: pan (V), select a square (S), crop (C), show grid (G) and focus square (F), which centres the view on the selected square and zooms in far enough to work on it. Esc returns to the pan tool.
Scroll to zoom around the pointer and drag to pan. Holding Space (or Alt) pans from any tool and snaps back when you let go, so you can nudge the view mid-calibration without switching tools. The zoom control sits in the corner of the canvas (− / + / Fit); Fit frames the printable area, so once the map is cropped it zooms to the crop rather than the whole image.
⌘Z / Ctrl+Z undoes, ⇧⌘Z or Ctrl+Y redoes, and the arrows sit at the left of the toolbar. Grid size, crop, selection and the tiled region are all tracked; a whole gizmo drag collapses into a single step. History is per map and resets when you switch maps.
Crop trims the printable area: drag out a rectangle, then move it by dragging inside it or resize it with the handles. Dragging outside the crop pans the view instead of redrawing it, so the area you set can't be lost by a stray click. Everything dimmed is left out of the export. With snapping off, the loupe follows the handle you are dragging so edges can be placed to the pixel.
Cropping preserves the calibration rather than undoing it. Edges snap to whole cells (the Snap to grid checkbox, on by default) and the cell counts follow the new size, so a square keeps exactly the same number of pixels — trimming two columns off a 30 × 20 map leaves 28 × 20 at the same scale. Reset crop restores the full image and scales the counts back up.
To print just a corner of a big map, select a square and right-click it: the menu tiles only a 3×3, 5×5, 7×7 or custom block of squares centred on it. The chosen block is framed and labelled on the canvas, and the page count updates immediately. Clear it from the region badge to go back to the whole map.
Maps live in the page for as long as the tab is open — the browser warns you before you leave, since reloading clears the library.
- Paper size / margin / cell size — page geometry; cell size defaults to 2.54 cm (a 1″ square).
- Never cut squares vertically / horizontally — page cut lines snap to cell boundaries on that axis, so no square is ever split across two sheets (enable both to keep every square intact).
- Drop small chunks — a slider (up to the printable size of one page). Pages narrower or shorter than the threshold — the slivers left at the edges of a map — are dimmed on the canvas so you can see what is being discarded, and excluded from the export. They still show in the export preview marked SMALL and can be re-included by clicking.
Under Advanced settings:
- Calibration loupe — magnification (1.5×–16×) and diameter of the magnifier shown while selecting a square. Lower magnification shows more of the map around the cursor; higher is for pinning an edge to the exact pixel.
frontend/src/
App.jsx state: map library, grid, crop, region, settings
hooks/useHistory.js undo/redo with drag coalescing
components/
MapViewer.jsx canvas: pan/zoom, tool dock, loupe, gizmos, overlays
Sidebar.jsx upload + map library
SettingsPanel.jsx page geometry, splitting options, loupe controls
ExportModal.jsx per-page preview and export
geometry.js rect maths shared by the crop and selection gizmos
icons.jsx line icons for the tool dock and undo/redo
engine/
tiler.js page-splitting math + canvas tile cropping
api.js in-browser image library, previews, PDF export
odt.js ODT (zip of XML + PNGs) writer
Built with React + Vite; PDF export uses jsPDF and ODT export uses JSZip.