As the number of markdown documents have increased, as I increasingly get rich formatted messages where I want to preserve the original formatting, I found myself going through too many repetitive steps to get a document from one format to another -- often to a nice looking PDF -- but I deal with data in many formats and I wanted a simple-to-use "swiss army knife" to make my life easier.
This program will convert documents between 31 input formats and 12 output formats on your own machine. Your documents are never uploaded, there is no account and no telemetry. The app goes to the network for three things: a web page you ask it to fetch, the images a document or a paste refers to by web address (so they can be embedded in the output), and an OCR language pack you select. Remote images in email and calendar invitations are never fetched. See Images and the network.
Online converters upload the file you give them. This one does the conversion locally, and the code that does it is in this repository.
- Gallery
- Install
- What's new (release notes, in their own file)
- Editing before converting
- Sample documents
- What it reads
- What it writes
- Diagrams, formulas and code
- Pictures
- Images and the network
- What it is good at
- What it deliberately does not do
- Batch and merge
- Settings
- Command line
- Troubleshooting
- Development
- Building a release
- Project layout
- License
The sample documents (Help → Sample Documents) in the editor and converted to PDF, Word, EPUB and slides, four seconds a screen; npm run screenshots remakes it.
Download the release for your platform:
| Platform | File |
|---|---|
| Windows x64 | docconversion-<version>-setup.exe (installer) |
| Linux x64 | docconversion-<version>.AppImage |
| macOS, Apple Silicon | Document Converter-<version>-arm64.dmg (or the -arm64-mac.zip) |
| macOS, Intel | Document Converter-<version>.dmg (or the -mac.zip) |
- Windows. Run the installer. It installs for your user only (no
administrator rights), adds a Start menu entry and a desktop shortcut, and
puts the command-line launcher in
<install>\resources\cli\docconv.cmd. - Linux. Make the AppImage executable and run it:
chmod +x docconversion-<version>.AppImage && ./docconversion-<version>.AppImage. AppImages need FUSE 2 (libfuse2,libfuse2t64on Ubuntu 24.04); see Troubleshooting. - macOS. Open the
.dmgand drag Document Converter to Applications, or unzip the.zip.
Unless a release says otherwise, the builds are not code signed. Windows
SmartScreen warns on first run: choose More info, then Run anyway. On
macOS, Gatekeeper refuses an unsigned app on first launch: right-click it and
choose Open, or clear the quarantine attribute with
xattr -dr com.apple.quarantine "/Applications/Document Converter.app".
Building a release says how a signed build is made.
Nothing else needs installing: English OCR, the diagram and formula engines and the SQLite engine ship inside the app, and it works offline.
Anything pasted or fetched opens in an editor first, so you can trim it before converting; a file opens there with Edit. Pasted Markdown arrives as formatted text, not as raw syntax. The editor keeps everything the converter keeps, and what you see is what is converted.
- Formatting: paragraph styles (headings, code block and the rest), bold, italic, underline, strikethrough, superscript, subscript, highlight (Ctrl+Shift+H), inline code, lists, quotes, links, tables (with buttons to add and remove rows and columns), horizontal rules, undo and redo, and Clear formatting.
- Footnotes: Insert footnote (Ctrl+Alt+F).
- Formulas: the fx menu inserts an inline formula (Ctrl+Shift+M) or a formula block. An inline formula edits in a popover that shows its TeX and its drawing.
- Diagrams: the diagram menu inserts a Mermaid or Graphviz diagram. While the cursor is in one, its source is shown over its drawing, and the drawing follows as you type.
- Code: code blocks are colored by language as you edit. The language
button in a block's corner (Ctrl+Alt+L from the code) opens a list to
search by name or alias (
js,py,c++). Pasted code that names no language has it guessed.
On macOS, Ctrl is Cmd in these shortcuts. A file that changes on disk while it is open in the editor is not reloaded behind your back: a notice offers Reload from disk or Keep my edits. The Gallery shows the editor with each sample document.
Help → Sample Documents (on macOS in its Help menu), or the start screen's Sample documents link, opens one of five invented documents, each 3 to 4 printed pages, made to show what the app does:
| Sample | Shows |
|---|---|
| Engineering design note (a footbridge retrofit) | Graphviz load path, siunitx formulas, right-aligned number tables |
| Software design specification (a sync service) | Mermaid class and sequence diagrams, colored SQL and code |
| Lab report (reaction rate against temperature) | display and inline formulas, an SVG plot, a photograph |
| Meeting summary (an office move) | a Mermaid mind map and Gantt chart, highlighted text, an annotated floor plan |
| Architecture diagram | a standalone SVG drawing, printed landscape by Automatic orientation |
A sample opens as an untitled copy, so converting it never touches the copy
inside the app: a Markdown sample opens in the editor, the SVG is added as a
dropped picture would be. The samples live in resources/samples, built by
scripts/samples/build-samples.mjs.
The internal names are in SOURCE_FORMATS in src/core/types.ts:
txt, md, docx, pdf, html, eml, msg, csv, xlsx, rtf, image, svg, metafile,
code, ipynb, pptx, doc, epub, mbox, odt, odp, ics, asciidoc, rst, json,
subtitles, opml, vcard, djot, textile, sqlite
Some of those names cover more than one file extension. The extensions are in
EXTENSION_FORMATS in src/core/extensions.ts, and the start screen lists
them from there.
A file is read by its extension, unless its contents are beyond doubt
something else: a picture, a PDF, or an Office, OpenDocument, EPUB or Outlook
file named as text (.txt, .md, .csv, source code and the like) is read as
what it is, and the app says so ("This file is named .txt but is a TIFF
picture; read as a picture."). Text that only starts like one stays text.
- Word.
.docxand.docmare read fully..doc(Word 97 to 2003) is read for its text only, and the app tells you what it could not recover. - Spreadsheets.
xlsxcovers.xlsx,.xlsand.ods..csvand.tsvare tables too. - Presentations.
pptxcovers.pptxand.pptm,odpcovers OpenDocument slides (.odp)..odtis OpenDocument text. - Web pages.
.htmland.htm. - Text and Markdown.
.txtand.text; Markdown as.md,.markdown,.mdown,.mkd,.mkdn,.mdwnand.markdn, and R Markdown and Quarto (.rmd,.qmd) with their code chunks. Markdown is CommonMark with GitHub tables, task lists, alerts, strikethrough and emoji shortcodes, footnotes, definition lists, YAML front matter,$…$/$$…$$math andmermaid/dotdiagram blocks. Links to the document's own headings ([Sources](#11-sources), a contents list) link in every output that can link; see What's new in 1.5.2. Minified JSON and XML are printed re-indented. - Other documents.
.pdf,.rtfand.epub. - Email.
.eml,.msg(Outlook) and.mboxarchives. - Calendars and contacts.
.icsand.icalinvitations; vCard contacts (.vcf,.vcard), one row per contact. A.vcfthat is not a vCard is the genomics Variant Call Format and is read as text. - Images.
.png,.jpg,.jpeg,.gif,.webp,.tif,.tiff,.bmp,.avifand.ico. Embed the image in the output, or run text recognition on it. Each page of a TIFF (a fax, a scanned letter) becomes a page of its own, recognized page by page. Word keeps a TIFF as it is; an output that can't hold a picture gets PNG or JPG. HEIC (iPhone photos) is not read: Chromium cannot decode it, so save it as JPEG first. - Drawings.
.svg, and Word's vector pictures.emfand.wmf(metafile), are kept as drawings where the output allows it: SVG, its text still text (selectable in a PDF), otherwise PNG. Word keeps an EMF or WMF as it is. A drawing's text is its description. An EMF or WMF also converts to plain text, as its labels in reading order (top to bottom, left to right), one a line, and to Markdown, as the drawing followed by a list of its labels. - Source code. More than 80 extensions, and names such as
DockerfileandMakefile, listed inEXT_TO_LANGinsrc/core/code-langs.ts, syntax highlighted in the output. - Notebooks.
.ipynbrendered withIn [n]:/Out[n]:prompts, with code, output and errors styled differently. - Data. JSON (
.json) and JSON Lines (.jsonl,.ndjson): an array of flat records reads as a table, anything else as formatted JSON. SQLite databases (.sqlite,.sqlite3,.db3, and a.dbor any other name that starts with the SQLite header): the schema and every table, and the views' rows if you ask for them. - Subtitles and transcripts. WebVTT (
.vtt) and SubRip (.srt), and Teams meeting transcripts: who said what, and when. - Markup and outlines. AsciiDoc (
.adoc,.asciidoc,.asc), reStructuredText (.rst), Djot (.djot,.dj), Textile (.textile) and OPML outlines (.opml).
Three inputs are not file formats:
- The clipboard. Ctrl+V or Paste from clipboard. Formatting from Teams, Word and Outlook survives, including tables, links and images. Images a web page puts on the clipboard by address are downloaded and embedded; see Images and the network.
- A web page address. Paste a link or use From URL. The readable article is extracted with Mozilla Readability and its images embedded, every one of them. Every redirect hop is checked against loopback and link-local addresses, so a page cannot make the app fetch something on your own machine or a cloud metadata service. Intranet addresses are allowed.
- A ZIP archive. Expanded into its convertible entries, each becoming a separate input. Junk entries such as macOS resource forks are skipped, with a cap of 100 entries.
Drop files anywhere in the window, including onto a list you have already loaded. A document dropped on the app's Dock icon or shortcut, or opened with it from the file manager, is added the same way (see What's new in 1.5).
The output formats are in TARGET_FORMATS in src/core/types.ts:
txt, md, docx, pdf, html, epub, revealjs, azw3, azw4, csv, json, xlsx
Documents
- txt — plain text.
- md — Markdown with GitHub-style pipe tables.
- docx — a Word document, on the page size you choose for pdf (Letter, A4, Legal, A3, Tabloid; Letter by default) with Word's own 1-inch margins. Orientation is chosen as for pdf: Automatic unless you choose Portrait or Landscape. A document of pictures a page (a TIFF) gets a page, and a section, a picture, each turned as its picture is and on the same paper. A picture larger than its page is made to fit. A heading that a link in the document points to gets a Word bookmark, and the link jumps to it.
- pdf — rendered by Chromium. Zoom 50–200% (90% by default), page size
(Letter, A4, Legal, A3, Tabloid), orientation, and an optional header and
footer with the title, date and page numbers. A horizontal rule (
---in Markdown) starts a new page. Orientation is Automatic unless you choose Portrait or Landscape: landscape pages for a wide picture on its own (each page of a TIFF by its own shape) and for a document that is mostly wide tables, portrait for the rest. Every PDF has a bookmarks sidebar, one entry per heading, and is tagged for screen readers; links to the document's own headings jump to them. - html — one standalone file.
- epub — an e-book. Chapters split on the document's own headings, images stored once each, markup written as XHTML because e-book readers require well-formed XML. A link to a heading goes to it, in whichever chapter it is.
Slides
- revealjs — a reveal.js deck as a single
self-contained HTML file. Slides are separated by a horizontal rule (
---in Markdown) or by a heading level you choose; Automatic uses heading 1, or heading 2 when heading 1 is only the title. Speaker notes written as<!-- notes: ... -->reach reveal's speaker view. A link to a heading goes to the slide that holds it.
Kindle
- azw3 — a reflowable Kindle e-book (KF8), built from the same content as the EPUB output, links to headings included.
- azw4 — a Kindle print replica: your PDF in a Kindle container. It does not reflow, so it is rendered at Kindle page size regardless of the page setup you chose: a Letter page shown on a 6-inch screen puts the text at roughly a third of its intended size.
Tables (TABLE_TARGETS)
- csv — one file per table found in the document.
- json — one file per table, keyed by the header row when there is one.
- xlsx — one workbook, one sheet per table. A table longer than Excel's 1,048,576 rows continues on a second sheet, its header row repeated.
No output refers to the network: every image is embedded in the file, or replaced by its description.
There is also a Convert to clipboard button, which puts both a rich HTML and a plain text version on the clipboard. It is hidden for outputs whose bytes are not text (Word, PDF, EPUB, Excel, Kindle), and for slide decks, which are a complete HTML document with about 240 KB of JavaScript inlined.
Diagrams and formulas are drawn by engines that ship inside the app, in a hidden, sandboxed window with no network access: Mermaid 11.12, Graphviz 16.1 (viz-js 3.31) and MathJax 3.2. Nothing is sent anywhere to be drawn.
- Where they come from. Markdown fences (
```mermaid,```dotor```graphviz) and$…$/$$…$$math, and the same in notebooks, AsciiDoc, reStructuredText and Djot; web pages that mark them as diagrams or TeX; and Word equations, which are converted to TeX. - How they are written. Sharp (SVG), the default, embeds them as SVG
in PDF, HTML, slides, EPUB and Kindle Print Replica (AZW4): text stays
selectable and they print sharp at any zoom. Image (PNG) (Settings →
Formulas & diagrams, the conversion screen, or
--diagrams png) embeds PNG instead. Word and Kindle (AZW3) always get PNG, sized to the text, with inline formulas set on the baseline. Markdown and plain text keep the source (fences and dollars), so a round trip loses nothing. - When one fails. A diagram with a syntax error, or a formula with an unknown macro, keeps its source in the output, with a notice naming it; the rest of the document converts.
- Code is colored by language (highlight.js grammars; more than 80
extensions, plus names such as
DockerfileandMakefile) in every output that can show color, and in the editor. Settings → Code or--no-code-colorwrites it plain black, for printing.
Copying the result to the clipboard (Convert to clipboard) turns diagrams and SVG pictures into PNG, which Word and Outlook can show.
The app reads PNG, JPEG, GIF, WebP, TIFF, BMP, AVIF and ICO pictures, SVG drawings and Word's EMF and WMF drawings, on their own or inside documents (Word, RTF, PowerPoint, OpenDocument, web pages, EPUB, email). Each output keeps what it can hold and gets a converted copy of the rest:
| Output | Kept as they are | Converted |
|---|---|---|
| Word | PNG, JPEG, GIF, BMP, WebP, TIFF, EMF, WMF | SVG gets a PNG copy; AVIF and ICO become PNG or JPG |
| PDF, HTML, slides, AZW4 | PNG, JPEG, GIF, WebP, SVG, BMP, AVIF, ICO | TIFF; EMF and WMF are drawn as SVG |
| EPUB | PNG, JPEG, GIF, WebP, SVG | EMF and WMF are drawn as SVG; the others become PNG or JPG |
| Kindle (AZW3) | PNG, JPEG, GIF up to 5 MB | the others (SVG and drawings as pictures), and anything over 5 MB, fitted under 5 MB |
| Markdown | PNG, JPEG, GIF, WebP, SVG | the others become PNG or JPG |
A converted picture becomes PNG when it is a screenshot, line art, a diagram,
a scan or a GIF (few colors or large flat areas), and JPG when it is a
photograph, unless Settings → Pictures (or --pictures jpg|png) says
otherwise. Pictures with see-through parts always stay PNG, and drawings stay
drawings (SVG) where the output allows it. A TIFF of several pages (a fax, a
scanned letter) becomes a picture a page, each page printed on a page of its
own and turned as it is; with OCR each page is recognized.
The limits that remain protect memory, not size: a TIFF page may be up to 50 megapixels, and a web picture up to 200 MB. HEIC (iPhone photos) is not read: Chromium cannot decode it, so save it as JPEG first.
What leaves your computer, in full: a web page you ask for (From URL), the pictures a document or a paste names by web address (on by default, can be turned off), and an OCR language pack you choose to download. Conversions themselves run locally; there is no account, no telemetry, no update check and no cloud service.
A document can refer to an image by web address instead of containing it: a
saved web page, a Markdown file with , a paste from a
browser. The app downloads such images when the input is read and embeds them,
so the editor shows them and every output carries them. This is on by
default and applies to:
- a file you open or drop, whatever its format;
- a paste, from Ctrl+V or Paste from clipboard;
- a web page fetched with From URL;
- the command line, which reads inputs the same way.
Opening a document that refers to remote images therefore contacts the servers
that host them, which tells those servers your IP address and that the
document was opened. Settings → Web turns this off, or refuses intranet
addresses (see Settings); on the command line --no-fetch and
--no-local-network do the same (see Command line).
Two exceptions are built in:
- Email and calendar invitations (
.eml,.msg,.mbox,.ics) never have their remote images fetched. Senders use them to learn that a message was opened, so they are replaced by their description, as a mail client does. - Paths (
assets/fig.svg,../images/fig.png,/home/me/fig.png,C:\pics\fig.png,file:///…) name a file on disk, not a web address, and are never fetched. In Markdown, as well as Djot and Textile and a notebook's Markdown cells, a document opened from a file has these pictures read from disk, relative to its folder, and embedded. A file is embedded only if it really is a picture. A network share (\\host\share) is not read. A picture that is missing becomes its description.
Every download goes through the same checks as From URL:
- Only
httpandhttps. Pictures on your intranet are fetched, unless Settings → Web says not to: private addresses (10.x, 172.16–31.x, 192.168.x, IPv6fc00::/7) and single-word host names such ashttp://intranet/logo.png. Each redirect hop is checked, and this machine (localhost, 127.x,::1), link-local addresses (169.254.x, where cloud metadata services answer, andfe80::), and multicast and reserved addresses are refused, so a document cannot make the app reach a service on your own machine. The connection is made to the address that was checked, so a name cannot change its answer in between. - Every image is fetched, however many there are, four at a time, with 10 seconds allowed per image. A single image may be up to 200 MB, so that one response cannot exhaust memory.
- The body must actually be an image. A login page, an error page or a JSON reply sent in place of an image is not embedded.
An image that cannot be fetched, is over 200 MB, or is skipped becomes its
description, such as [image: quarterly chart]. A paste shows its progress
("Fetched 3 of 12 images"); Skip remaining images opens the editor straight
away with the images that have arrived. During a conversion the usual Cancel
applies.
The writers never fetch anything themselves. Any remote reference still present when a document is written is replaced by its description, and the window that renders PDF output has no network access at all.
Tables. Recovered from every source that carries one — the easy cases (HTML, Markdown, spreadsheets, CSV) and the hard ones:
- A PDF has no table structure, only text and its position, so the grid is inferred from the geometry. On the test corpus this recovers 408 of 409 rows across 20 tables, with no false tables on a 70-page document of ordinary prose.
- The same inference runs on text-recognition output, so a scanned table can become a CSV file.
- RTF tables are parsed from the control words, and nested tables stay nested.
- reStructuredText simple tables, grid tables and column spans use ports of docutils' own algorithms.
- A
.txtfile whose columns are separated by tabs is read as a table.
Text recognition. A PDF with no text layer, or an image, is read with tesseract.js, offline, in a separate process. English ships with the app; 24 further languages are downloaded only when you ask for one, verified by checksum, and stored alongside it. The confidence threshold is set per script, and no uncalibrated script is given a lower threshold than English, which was measured.
Images in PDFs. Extracted and placed back into the output in reading order.
Large tables in PDF output. Chromium's printToPDF refuses documents above
a size that depends on the shape of the content, and clips wide tables with no
warning and no error. Large tables are split across several print calls and the PDFs
joined, repeating the header row on each part.
Page fit advice. Before printing, the app estimates how wide the widest table needs to be, and suggests the smallest page setup that fits. It never changes the paper size. Orientation it changes only in Automatic, the default, and only for a document that is mostly tables too wide for a portrait page; a report with one wide table among its text stays portrait and gets the tip instead. Portrait and Landscape are kept exactly as chosen.
Merging. Several inputs into one document. When the output is PDF and the inputs are PDFs, pages are copied straight across rather than re-rendered, so text layers, images and scanned pages survive unchanged.
Most of the limits below are choices rather than gaps.
It does not reproduce layout. This converts content, not appearance. Fonts, margins, columns and exact spacing are not carried across. If you need the output to look like the input, this is the wrong tool.
It does not guess at a table it cannot read. A wrong table is worse than no table, because it silently reorders the document's content.
- Grid inference declines when the geometry is not convincing; the PDF confidence floor is 0.75.
- Text recognition passes three separate checks before its output becomes a table. A paragraph of prose looks like a grid when all you have is the position of each word — one became a 17-column table before those checks existed.
- A
.txtfile whose columns are aligned with spaces is not read as a table. Indented code and aligned prose cannot be told apart from columns without understanding the text. - A
.docfile produces no tables. The old Word format keeps a marker at the end of each cell but no column geometry, so the grid cannot be rebuilt. The cell markers are made visible as|instead, which shows where the cells divided without asserting a column structure that cannot be determined.
It reports what it could not recover from a .doc. Tables, numbered lists
and embedded pictures are named, with a suggestion to re-save as .docx. It
does not invent numbering and does not emit a placeholder where a picture was.
The picture bytes are recoverable; their positions are not.
Table cells lose inline formatting in Markdown output. Tables are rebuilt
from a plain-text grid, which is what stops a cell containing | corrupting the
table.
Some structure cannot be recovered. Recorded so they are not reported again as bugs:
- A PDF paragraph crossing a page break stays two paragraphs; paragraphs are built one page at a time.
- A PDF sub-heading that differs from body text only by being bold stays a paragraph — the PDF library reports glyph height, which bold does not change.
- OpenDocument numbered lists are written unnumbered; the numbering style lives in a part of the file the reader does not load.
- HTML
<style>blocks are not interpreted, so an element hidden by a stylesheet is still converted. - Word tab-stop positions cannot be reproduced. Tab stops stay visible so nothing runs together.
Links pointing inside the same document are dropped. The internal HTML
carries no element IDs, so such a link has no destination after conversion. The
link is removed and its text kept, because a link with no destination is worse
than plain text. See
docs/superpowers/specs/2026-09-01-epub-output-and-anchors-design.md.
No auto-update. The app does not check for updates and contains no updater.
electron-builder.yml sets publish: null, so no update endpoint is written
into the packaged app.
macOS builds are unsigned unless built with a certificate. npm run build:mac
cross-builds an unsigned x64 zip on Linux; a .dmg, an Apple Silicon build,
signing and notarization need a Mac (hdiutil and codesign are macOS-only),
which scripts/build/build-macos.sh does (see
Building a release). Without an Apple Developer ID
certificate, Gatekeeper challenges the app on first open.
Out of scope. .ppt (the old binary PowerPoint format, with no usable
JavaScript reader), .odg, LaTeX, .pst archives, and converting email
attachments recursively.
Load several inputs by dropping more files, or with Add files and Add from clipboard, then choose a mode. Files are read from disk when you convert, not when you add them, so a file you changed in the meantime converts as it is now.
- Batch converts each input separately into a directory you choose. You get a result per file, can retry one that failed, and canceling marks the rest as canceled. A file that was moved or deleted after it was added fails on its own row, by name, and the rest carry on.
- Merge joins everything into one document. Each source gets a heading with its name, which you can turn off, and a page break is always inserted between sources.
The csv, json and xlsx outputs are excluded from merge, since they produce one file per table and are per-file work by definition.
Settings holds the defaults for choices most people make once. Open it with File → Settings… (Ctrl+,) on Windows and Linux, or the app menu's Settings… (Cmd+,) on macOS; the start screen also has a Settings link and the conversion screen a gear button. A change is saved as soon as you make it and applies from the next conversion; there is no OK or Cancel. Every setting says which outputs it affects.
| Section | Setting | Default | What it does |
|---|---|---|---|
| Pictures | Pictures the output can't hold | Automatic | A picture the output can't hold as it is (a TIFF in a PDF, a WebP in a Kindle book) is converted. Automatic makes it PNG, which keeps every pixel, when it is a screenshot, line art, a diagram, a scan or a GIF (few colors or large flat areas), and JPG, much smaller, when it is a photograph; JPG or PNG makes every one that kind. Pictures with see-through parts always stay PNG; drawings (EMF, WMF, SVG) stay SVG where the output allows it, otherwise PNG |
| Web | Fetch pictures from the web | on | Off, web pictures in documents, pastes and web pages are not downloaded; each becomes its description, and the result says how many. A web page opened from an address is still fetched. Email and calendar never load web pictures, whatever this says |
| Web | Allow pictures from your local network | on | Off, intranet addresses are refused too (pictures, and web pages opened from an address). Shown only while fetching is on |
| Formulas & diagrams | Draw diagrams and formulas as | Sharp (SVG) | Or Image (PNG). Word and Kindle (AZW3) always get images |
| Code | Color code by language | on | Off, code blocks are written plain black, for example for printing. The editor's colors stay either way |
| Pages | Page size; zoom; header and footer | Letter; 90%; off | What a new PDF starts with; the page size is a new Word file's too. On first run, unless the system's region uses Letter paper (US, CA, MX or PH), the page size is saved as A4. Orientation has no setting: it starts at Automatic every time, and Portrait or Landscape is chosen on the conversion screen for the document at hand |
| Reading | Transcripts: show times | on | Subtitle and transcript paragraphs start with their time |
| Reading | SQLite: include view results | off | Adds the rows each view returns; views are always listed in the schema |
| OCR | Default language | English | The language OCR reads in. One that isn't installed is downloaded first, after showing its size |
| OCR | Installed languages | Each downloaded language with its size and Remove, which asks first and says how large the download to get it back is. English is included and can't be removed. If you remove the default language, English is used until you install it again |
The conversion screen is for this session. Its page size, zoom, header and footer, diagrams, OCR language and reading choices start from Settings each time the app starts. A change made there lasts until you quit and is never saved; Settings is the one place defaults change. A screen value you have not touched follows a change made in Settings at once.
Reset this section and Reset all settings… (with a confirm) put the defaults back. Neither removes a downloaded OCR language.
The settings are saved in settings.json in the app's data folder,
<appData>/docconversion: ~/.config/docconversion on Linux,
%APPDATA%\docconversion on Windows, ~/Library/Application Support/docconversion on macOS. It is the same folder for an installed copy
and for npm run dev, and downloaded OCR languages are kept beside it, in
ocr/. The file is JSON and holds only what differs from the defaults. If it
can't be read, it is renamed to settings.json.bad-<time>, the defaults are
used, and the window says so.
The command line does not read these settings unless asked:
--settings app uses this file, and --settings <file> any file in the same
format (see Command line). Every setting also has a flag.
docconv convert <inputs...> --to <txt|md|docx|pdf|html|epub|revealjs|azw3|azw4|csv|json|xlsx>
[--out <fileOrDir>] [--from <format>] [--merge] [--no-headings] [--table N]
[--split <auto|hr|h1|h2>] [--diagrams <svg|png>] [--scale 1.25] [--page A4]
[--orientation <auto|portrait|landscape>] [--landscape] [--header-footer]
[--no-header-footer] [--ocr] [--ocr-lang <code>]
[--pictures <auto|jpg|png>] [--fetch] [--no-fetch] [--local-network]
[--no-local-network] [--code-color] [--no-code-color]
[--json-as <table|text>] [--no-timestamps] [--sqlite-views]
[--settings <file|app>] [--no-clobber] [--help] [--version]
The command line is the app itself, started with convert as its first word:
- Windows:
<install>\resources\cli\docconv.cmd convert …(the installer puts the app in your user folder, under%LOCALAPPDATA%\Programs; add itsresources\clifolder toPATHto typedocconv). - Linux:
./docconversion-<version>.AppImage convert …. - macOS:
"/Applications/Document Converter.app/Contents/MacOS/Document Converter" convert …. - A development checkout:
npm run cli -- <args>(afternpm run build).
--help and --version work without convert. Examples:
docconv convert report.docx --to pdf --page A4
docconv convert notes.md --to docx --out notes.docx --settings app
docconv convert scan.tiff --to txt --ocr --ocr-lang deu
docconv convert a.md b.md c.md --to pdf --merge --out handbook.pdf
docconv convert *.html --to epub --out books/ --no-fetch
docconv convert sales.xlsx --to csv --table 2 --out sales.csv
docconv convert talk.md --to revealjs --split h2 --diagrams png
docconv convert app.sqlite --to xlsx --sqlite-views --out app.xlsx
--outnames a file, and its extension is honored. Given a directory, names are derived from the inputs. Given no extension, the target's is appended.- Output files are overwritten by default so a script gets a predictable path.
--no-clobberrefuses instead. --scalemust be between 0.1 and 2.0; a value outside that is rejected rather than clamped. The default is 0.9.--pagesets the paper of a pdf or a docx (Letter by default).--orientationsets the PDF page's orientation.auto, the default, chooses as the app's Automatic does: landscape for a wide picture on its own (page by page for a TIFF) and for a document that is mostly wide tables, otherwise portrait.portraitandlandscapeare every page.--landscapeis the older spelling of--orientation landscape; the two together are a usage error (exit 2), and--orientationwith any output but pdf and docx is too (a Kindle AZW4 page is always chosen automatically). For docx it turns Word's page the same way.--table Npicks one table, for csv and json output only.--splitchooses where a new slide starts, for therevealjstarget.--mergecannot be combined with csv, json or xlsx.- A picture the output can't hold (a TIFF in a PDF, a BMP in an EPUB) is
converted as in the app: by default (
--pictures auto) to PNG when it is a screenshot, line art, a diagram, a scan or a GIF, and to JPG when it is a photograph;--pictures jpgor--pictures pngmakes them all one kind. Pictures with see-through parts stay PNG, and drawings stay SVG or PNG, either way. --no-fetchconverts without downloading web pictures; each becomes its description and aNOTEsays how many, and that--no-fetch(or the settings file) turned fetching off.--no-local-networkstill fetches but refuses intranet addresses; neither it nor--local-networkcan be combined with--no-fetch.--no-code-colorwrites code blocks plain, not colored by language.--no-header-footerleaves the PDF header and footer off.--fetch,--local-network,--code-colorand--header-footerturn these back on when a--settingsfile turns them off. Without--settingsthe first three are on already, so they change nothing. A flag and its--no-opposite together are a usage error (exit 2).--ocr-lang deureads with--ocrin another language. Its pack must be installed: English ships with the app, others are downloaded in the app.--settings <file>starts from a settings file (the format the app saves);--settings appuses the app's own, in its data folder (~/.config/docconversion,%APPDATA%\docconversion,~/Library/Application Support/docconversion). The file is only read. Flags win over it, a value that doesn't apply to the run is ignored, and a bad value is aNOTEand its default. Without--settingsthe app's settings are never read, so a script gives the same result on every machine.- A flag that can do nothing for the output or the inputs given (
--picturesfor txt,--diagramsfor Word,--sqlite-viewswith no database) is an error, exit code 2.--table,--split,--scale,--page,--landscapeand--header-footerpredate this rule and are still ignored instead. - More than one input requires
--out. - Exit code 0 when everything succeeded, 1 when anything failed, 2 when the arguments were wrong.
- On Linux, PDF output needs a display server — run under
xvfb-runif headless.
- Windows says the app is unrecognized (SmartScreen). The build is not code signed: choose More info, then Run anyway.
- macOS says the app is damaged or can't be opened. It is unsigned and
quarantined: right-click it and choose Open, or run
xattr -dr com.apple.quarantine "/Applications/Document Converter.app". - The AppImage does not start on Linux ("dlopen(): error loading
libfuse.so.2"): install FUSE 2 (
sudo apt install libfuse2, orlibfuse2t64on Ubuntu 24.04), or run it with--appimage-extract-and-run. - PDF output fails on a Linux server. PDF is printed by Chromium, which
needs a display server: run the command line under
xvfb-run. - A web picture is missing (it shows as
[image: …]). It could not be fetched within 10 seconds, was over 200 MB, was not a picture, or is on an address the app refuses (this machine, link-local, or the intranet when Settings → Web says so). Email and calendar invitations never fetch them. - A diagram or formula shows its source. It has an error, or uses a macro MathJax doesn't know; the notice after the conversion names it.
- Settings were reset. A
settings.jsonthat can't be read is renamed tosettings.json.bad-<time>beside it and the defaults are used; the window says so. - OCR in another language needs that language's pack, downloaded once from
Settings → OCR (it shows the size first). On the command line,
--ocr-languses packs the app has installed. - An iPhone photo (HEIC) is refused. Save it as JPEG first.
npm install
node scripts/fetch-ocr-assets.mjs # one time: downloads the English OCR data
npm run dev # launch with hot reload
npm test # unit tests
npm run typecheck
Two tests are skipped by default: the visual and EPUB-corpus harnesses, which run only when their environment variables are set.
A passing test suite is not evidence that a document converts correctly. A
string assertion cannot see an <img> tag printed as visible text, two words
run together, or a table clipped off the edge of the page. All three shipped
past a green suite and were found by rendering a document and looking at it.
For any change affecting layout, render one and read it:
VISUAL_FILES="a.pptx|b.ipynb" VISUAL_OUT=/tmp/v npx vitest run tests/tools
npx electron scripts/print-pdf.cjs /tmp/v/a.html /tmp/v/a.pdf
VISUAL_TARGET sets which output format the page is styled for (default pdf).
tests/tools/epub-corpus.test.ts does the same for e-books via EPUB_FILES
and EPUB_OUT.
tests/corpus/ holds synthetic documents built by generators in
scripts/fixtures/, so a fresh clone is self-contained. They are built to
reproduce the structural property under test rather than to look realistic —
a fixture that passes without exercising the property is worse than none.
npm run test:e2e -- <outDir> tests/fixtures/sample.docx ...
npm run build && npm run test:cli
npm run build && npm run test:ui
npm run screenshots # remake the screenshots and docs/screenshots/tour.gif
npm run screenshots:gif # remake only tour.gif from the PNGs already there
The first converts every fixture to every output format inside a real Electron main process, then checks the result by reading it back through the app's own readers.
test:ui drives the real window and checks what it does between two
conversions: that a file changed on disk is converted as it is now, that a
deleted file is reported rather than converted from old bytes, and that edits
made in the pane survive a result being shown and dismissed. On Linux it runs
under xvfb-run when that is installed; anywhere else it uses the real desktop
and overwrites the clipboard.
npm run build:unpack # the app, unpacked, for this OS -> dist/<os>-unpacked
npm run build:win # NSIS installer -> dist/docconversion-<version>-setup.exe
npm run build:linux # AppImage -> dist/docconversion-<version>.AppImage
npm run build:mac # x64 zip -> dist/Document Converter-<version>-mac.zip
Each build:<os> first installs that platform's @napi-rs/canvas binding
(scripts/native-for-target.mjs): npm installs only the host's, and a package
without its own converts every PDF with no pictures, silently. build:win
runs on Linux too, but electron-builder needs Wine there to finish the
installer (it runs it once to write the uninstaller); dist/win-unpacked is
made without Wine. build:mac on Linux makes an unsigned x64 zip, zipped by
scripts/build/zip-mac.mjs with Info-ZIP zip (apt install zip):
electron-builder's own zip on Linux turns the Electron framework's symlinks
into copies, tripling its size.
What goes into a package is an allowlist in electron-builder.yml: out/
(less the e2e bundle and the build's own package record), resources/,
package.json and node_modules/. Nothing else in the checkout can be
packaged by accident. Of the @napi-rs/canvas bindings in node_modules,
a package takes only its own OS's and architecture's, whichever others the
build machine has installed.
One script per OS, run on that OS, makes a release build end to end:
scripts/build/build-linux.sh # on Linux x64
scripts/build/build-macos.sh # on a Mac
powershell -ExecutionPolicy Bypass -File scripts\build\build-windows.ps1 # on Windows x64
Each checks the platform, Node (engines in package.json: 22.13 or later)
and npm, runs npm ci, the type check and the unit tests, builds, and prints
each artifact's path, size and SHA-256. --e2e (-E2E on Windows) also runs
test:cli and test:ui against the build; --skip-checks (-SkipChecks)
skips the type check and tests, for trying a build, not for a release.
| Script | Makes | Signing |
|---|---|---|
build-linux.sh |
dist/docconversion-<version>.AppImage (x64) |
none |
build-windows.ps1 |
dist/docconversion-<version>-setup.exe (NSIS, x64) |
signed when CSC_LINK (or WIN_CSC_LINK) and CSC_KEY_PASSWORD name a code-signing certificate; otherwise unsigned |
build-macos.sh |
a .dmg and a .zip each for Apple Silicon and Intel (--arch arm64|x64 for one) |
signed with a Developer ID Application certificate in the keychain (or CSC_LINK), and notarized when APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER, APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD/APPLE_TEAM_ID or APPLE_KEYCHAIN_PROFILE are set; otherwise ad-hoc signed, with a note |
The version is the one in package.json. The scripts do not tag, publish or
upload anything.
src/core/— the conversion logic. No Electron dependency, covered by unit tests.readers/— one file per input format, plusurl.tsand the sharedodf-common.ts/email-common.ts.writers/— one file per output format.detect.ts— decides what an input is, via ordered rules returning either a format or a reason it is unsupported.allowlist.ts— the single list of HTML tags and attributes the internal document may contain. The editor's schema must model every one of them.grid-infer.ts,table-grid.ts— find and extract tables.page-fit.ts,html-chunk.ts— estimate fit, and split a large table for printing.slides.ts— cuts the document into slides.merge.ts,pdf-merge.ts— join documents, and join PDFs page for page.net/guarded-fetch.ts— the fetcher that refuses loopback, link-local and reserved addresses.
src/ocr/— the recognition pipeline, the language catalog, and checksum-gated pack installation. No Electron dependency.src/cli/— argument parsing and output-path resolution. No side effects.src/main/— the Electron main process: IPC handlers, dialogs, conversion orchestration, the recognition host, the headless CLI runner, settings, the menu and the About window.render-host.ts— the hidden, sandboxed window that draws diagrams, formulas, metafiles and converted pictures.sqlite-host.ts,sqlite-worker.ts— SQLite databases, read in a worker with its own memory cap.net.ts— the pinned fetcher: connects only to the addresses the guard checked.file-handles.ts— keeps the path of each file the app was given and hands the interface a token for it, so a conversion re-reads the file from disk.
src/preload/— the typedwindow.apibridge. Context isolation on, Node integration off.src/renderer/— the React interface.lib/items.ts— the input list, which is the only state that outlives the editor: edits are written back to it before a result is shown.
resources/— shipped with the app:render/(the diagram and formula engines and the render page),sqlite/(sql.js),ocr/(English OCR data),samples/, andnotices/(written by the build).scripts/— build helpers:build/(the release scripts),screenshots.mjs(the README's pictures and tour),samples/,fixtures/,notices/.docs/screenshots/— the README's tour and picture.
MIT © Stephen Genusa
The app ships many open-source components, each under its own license; Help →
About lists every one with its license text, and the build writes the same
list to resources/notices/THIRD_PARTY_NOTICES.txt. Among them: reveal.js
(MIT), Mermaid (MIT), MathJax (Apache 2.0), Graphviz via viz-js (EPL 2.0),
sql.js (MIT), tesseract.js (Apache 2.0), and OCR language data from the
tesseract-ocr project (Apache 2.0).

