NeXopp opens, edits, and saves Xournal++ .xopp
files on Android. Draw and handwrite with a pen/stylus on a tablet or phone, then save back to
the same .xopp format so the file round-trips cleanly to and from desktop Xournal++ on
Linux. The guiding principle is format fidelity and round-trip safety: a file edited on
Android reopens correctly on the desktop, and vice versa.
- What the project is and how to work in it:
CLAUDE.md. - How it works internally (the
.xoppschema, data path, model):docs/architecture.md. - Build/emulator tooling:
docs/tools.md. - What's next (active tasks):
TODO.toml; what's already shipped:FINISHED.toml. These are TOML task files driven by thetodoskill — runscripts/todo.sh listorscripts/todo.sh statsto read them.
Status: the
.xoppread/write core and its tests are in place, and the Android editor is functional — pen/highlighter/eraser drawing with pressure, colour and width pickers, undo/redo, zoom, pan, a page navigator (add/remove/jump, 1-4 pages per row), on-device authoring of text/image/LaTeX elements, LaTeX math rendering, multi-page documents with layers and backgrounds, and PDF import and export. The controls live in a vertical rail down the left edge. Runscripts/todo.sh listfor what's next.
Everything builds through the shared Android toolchain in /data/android (a baked
android-builder:local container), so the only host requirement is Docker. You do not need
a local JDK, Android SDK, or Gradle. To run the app on a virtual device you additionally need a
KVM-capable host for that directory's headless emulator (see docs/tools.md).
The one command you need:
scripts/build.shThis runs the full check loop through the shared toolchain container — unit tests + a debug
APK. Task variants, output paths, caching behaviour and the rest of the pipeline live in
docs/tools.md, the authoritative home for the build.
The unit tests run on the JVM with no device attached, covering the .xopp round-trip, the pure
render/ geometry and the audio sidecar mapping. What each one asserts — and the rule for the
optional real-file udiff.xopp test — is documented in
docs/architecture.md.
Installing and launching the built APK — the headless emulator (emulator.sh install /
launch), when a raw adb install to a physical device is appropriate, and how builds reach
the owner's tablets and phones — is documented in docs/tools.md. That is the
authoritative flow; don't invent another one.
-
Open — the top-bar menu (the ☰ button, top right) has Open; it launches the system file picker; choose a file. It's read in place via the Storage Access Framework. Open accepts several kinds of file and works out which is which from the file's contents, not its name: a gzip-compressed
.xopp(the usual desktop format), a single-file zipped.xopppackage (its bundled PDF travels inside), an uncompressed Xournal++ XML file, a plain PDF — picking a PDF opens it as a fresh annotatable document exactly as Import PDF below does — and a plain-text file (see Open a text file below), and an image (see Open an image below). A file that is none of these is refused with an "Open failed" notice. Whichever.xoppcontainer it came from is remembered, so a later Save writes it back in the same one. Files on remote shares work too — anything the system picker lists, including SSHFS, FTP, WebDAV and cloud providers mounted as storage. Those reads can be slow, so the document is fetched in the background behind an "Opening…" note and only appears once it has fully landed; a link that drops mid-transfer leaves the app as it was and reports the failure. Every page is shown, one above the next, each drawn with its own background ruling (plain, lined, ruled, graph, or dotted) and all of its layers — including strokes, text boxes, images, and LaTeX images (rendered as real math — fractions, super/subscripts, roots, and Greek/operator symbols; malformed formulae fall back to their source text). If the.xoppwas made by annotating a PDF, its PDF background is reloaded automatically — the reference the file stores is resolved and the PDF pages render underneath your annotations again, so a saved project reopens intact. Every way Xournal++ can name that PDF is understood: a plain file path, a path relative to the.xoppitself (bg.pdforscans/bg.pdf— how a portable, desktop-authored document usually stores it), and an attachedyourfile.xopp.bg.pdfsibling. If the referenced PDF can't be found (e.g. a desktop path that doesn't exist on the device, or a folder NeXopp wasn't given access to), those pages open blank and a notice is shown — the reference itself is kept, so saving the file doesn't throw the background away. -
Open a PDF from another app — NeXopp registers as a PDF handler, so it shows up in the Open with / Share sheet of file managers, browsers, and mail clients. Tap a PDF there, pick NeXopp, and it opens straight into a new tab as a fresh annotatable document over the PDF's pages — the same result as opening that PDF through the menu's Open. This works whether the app was closed or already running; a PDF handed over while it's running is added as another tab, leaving the documents you already had open untouched. The tab is named after the PDF. The first Save asks where to put the
.xopp. A PDF handed over this way has no.xoppfile behind it, and NeXopp never writes document bytes over your original PDF — so the first Save opens the destination picker, pre-filled with the PDF's own name and a.xoppextension (bhm_prior.pdf→bhm_prior.xopp), which normally puts the annotations beside the original. After that first save the tab belongs to the.xoppit was written to, and later Saves write straight back to it. The PDF stays on as the page background and is referenced by the saved file, so the document reopens over those same pages in desktop Xournal++ (Save As… → zipped package embeds the PDF inside the file instead, if you'd rather carry it around as one file). -
Tabs — several documents open at once — every document you open lives in its own tab, and the tab strip under the top bar is always shown — even with a single document open — so the same tab controls are always in the same place. Tap a tab to switch to that document; every tab carries its own ✕, so you can close any document without switching to it first; tap + at the end of the strip — or New document in the ☰ menu — to start a fresh blank document in a new tab. The tabs, the ✕ and the + are all finger-sized targets, so switching or closing a document works with a fingertip and doesn't need a stylus. The strip is kept deliberately compact (a slim 42dp band) so it gives up as little of the canvas as it can. Open always opens into a new tab rather than replacing what you're working on. Each tab carries its own save format, its own background PDF and the page you were on, so switching back lands you where you left off. Closing the last tab leaves you on a fresh blank document. One thing to note: undo history doesn't follow a tab switch — the incoming document starts with a clean undo history, though all of its content and unsaved edits are intact. Reopening the app brings the whole strip straight back, however many tabs it holds: each document is read in the background at the moment you switch to it, so the app starts and stays responsive instead of stalling on a big session.
-
Split view — two documents side by side — Split view in the ☰ menu divides the drawing area into a left and a right pane, each showing its own document. Drag the bar down the middle to rebalance the two halves (it's a finger-wide grab strip, and neither pane can be squeezed below about a sixth of the width). Each pane has its own tab strip, so the two halves hold entirely separate sets of open documents, and each keeps its own scroll position, zoom, current page, layers and undo history. Handy for copying between two notebooks, or for writing notes beside a PDF you're reading.
The toolbar and the ☰ menu always drive the pane you last touched — tap or draw in a pane to give it focus, and Save, Import PDF, the pen settings and undo/redo then apply to that document. Choosing Close split view hands the whole area back to the left pane; the right pane's tabs are kept, so turning split view on again brings the same documents back.
-
Tab overview — see every open document at a glance — the grid button in the top bar (between redo and the ☰ menu) opens a grid of every tab in the current pane, each shown as a small picture of the page that tab was left on, with its name underneath and the current tab outlined. Tap a picture to switch straight to that document. It is the quickest way to find the right document when several tabs are named alike (or are all "Untitled"). Pictures appear as they are drawn — a document that hasn't been reopened since the app started has to be read from the session cache first, so its card is blank for a moment. PDF and image page backgrounds aren't drawn in the pictures; your handwriting and drawings are.
-
The tab strip scrolls sideways — open more documents than fit across the screen and the strip scrolls rather than pushing tabs off the edge, so every tab (and the trailing +) stays reachable. Drag anywhere on the strip except the current tab to scroll it, and the selected tab is always scrolled back into view when you switch documents.
-
Drag the current tab sideways to reorder it — press the selected tab and slide it left or right along its strip to move it past its neighbours; the order sticks and is restored with the rest of the session. Only the selected tab reorders — dragging any other tab scrolls the strip instead, so tap a tab first if you want to move it. A drag only takes over once your finger has actually moved, so a plain tap still switches document and a stationary hold still opens the long-press menu below. Dragging reorders within a strip; use that menu to send a tab to the other pane.
-
Long-press a tab to send it to the other view — holding a tab pops up a small menu with two entries. Move to other view takes that document out of this pane and opens it in the other one; Mirror on other view leaves it where it is and opens a second view of the same document in the other pane. Either one opens split view automatically if it was closed.
-
A mirrored document is live in both panes — the two mirrored tabs are two windows onto one document, not two copies: a stroke, an erase or a page change made on one side appears on the other immediately, and both save back to the same file. The views stay independent in every other respect — each keeps its own scroll position, zoom and current page, so you can work at the top of a page on the left while watching the bottom of it on the right, or keep a diagram in view while writing about it further down. Undo lives in the pane you are editing in: the other view drops its undo history when it takes an edit, so an undo can't quietly discard the work the other side just did.
Because tabs are named after files, and different files are often named alike, mirrored tabs are marked with a small coloured dot. Tabs sharing a dot colour are views of the same document — two "notes.xopp" tabs with no dots are two different files that merely share a name.
-
Tabs are restored when you reopen the app — the set of open tabs is cached on the device (including edits you hadn't saved yet), so closing the app and starting it again brings back exactly the tabs you had, with the same one showing. This is a convenience cache, not a substitute for saving: your
.xoppfile on disk is still only written when you Save, and that file is the only thing desktop Xournal++ ever sees. -
Open a text file — picking a plain-text file (a
.txt, a log, a source file, notes) opens it as an annotatable document: the text is typeset onto A4 pages in a monospace face, wrapped at the margin and split across as many pages as it needs, and those pages become the document you draw on. The text is real, selectable text, not a picture of text, so the Select text (PDF) tool works on it and you can copy passages straight out. Annotate it exactly like any other document. The first Save asks where to put the.xopp, since a text file has no.xoppof its own, and the typeset pages are saved inside the.xopp(the zipped single-file package) — so the file reopens with its text intact on this device and in desktop Xournal++, with nothing else to keep alongside it. Reopening the same text file again reuses the pages already typeset. A file named.mdor.markdownis recognised as markdown and typeset as markdown, cached separately from the same text opened under a.txtname. Headings are set large and bold,**bold**and*italic*are drawn in real bold and italic type,`code`and fenced code blocks in monospace, lists get hanging bullets or numbers, block quotes are indented, and---becomes a drawn horizontal rule — with the markup characters themselves gone from the page. The result is still selectable text, so Select text (PDF) copies the words without the syntax. Very large files are refused rather than opened: typesetting a huge log takes minutes and fills the cache, so a file over the text import limit (64 MB by default) comes back as "Text file is N MB, over the 64 MB import limit" instead of stalling the app. Raise or lower that limit under Settings → Storage. -
Open an image — picking a PNG, JPEG or WebP opens it as a one-page document with the picture as the page background, at the picture's own proportions, with an empty layer on top to draw on. Annotate it like any other document. The image itself is linked, not copied — the saved
.xopppoints at the picture where it already lives, so keep it in place — and, as with a PDF, the first Save asks where to put the.xopp, since an image has no.xoppof its own. A truncated or corrupt image is refused with a "Couldn't read that image" notice. NeXopp also registers as an image handler, so — exactly as with a PDF — it appears in the Open with / Share sheet of gallery apps, file managers, browsers, and mail clients: tap a picture there, pick NeXopp, and it opens in a new tab without disturbing the tabs you already had. Reopening such a.xoppfinds its picture again the same ways desktop Xournal++ does: bycontent://URI, by absolute path, by a path relative to the.xopp's own folder, or from the sibling file an attached background names. A picture that can't be found leaves those pages blank with a "Background image not found" notice — the reference itself is preserved, so putting the file back makes it render again. Saving through Save As… → zipped.xoppinstead bundles the pictures inside the.xopp, one archive entry per image-backed page, so the file carries its backgrounds to another machine. -
Import PDF — the menu's Import PDF first asks how the PDF should join the document: Replace (the PDF's pages become the document, discarding the pages currently open) or Append (the PDF's pages are added after the pages already open, keeping their annotations — and it's a single undo away). A
.xoppcan reference just one background PDF, so appending onto a document that already has one merges the two into a single joined PDF — the incoming PDF's pages are added to the end of the existing background PDF, that joined file becomes the document's one background source, and the appended pages are renumbered against it. Repeat appends keep composing onto the joined PDF without touching the original files, and the joined PDF is what the saved.xopplinks to (or embeds, for a zipped Save As), so the result reopens in desktop Xournal++ with every appended page's background intact. Appending onto a brand-new, untouched blank page drops that stray blank sheet. Choosing a mode launches the picker filtered to PDFs; the import gives you one page per PDF page, each PDF page rasterised and shown as the page background (à la desktop Xournal++ PDF annotation). Draw on top as usual; the strokes are kept separate from the PDF and thepdfbackgrounds round-trip when you Save the.xopp. The source PDF's reference is recorded in the saved file, so reopening the.xopplater reloads that PDF and shows the same backgrounds again — no need to re-import. NeXopp writes that reference as a relative path whenever it can: if the PDF sits in the same folder you save the.xoppinto, the file records justthepdf.pdfrather than a device-specific location. That's the portable form — copy the two files to a Linux box together and desktop Xournal++ finds the background exactly as it does here. When the PDF lives somewhere else entirely, the full reference is recorded instead, which still works on this device. Nothing to configure; it just picks the more portable of the two. -
Select text (PDF) — for an imported PDF that carries a real text layer (i.e. not a pure scan), the Tool pop-up's Select text (PDF) tool lets you drag across the page to select the underlying text; the selected words highlight, and a Copy button puts them on the system clipboard to paste elsewhere. It reads the PDF's own text — no OCR — so scanned image-only PDFs have nothing to select (OCR for those is planned). The selection is view-only and doesn't change the document.
-
Vertical space — pick Vertical space from the rail to reflow a page: drag down on the page to insert blank vertical space, pushing everything below the grab line down with your finger, or drag up to close a gap and pull that content back. A dashed guide shows the grab line while you drag; only objects whose top edge is below the line move (one the line passes through stays put rather than being torn), and all layers move together. The whole drag is a single undo step, and since it only shifts coordinates it round-trips to desktop Xournal++ unchanged.
-
Draw — the controls live in a vertical rail down the left edge. It starts with eight tool slots, each standing for a group of related tools and showing the one that group is currently set to: Draw (pen · highlighter), Eraser (partial · whole stroke), Line (line · arrow · double arrow · spline), Shape (rectangle · ellipse · coordinate axis), Pan, Select (rectangle · lasso · select text · background copy), Insert (text · LaTeX · image), and Vertical space. Tap a slot to switch to the tool it shows — the active slot is highlighted — or long-press it to pick a different member, which both switches to that tool and re-faces the slot. Those per-slot choices are remembered across app restarts, so the rail comes back the way you left it. The remaining buttons — Colour & size, Style, Presets, Guides, Layers, Zoom, Background, Pages — each open a small pop-up anchored to their own button (opening to the right of the rail). Pick Pen or Highlighter and draw with one finger or the stylus; pen pressure sets stroke width. The Highlighter instead lays down a broad, constant-width translucent band (pressure-independent, ~6× the pen width) that shows the page through it — it saves as a
highlighterstroke and reopens the same way in desktop Xournal++. Choose a colour (swatches) and a base width from the Colour & size pop-up, which holds both settings in one menu — its button face is a dot sized to the current width and filled with the current colour. Its colour half ends with an editable custom slot (marked with a pencil): tap it to draw with its current colour, or long-press it to open a picker — a saturation/value square over a hue slider plus a#RRGGBBhex field — to set any colour. Its size half offers three width slots, each drawn as a filled dot sized to that slot's width (the widest slot fills the row, the rest scale down in proportion) next to its exact point size, so the three read as a tip-size ladder rather than three arbitrary letters: tap a slot to draw with it, or long-press a slot to open a resize dialog (0.5 → 15 pt) that redefines that slot's width — drag the slider for a broad sweep, tap − / + to nudge it 0.1 pt at a time, or type an exact point size into the text field. Below the swatches the Colour pop-up shows a Recent row — the last seven colours you picked, most-recent-first — so a colour mixed in the custom picker stays one tap away after you move on. That same palette — swatches, custom slot and Recent row — is what the text-box dialog and the selection recolour menu offer, so a colour picked anywhere shows up in the recents everywhere. The custom colour, the three widths, the recent row, and the colour/width you were last drawing with are all remembered across restarts, so the app reopens with the pen you left off with. New strokes land on the active layer (see Layers below) of whichever page you draw on. -
Line & shapes — the Tool pop-up also offers Line, Arrow, Double arrow, Rectangle, Ellipse, Coordinate axis, and Spline. Pick one and drag from one corner/endpoint to the other; a live preview follows your finger and the shape commits on release. Shapes are saved as ordinary strokes in the current pen colour and width, so they round-trip to desktop Xournal++ like any other stroke. A double arrow gets a head at each end; a coordinate axis puts its origin where the drag started and runs an arrowed x and y axis out to the drag's width and height. The Spline tool is for a smooth curve through points you place one at a time. Tap to drop a control point; drag away from a tap instead of lifting to pull out a tangent handle that bows the curve through that point (lift where you want the curve to lean). Keep tapping to extend it — the whole curve previews live as you go, with a dot on every control point, a line through each tangent handle showing how hard that point is bowed, and a dashed rubber band from the last point to a hovering stylus, so you can see where the next tap will take it. While a curve is open, a bar shows how many points it has and offers Finish (disabled until there are two, since a single point draws nothing), an undo button that drops just the last point, and a close button that discards the whole curve. Double-tap or Enter also finishes it, Backspace drops the last point, and Escape or Back throws it away; switching to another tool commits whatever you have so far. The result is one ordinary constant-width stroke in the current pen colour and width, so it round-trips to desktop Xournal++ like any other stroke. The rail's Style pop-up sets Line style (Solid, Dashed, Dash-dot, Dotted) and Fill (on/off with an opacity slider). These settings apply to all shapes and strokes drawn with the line/shape tools and save on the
<stroke>element (style/fill), reopening the same way in desktop Xournal++. -
Shape recognition — turn it on from the rail's Shape recognition button (the triangle; it tints while on, and the state is the same persisted setting as Settings → Stylus → Shape recognition, so you can flip it mid-page without leaving the editor) and a freehand stroke is snapped, the moment you lift, to the shape it clearly resembles: a straight line, an arrow (shaft plus a barb folded back over it), a circle/ellipse, a rectangle (squared to its bounding box when you drew it roughly upright), a triangle, or a short polyline. Anything the recogniser doesn't recognise — handwriting above all — is kept exactly as you drew it. The result is one ordinary constant-width stroke, so it round-trips to desktop Xournal++ like any other. The toggle is off by default and only affects the pen, never the highlighter.
-
Snapping — two optional aids under Settings → Editor. Snap to grid pulls the start and end of a shape drag onto the page background's ruling, so lines and boxes line up with the paper: a graph or dotted page snaps both axes to its squares, a lined/ruled page snaps only the vertical position (it rules no vertical lines), and a plain page snaps nothing. Snap rotation makes the selection's rotate handle step in 15° increments — handy for turning something exactly upright or square. Both are off by default, and neither changes what is written to the file: the result is still ordinary stroke geometry.
-
Setsquare & compass — the Guides pop-up on the rail lays a physical-feeling drawing instrument on the page: a Setsquare (a 30/60/90 geometry triangle) or a Compass (a circle of a chosen radius). While one is on the page, anything you draw within about a quarter-inch of its edge is ruled onto that edge — so a freehand pen stroke along the setsquare's side comes out perfectly straight, and a stroke swept around the compass comes out as a clean arc. Draw further away and the pen behaves normally, so you don't have to keep switching the guide off. A finger drags the guide around by its body and re-poses it by the amber handle at its tip: for the setsquare that rotates and lengthens it (with Snap rotation on, in 15° steps), for the compass it opens the radius. You can hold the guide steady with one hand while the pen rules along it, exactly as on paper. Choose Off to take it away. The guide is only an input aid — nothing about it is written to the file, and what you draw is an ordinary stroke that round-trips to desktop Xournal++ like any other. Which guide you had on is remembered across launches.
-
Drawing guides (setsquare & compass) — the Guides rail button lays a virtual instrument on the page: a Setsquare (a 30/60/90 geometry triangle) or a Compass (a circle). Anything you draw within about a quarter-inch of the instrument's edge is ruled onto it, so a freehand stroke along the setsquare comes out perfectly straight and a stroke swept around the compass comes out as a clean arc of that radius; move the pen away from the edge and it draws freehand again. Slide the guide with a finger placed on its body — inside the setsquare's shaded triangle, or on the compass's centre dot — and drag its amber tip handle to re-pose it: that rotates and resizes the setsquare (with Snap rotation on it lands on 15° steps) and opens or closes the compass. The edges themselves are left free to draw against, so ruling along the outside of the setsquare never drags it out from under your pen, and you can go on holding it steady while you draw. Choose Off to take it away. The guide is purely an input aid: it is never written to the
.xoppfile, so what you draw with it is ordinary stroke geometry. Which guide is out is remembered across launches. -
Presets — the Presets pop-up (the bookmark slot on the rail) saves the whole tool configuration under a name: the tool itself plus its colour, width, line style and fill. Type a name in Save current tool as… and tap + (leave the name blank for "Preset 1", "Preset 2", …); saving under a name you already used overwrites that preset. Each row is numbered with its slot number (1, 2, 3… — the number a Preset slot palette action fires) and shows a swatch in its colour, sized to its width. Tap a preset to make it the live tool — one tap restores every knob at once, and long-press a preset to overwrite it with the live tool — the slot keeps its name, position and id, so a radial-palette slot bound to it keeps working. The ▲/▼ arrows reorder the list and the bin deletes a preset. Presets are remembered across launches, and are an app-side convenience only: nothing about them is written into the
.xoppfile. -
Erase — tap the rail's Eraser slot and drag over strokes. Long-press the slot to pick which eraser it stands for: Eraser (partial) rubs out just the part of a stroke the eraser passes over, splitting it into the surviving pieces; Eraser (whole stroke) removes any stroke the eraser touches entirely. Like every tool slot, the choice is remembered across restarts. The eraser has no size of its own — its tip follows the width slots in the Colour & size pop-up: the tip's radius is the selected pen's full width, so the rubber is twice as wide as the ink it removes — wide enough to bite, narrow enough to erase precisely — and the three slots still give three clearly different tips. It is measured in document points, so it rubs out the same amount of ink whatever the zoom. A thin black circle shows exactly where the tip's edge falls — it follows a hovering stylus, and follows the contact point while you rub (finger touches included). When you erase by holding the stylus barrel button, the circle appears the moment the button goes down, so you can see the tip's reach before touching the page. It is on-screen chrome only: it is never part of the page and never written to the
.xoppfile. The eraser only affects the selected layer — ink on other layers is left alone — and hidden layers are never erased. If your stylus has an eraser tip (the flip-over end), using it erases no matter which tool is selected; so does holding the stylus barrel button (configurable — see Settings) — and the tip circle appears the moment the button goes down while the pen hovers, so you can see what you are about to rub out before touching the glass. Double-clicking the barrel button with the pen lifted off the glass runs its own action — undo by default (see Settings). -
Layers — the Layers pop-up manages the visible page's layers (top of the list = top of the page). Each row can make the layer active (tap its name — new ink lands there, marked with a filled dot), show/hide it in the editor (the eye toggle — hiding is view-only and never changes the file), reorder it up/down (z-order), merge it down into the layer below (the merge button — the two layers' contents combine in z-order, the lower layer keeps its name, and the emptied upper layer goes away; disabled on the bottom layer), rename it, or delete it (a page always keeps at least one layer). Add layer puts a fresh empty layer on top. With something selected, each row also shows a move-selection-here button. Layer names round-trip via the
<layer name>attribute; every structural change is undoable. -
Stylus — the app is stylus-first. Rest your palm on the screen while you write: once the pen is down, finger/palm touches are ignored for drawing (a second finger still pans). A hovering stylus shows a preview ring where the tip will land. Pen pressure sets stroke width, with a configurable feel, tapering more deeply at a light touch to match desktop Xournal++. Handwriting is smoothed as you write — digitiser wobble in both position and pressure is filtered out, and redundant points are dropped when the pen lifts, so strokes look clean and files stay small. All of this is tuned in Settings below.
-
Select — the rail's Select slot selects objects the way desktop Xournal++ does. The marquee shape is the tool itself: long-press the slot to pick Select rectangle (drag a box; every object fully inside is selected), Select lasso (trace a free-form loop; everything wholly inside is selected), Select text (PDF), or Select background (flatten). Both shapes allow a few points of slack at the edge, so tracing tightly around a hairline stroke still catches it. A marquee only picks objects on the active layer, as on the desktop — switch layers to select the ink on another one. Or tap a single object to select just that one. Selected objects get a dashed outline with handles:
- Drag inside the outline to move them — drag onto a different page to move them there.
- Drag a corner handle to resize (uniform scale).
- Drag the round knob poking out from the right edge to rotate — shown only when the selection is all
strokes (text and images have no rotation in the
.xoppformat, so they can't be rotated). - The floating action bar offers Cut, Copy, Duplicate, a palette to recolour (the same swatches, custom slot and Recent row as the pen's colour pop-up) and a line-weight menu to re-width the selection, Delete, and Done (deselect).
- Paste appears in the bottom bar (when nothing is selected) and drops the copied objects onto the page you're viewing.
- Select background (flatten) is rectangle-only: drag a box to mark out a region, then copy it as one flat image including the page background and all layers. Paste then drops that image back onto the current page. The box stays on the page after you let go, and the bottom bar gains Copy, Cut and a ✕ for it: Copy puts the region on the clipboard (you can press it again later, once the clipboard has moved on to something else), Cut copies it and then erases the objects inside it on the active layer, and ✕ (or Back) drops the box. Marking out a box does not touch the clipboard on its own — only Copy/Cut do, so drawing a region never silently throws away what you had copied. The page background itself is copied but never erased — it belongs to the page, not to the region.
Object selection edits are undoable, and so is a background Cut. A background Copy only fills the clipboard; the later paste is undoable. (Selection is per page; two-finger pan still works.)
-
Text — pick Text from the Tool pop-up and tap where you want a text box; a dialog takes the content from the keyboard and lets you style it: font family (Sans / Serif / Monospace), bold, italic, a size slider (6–96 pt), and a colour — the same picker the pen uses, so the custom slot and the Recent row are shared with it. Tapping an existing text box reopens it for editing with all of that prefilled from the box (clearing the text deletes it). Every property round-trips to and from desktop Xournal++ via the
.xopp<text>element. (Underline isn't offered — the format can't store it.) -
Image — pick Image and tap where the image should go; the system picker opens, and the chosen picture is placed at that point (scaled to a sensible size).
-
LaTeX — pick LaTeX and tap to place a math image; type the LaTeX source (e.g.
\frac{a}{b},x^2,\sqrt{y},\alpha) and it's rendered as real math. -
Undo / Redo — the arrows in the top bar undo and redo edits, one gesture at a time (drawing, erasing, and adding/editing text/image/LaTeX are all undoable). They enable and disable as history allows; opening a file starts fresh history. History is 200 edits deep — past that the oldest step is dropped, so the most recent edits always stay undoable without the stack growing forever.
-
Scroll — drag with two fingers to move around the page stack, or pick the Hand tool from the Tool pop-up to pan with one finger (handy on a stylus). A quick one-finger flick keeps the pages gliding with momentum and coasts to a stop — the faster the flick, the much farther it carries — while a two-finger pan stops the instant you lift. Touch down again to halt a glide at once. With the Hand tool, a double-tap navigates: tap twice on the left edge to jump to the previous page, on the right edge for the next page, or in the centre to toggle full-page view (hides the top bar and side toolbar for a distraction-free canvas; double-tap the centre again to restore them). A PDF-style scroll thumb rides the right edge whenever the document is taller than the screen: drag it to page quickly through a long document (a page-number bubble shows where you are as you drag). A small grip bulges out of its centre so it's easy to grab. It sits faint while idle and brightens as you scroll; only the thumb itself grabs touches, so the rest of the page's right margin still takes ink. A page counter ("3 / 12") sits permanently in a corner of the canvas — the bottom-right by default, and movable from Settings ▸ Appearance — so you always know which page of how many you're on; it stays put in full-page view, where the bars are hidden. With a mouse connected, the scroll wheel scrolls the document vertically — wheel down moves further down the pages, the same direction as dragging the scroll thumb down — and leaves the zoom level alone.
-
Back button — the Android back button (or back gesture) navigates instead of quitting. Each press steps one layer out of wherever you are, and only the last press leaves the app. Dialogs and pop-up menus close on back as you'd expect; beyond those, back closes the radial palette, then cancels an unfinished spline, then leaves a text edit or drops a selection, then clears picked pages and leaves the page-overview edit mode, then restores the chrome from full-page view — and finally exits. In Settings, back returns from a section to the settings index, exactly like the on-screen back arrow, and a second press closes Settings back to your document. Back is ignored while a document is opening or saving, so a slow transfer can't be interrupted half-way.
-
Zoom — pinch with two fingers anywhere on the canvas to zoom in or out; the point between your fingers stays put as the page grows or shrinks, and you can pan at the same time in the one gesture. The % button on the rail also opens a zoom pop-up with − / + buttons; tap the percentage to reset to 100%. Zooming wider than the screen lets you pan sideways. Zoom ranges from 25% to 1000%, so you can work on fine detail; ink stays sharp at every level. PDF page backgrounds stay sharp too: past a certain zoom only the part of the page you can actually see is re-rendered, at full screen resolution, so PDF text is crisp all the way to 1000%. A freshly zoomed or panned area may look soft for a moment before the sharp version lands.
-
Background — the grid button on the rail opens the page-background pop-up, which sets the paper ruling of the page in view: Plain (bare sheet), Lined, Ruled (lined with a red margin), Graph, or Dotted. The current style is check-marked; picking another re-rules the page immediately (an undoable edit) and round-trips via the
<background style>attribute. On a PDF or image-backed page there's no solid sheet to re-rule, so the items are disabled. -
Pages — the document button on the rail opens the page navigator: it shows Page N / M with ◀ / ▶ to jump to the previous/next page, plus Add page (a blank page after the one in view — it keeps the current page's size and paper ruling, but a PDF/image background is dropped to a plain white sheet so the new page is genuinely blank, not a copy of the page underneath) and Remove page (the one in view; the last page is never removed). Add and remove are undoable.
- Pages per row — the 1 / 2 / 3 / 4 chips zoom the canvas out to a page overview: pick 2, 3 or 4 and the pages lay out side by side in a grid of that many columns (each fit to its column, rows top to bottom), instead of the usual single-page stack. Everything still works in the grid — you can draw, erase and select on whichever page you touch — so it doubles as a two-page spread for reading and a thumbnail overview for finding a page. The choice is remembered across launches; 1 returns to the single-page stack.
- Overview mode — View or Edit — under the columns chips, the View / Edit chips decide what the grid does with a tap. View (the default) keeps the overview a pure reading-and-navigation layout: tapping a page with the Hand tool simply jumps to that page, and there is no selection, no selection tint, and no drag-to-reorder. Edit turns the page tooling on — tap to select, drag to reorder, and copy/paste/delete the selected pages (all described below). Leaving edit mode clears any selection, so the grid never keeps stale selection chrome on it. The chips are only available at 2 or more pages per row; drawing, erasing and selecting on a page work the same in either mode.
- Reorder pages in the overview — while the grid is showing in Edit mode, press and
hold a page with your finger until it dims: that lifts it. Drag to another page — the slot it
would land in is outlined — and lift your finger to drop it there; the page moves to that
position and the pages after it shift along. The move is a single undoable edit, and the new
order is what gets written to the
.xopp, so it round-trips to desktop Xournal++. The pen is never a candidate for the lift, so drawing on a grid page is unaffected; sliding your finger before the press registers pans as usual. - Delete pages from the overview — while the grid is showing in Edit mode, pick the Hand tool and tap
pages to select them: each picked page is tinted and outlined, and tapping it again unpicks it.
The Pages pop-up then grows three entries — Copy N selected, Delete N selected, which
removes every picked page in one undoable edit, and Clear selection. A document always keeps at least one page, so
selecting every page deletes nothing (that entry is disabled). The remaining pages keep their
order and are what gets written to the
.xopp. The selection is view-only state: it clears when you return to 1 page per row, and after any page add/remove/reorder (the indices have moved). - Copy and paste pages — Copy N selected puts the picked pages on a page clipboard (in document order) without changing anything yet. The Pages pop-up then offers Paste N pages, which inserts them in one undoable edit directly after the last selected page — or after the page in view when nothing is selected — and scrolls to the first pasted page. The copies carry everything the page holds: strokes with pressure, every layer and its name, page size, and the background (a ruled sheet, an imported image, or the same PDF page), so a pasted page is a true duplicate that round-trips to desktop Xournal++. The clipboard survives until the next copy, so one copy can be pasted repeatedly.
- Page size… — the last row shows the page-in-view's size (a preset name like A4, or its
dimensions) and opens a Page size dialog: pick a preset (A4 / A5 / Letter / Legal), or type
a custom width and height in mm / in / pt (the unit toggle converts the fields), and swap
width↔height for landscape. Set resizes that page (undoable); the dimensions round-trip via the
<page width= height=>attributes to desktop Xournal++.
-
Settings — the top-bar menu opens Settings, a list of sections — Stylus, Palette, Editor, Toolbar, Navigation, Appearance, Storage and About. Tap a section to open it as its own page; back returns to the list, and back from the list returns to the editor. Your choices persist across restarts. Under Stylus:
- Finger draws — on by default; turn it off so fingers only pan/zoom and never actuate a tool at all — pen, highlighter, eraser, text and selection all become stylus-only (best on a stylus tablet where a palm would otherwise draw).
- Hover preview — show a ring where a hovering stylus will land.
- Palette haptics — tick as a radial-palette flick crosses into a new slot, with a stronger confirm buzz when it commits. Turn it off for a silent palette.
- Close palette on select — off by default; turn it on to dismiss the radial palette the moment a slot is picked. Off, the palette stays open after a pick so you can make several selections, and closes only when you tap outside it.
- Barrel button — what the stylus side-button does while held: Erase (default), Select, or None.
- Barrel double-click — what a rapid double-click of that same button does, recognised only with the tip off the glass (so it never interrupts a stroke): Undo (default), Redo, Toggle eraser, Toggle select, Toggle full page, Radial palette, or None. The two toggles flip back to the previous tool when double-clicked again.
- Radial palette — set the double-click to Radial palette and a two-ring menu pops up right at the pen tip. Flick outwards onto a slot (hovering is enough — the tip never has to touch the glass) and the slot lights up; the pick fires when you lift the tip on it, or when you double-click the barrel again without coming down at all. The menu stays open after a pick, so you can set a tool, a colour and a width in one summoning; it closes when you click off it — release well clear of the outer ring (an empty slot, or the hollow centre of the ring, simply does nothing). The middle of the ring is a hollow, inert hole, not a button: releasing the tip in there neither picks anything nor dismisses the menu, so a flick that falls short is a no-op you can simply repeat — to close, release outside the outer ring instead. A second barrel double-click picks and closes. Nothing you do while the menu is open can leave a stroke behind: the canvas ignores the pen until the menu closes. Slots run the same actions the toolbar does: pick or toggle a tool, set the pen colour or width, undo/redo, toggle full-page, and the page commands (new page before/after, duplicate, delete, next, previous). It starts from the built-in layout — tools and undo/redo on the inner ring, the standard pen colours on the outer one — and you can re-arrange it under Settings > Palette (below).
- Open the palette by touch — which touch gesture also summons that menu, for the many
styluses that have no side button at all. The side button is not an option here: what a barrel
double-click does is decided solely by Barrel double-click above, so there is exactly one
owner of that gesture.
- None (default) — no touch gesture opens the palette; only the barrel double-click can, and only when it is set to Radial palette.
- Pen-tip long press — rest the tip on the glass and hold it still; the ring opens where it rests, with a buzz. Move the tip more than a hair before the hold completes and you simply draw as usual — a stroke is never stolen, and the few pixels laid down while holding are thrown away rather than committed.
- Two-finger tap — tap two fingers on the canvas and lift them promptly; the ring opens midway between them. Anything that looks like a pan or a pinch (either finger travelling, a third finger, or a hold longer than a quarter-second) is left to pan and zoom as usual. Only one touch gesture is live at a time, so the two never fight each other for a touch. The barrel double-click is independent of this setting and keeps working alongside whichever touch gesture is chosen.
- Pressure sensitivity — Soft (thickens with a light touch), Linear, or Firm (needs a harder press).
- Stroke precision — how much of the pen's detail a stroke keeps: Economy, Balanced
(default), High, or Maximum. Strokes are thinned to a sub-pixel error budget as they're
drawn; raising the precision shrinks that budget, which draws visibly rounder curves on a large,
high-density tablet at 100% zoom and below, at the cost of a bigger
.xopp. Lowering it keeps files small. Existing strokes are unaffected — the setting applies to what you draw next. The budget is also capped in page units, so a stroke drawn zoomed out or in the multi-page overview stores the same detail as one drawn at 100% — zooming out never costs you precision. - Shape recognition — off by default; when on, a finished freehand pen stroke snaps to the primitive it resembles (see Shape recognition above).
Under Palette — the editor for the radial palette described above:
- The palette list sits at the top: one chip per saved palette, the selected chip being the one the editor below is editing, and a ● marking the one the pen actually opens. The buttons under the row add a palette (a new empty one, which also becomes active), rename the selected one, move it earlier/later, and delete it — you always keep at least one, so the delete button is greyed out on the last palette. Use this palette on the pen makes the selected palette the active one. Editing and activating are separate on purpose: you can arrange a second palette without disturbing the one your pen is currently using.
- The page then shows a diagram of the two rings for the selected palette, drawn from the same geometry the pen sees, so what you arrange here is exactly what pops up at the tip. An assigned slot shows its colour swatch or the same icon the toolbar rail uses for that action — a pen slot wears the rail's pen — with the width slots reading as a number; an empty one is a dashed outline.
- Tap a slot to select it (a highlight ring marks it, and the Selected slot line below spells out which ring, which position, and what it currently holds). The tap also opens the action picker — a bottom sheet listing everything a slot can do: Select tool and Toggle tool for each tool, the shared colour swatches, a width slider with a Set width button, Undo, Redo, Toggle full page, the page operations (new page after/before, duplicate, delete, next, previous), and — once you have saved any — a Preset group listing your saved tool presets (each numbered with its slot, "1. Fine liner"), so one flick restores a whole tool configuration (the slot draws a bookmark, tinted with that preset's own colour so several preset slots are told apart at a glance — very dark colours are lightened just enough to read on the slot's dark disc). Pick one and the sheet closes. A slot keeps the preset's name, so editing that preset updates the slot too; if you delete the preset, the slot reads "Preset (deleted)" and flicking onto it does nothing.
- Preset slot — the position-based twin of the group above, listed as Preset 1: Fine liner, Preset 2: Marker… (one row per preset you have saved, naming whoever sits there now). A slot bound this way follows the position rather than the preset: it always fires whatever preset currently sits first, second, and so on, so reordering the presets list re-points the slot to match. The slot draws its number (★1, ★2…) instead of a bookmark, and a position no preset occupies (you deleted one, say) reads "Preset 3 (empty)" and does nothing when flicked.
- Switch palette — once you have more than one palette, the sheet also lists a Switch palette group with one row per palette (the slot draws a target ring). Flicking onto it makes that palette the active one and pops it straight back up in the same place, so you can step from a pen palette to a shapes palette and make your pick without re-summoning the ring. The slot stores the palette's name, so reordering the list is safe; renaming or deleting the target leaves the slot doing nothing.
- Clear slot — the first row of the sheet empties the slot; an empty slot cancels the menu when flicked onto, so it's the way to make a gap.
- Below the diagram a summary line reports how full the palette is ("12 of 24 slots assigned.", or a warning that it would open empty), followed by two bulk actions: Reset to default restores the stock arrangement — tools on the inner ring, pen colours on the outer — and Clear all slots empties all 24. Both throw away your current assignments, so both ask for confirmation first; Clear all slots is greyed out when the palette is already empty.
- Every change is saved as you make it and pushed straight to the editor: the next barrel double-click already shows the new layout, and it survives a restart with the rest of your settings.
Under Editor:
- Snap to grid — off by default; when on, the endpoints of a shape you drag out land on the page background's ruling instead of anywhere in between (see Snapping above).
- Snap rotation — off by default; when on, rotating a selection steps in 15° increments.
- Default tool — which tool is active when a document opens: Pen (default), Highlighter, Eraser, or Hand (pan).
Under Toolbar:
- Toolbar position — which edge the tool rail is docked to: Left (default), Right, Top, or Bottom. Top/bottom lay the tool, colour & size, zoom, and page buttons out in a horizontal row along that edge; left/right keep the familiar vertical rail.
- Rail buttons — the full list of rail positions (the seven tool slots plus Colour & size, Style, Presets, Layers, Zoom, Background and Pages), each with a switch to hide it. To move one, press and hold its row and drag it up or down — the row lifts and the rest of the list shuffles under it as you go, so you can carry a button several places in one gesture. The rail draws them in this order, top-to-bottom (left-to-right when docked horizontally). Both the order and the hidden set are remembered across app restarts.
Under Navigation:
- Momentum scrolling — a slider setting how far a one-finger pan keeps gliding after you flick it. 0 turns momentum off (a released pan stops dead), 1.0 is normal (the default — a moderate flick coasts at about the speed you flicked), and higher values up to 10.0× stretch every coast farther. Two-finger pans never glide, whatever this is set to.
- Momentum curve — picks how sharply a faster flick coasts farther: Linear (even), Quadratic (the default — coast grows with the square of flick speed), Cubic, or Exponential (rewards fast swipes the most, so a tiny flick barely drifts while a hard swipe flies many pages). All four meet at the same moderate-flick reference, so this only changes how small flicks fall off and fast ones take off — the slider above still sets the overall strength.
- Panning sensitivity — a slider setting how far the canvas moves per unit of pan travel. 1.0 is one-to-one (the default — the page tracks your finger exactly), values below 1 pan slower than your finger, values up to 4.0× pan faster, and 0 turns panning off entirely. The gain also scales the fling, so a released pan coasts at the same visual rate it was moving.
Under Appearance:
- Theme — System (the default — follows the device's light/dark setting), Light, or Dark. The choice repaints the whole app from one Material 3 scheme: the top bar, the tool rail and its swatch rings, the settings pages, and the canvas backdrop, selection and guide colours. Page and ink colours are document data and never change with the theme.
- Use system colours — On Android 12+ takes colours from your wallpaper (Material You). When off, the app uses its fixed purple accent. Works alongside Theme to control the overall colour scheme.
- Page counter position — two drop-downs placing the always-visible "page X of Y" badge: Vertical (Top / Center / Bottom) and Horizontal (Left / Center / Right). The default is the bottom-right corner.
Under Storage — two budgets that bound what opening documents costs on disk and in memory:
- Text import limit — the largest plain-text file that may be typeset into a document (1 / 16 / 64 / 128 / 256 MB). Text is typeset a line at a time, so the limit is about time and output size rather than memory — a several-hundred-megabyte log is tens of thousands of pages and would take minutes; anything over the limit is refused with a message naming both sizes. Raise it if you really do mean to import something huge.
- PDF cache limit — how much space the app keeps for the background PDFs it generates and imports (64 / 128 / 256 / 512 / 1024 MB). These are what make reopening the same text file instant. Once the cache is over budget, the oldest ones no open tab is using are deleted; they are regenerated the next time you open that file, so nothing is lost but time.
Under About — what this build is and where it came from:
- Version and Git commit — the version name and code, plus the short commit the APK was
built from (baked in at build time; it reads
unknownwhen built outside a git checkout). Quote both when filing a bug. - Licence — NeXopp is free software under the GNU GPL, version 2 or later, the same licence
as Xournal++; the full text is in
LICENSE, and the page links to the GPL and to the Xournal++ project. - Source — a link to github.com/bamonroe/NeXopp.
- Buy me a coffee — a link to patreon.com/bamonroe if you'd like to support the work. Every link opens in your browser.
-
Export PDF — the menu's Export PDF flattens the whole document to a PDF: each page is drawn at its true size with its background (a PDF page, an image, or a ruled sheet) and every stroke and element merged on top, then written to the location you pick. Pages backed by an image keep that picture in the export, at print-usable resolution. When a page came from an imported PDF, its original page is kept as vector content and your annotations are laid over it as vectors too — so re-exporting an unchanged PDF stays about its original size and sharpness instead of ballooning from a rasterised copy. Use this to share an annotated copy; Save keeps the editable
.xopp. -
Fonts in generated PDFs — when NeXopp generates a PDF page for you (the text-import path), it typesets with DejaVu Sans (plus its bold, oblique and bold-oblique companions, used for markdown emphasis) and DejaVu Sans Mono, which ship inside the app. Because they are embedded (and subsetted) into the PDF, imported text in Cyrillic, Greek, CJK, or box-drawing characters renders the same on any viewer without those fonts installed, and the file only carries the glyphs it actually used. A character even DejaVu doesn't have is drawn as a
�placeholder rather than failing the export. Both fonts are freely licensed; the full licence ships with the app and is checked in atapp/src/main/assets/fonts/LICENSE.txt. -
Audio (record & replay) — the rail's Audio slot records the microphone while you write, and every stroke you draw is tagged with the moment in that recording it was started. Tap Record to start (Android asks for microphone permission the first time), and Stop recording to finish. Then pick the Play object tool from the rail and tap any stroke to hear the audio from the instant that stroke was drawn — the same
fn/tsstroke tagging desktop Xournal++ uses, so recordings made there replay here and vice versa. Stop playback in the same pop-up silences it.Audio is not stored inside the
.xopp— it lives in a.wavfile beside it, exactly as on the desktop. Android only grants an app access to the one file you picked, not its folder, so the first time you record, use Choose audio folder… in the Audio pop-up and pick the folder your.xoppfiles live in. NeXopp then writes new recordings there when you save, and loads a document's recordings from there when you open it. Until you choose a folder, recording and playback still work for the session, but the.wavnever leaves the app — so a file you take back to the desktop won't have its audio. -
Save — the menu's Save writes the whole document back out to a
.xoppfile, preserving every page, layer, background, and element — strokes plus the text, images, and LaTeX images you authored on-device. Save writes in whichever format you last chose in Save As… (see below): it starts as Original, and once you Save As Zipped, every later Save stays Zipped until you switch back. Opening a file adopts the format it was stored in. Save writes straight back to the file the tab came from — no picker — whether that file is on local storage or a mounted remote share; you're only asked for a location when the tab has no file yet (or the grant on it has lapsed). The document is encoded locally and then pushed across in one pass behind a "Saving…" note, so a slow or broken link can never leave a half-written.xoppon the far end. -
Save As… — the menu's Save As… opens a dialog to name the file and pick its format:
- Original (gzip) — the standard Xournal++
.xopp(gzip-compressed XML). For a PDF-backed document the PDF stays linked by location (its path/URI), so the.xoppis small and reopening it reloads that PDF from where it lives — the interchange-safe default. - Zipped (single file) — one self-contained
.xoppwith the PDF embedded inside it, so the document is fully portable and moves as a single file. It reopens with its background intact in NeXopp itself (the PDF travels in the same file) as well as on desktop Xournal++.- Note — targeting release Xournal++ on Arch Linux. The current released desktop Xournal++ (1.3.5) has a bug in its ZIP reader that rejects a correctly labelled archive, so NeXopp deliberately writes a slightly non-standard internal marker to open on that release. This is a temporary workaround; it will be reverted to the standard once upstream fixes the bug.
- Original (gzip) — the standard Xournal++
The file on disk is the only source of truth — there's no cloud, account, or custom format.
The file on disk is the only source of truth — there's no cloud, account, or custom format.
Every package and what it holds is documented, file by file, in
docs/architecture.md — the authoritative layout. The app's Kotlin
sources live under app/src/main/java/com/xopp/android/ (format/, io/, render/, audio/,
tabs/, panes/, ui/, plus MainActivity.kt), with JVM unit tests under app/src/test/ and
the containerized build in Dockerfile, compose.yaml and scripts/build.sh.
NeXopp is free software under the GNU General Public License, version 2 or later — the same
licence as Xournal++. The full text is in
LICENSE. It comes with no warranty.
If NeXopp is useful to you, you can support its development at patreon.com/bamonroe.