A GUI to produce PDFs or DjVus from scanned documents.
Scantpaper is a Linux application (it needs GTK4, SANE, and a Python 3 interpreter, all of which are available on other Unix-like systems like MacOS or BSD as well). It is the Python rewrite (v3) of the popular gscan2pdf.
Screenshot: Main page
Install scantpaper and its dependencies (see Download, Installation & Removal), then:
scantpaper (or
python3 -m scantpaper.app from a source checkout, with
PYTHONPATH=src set). Add
--debug|info|warn|error|fatal to enable logging at the
required level.scantpaper provides a GUI for scanning, editing, and saving documents as PDF, DjVu, TIFF, PS, TXT, hOCR, SDB (scantpaper session), or image files (PNG, JPEG, PNM, GIF), and can prepend or append to an existing PDF. It supports batch scanning, metadata, OCR, and various editing tools.
Scans are acquired with SANE and held in a session database while you
edit them. When saving, PDFs are produced with img2pdf and
OCR’d with ocrmypdf (which produces PDF/A out of the box);
DjVu export uses djvulibre-bin, TIFF export uses
libtiff, and images are written with ImageMagick.
┌─────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────────────┐
│ SANE │ │ SQLite session │ │ edit tools / │ │ img2pdf / │──▶ PDF (PDF/A)
│ scanner │──▶│ (pages in temp │──▶│ OCR │──▶│ ocrmypdf │
│ │ │ directory) │ │ (tesseract) │ ├──────────────────┤──▶ DjVu
└─────────┘ └─────────────────┘ └──────────────┘ │ djvulibre-bin │
├──────────────────┤──▶ TIFF
│ libtiff │
├──────────────────┤──▶ PNG, JPEG, PNM, GIF
│ imagemagick │
└──────────────────┘
Page numbers are always consecutive (1, 2, 3, …). Deleting a page renumbers the remainder automatically, and editing a page’s number in the document table moves that page to the corresponding position.
Single-sided: Each scan is appended at the end.
Double-sided: Scan all facing pages first (front 1, front 2, …, front n); they are appended in order. When you flip the stack the ADF feeds them in reverse (back of page n, then back of page n-1, …, back of page 1). Each reverse page is inserted immediately after its matching front page, producing a fully interleaved result: front 1, back 1, front 2, back 2, …, front n, back n.
scan pass 1 (fronts): front 1, front 2, ..., front n
flip stack
scan pass 2 (backs): back n, back n-1, ..., back 1
interleaved result: front 1, back 1, front 2, back 2, ..., front n, back nExtended mode (insert before page N): Each new scan is inserted before the selected page, advancing the insertion point for the next scan.
Automatic document feeder (ADF) and duplex scans now import every side of the document. Some Brother scanners (e.g. the DS-740D) prefetch the reverse side of a sheet as soon as the front side finishes reading; cancelling the scan session between pages discarded that buffered side, so only the first page of a duplex job was imported. Scantpaper no longer cancels the session between pages when scanning from a feeder, which preserves the prefetched side. The session is still cancelled at the end of the batch, when the requested page count is reached, on error, or when you cancel the scan.
For multi-page flatbed batches the behaviour follows gscan2pdf semantics: the Force new scan job between pages preference (enabled via Edit → Preferences, only available when Allow batch scanning from flatbed is enabled) controls whether the session is cancelled between flatbed pages; the session is always cancelled at the end of the batch. Switching to a flatbed source starts # Pages at one page. To scan an unbounded flatbed batch, choose All explicitly; the dialog then warns that scanning continues until you cancel. Cancelling stops the batch after the page already in progress. If the scanner does not respond to cancel, scantpaper reports the failure and rebuilds the device session for the next scan; a device still held by the operating system is reported as busy rather than leaving the application unable to scan.
When scanning a book destructively (cutting off the binding and aligning every untorn outer edge to the same rim), enable Alternate rotation every 2nd page in the Postprocessing tab. Odd pages then use the configured rotation angle, even pages use that angle plus 180 degrees, so the flipped back of each sheet comes out upright too. The toggle is only visible for flatbed scanning (including one-page-at-a-time manual scans) when batch scanning from the flatbed is allowed; rotation still runs before OCR. In manual (single-page) mode the alternating parity persists across consecutive scans until you explicitly turn the toggle off.
SANE backends sometimes list a webcam or another non-scanner as a
device. Select it in the Device list and click
Blacklist this device to hide it; the pattern is stored
in the Device blacklist field in Edit →
Preferences. That field also accepts a regular expression,
matched case-insensitively against the label shown in the list and the
device’s backend name, so patterns such as Camera or
v4l:.* work. If a pattern hides every device, scantpaper
reports that no devices were found rather than offering an empty
list.
115,2 mm in a German locale), show
them without trailing zeros (174 mm, not
174,00), and step by whole units with the arrows while
keeping the typed fraction. Only separators your locale actually uses
are accepted when typing numbers; a period in a comma locale is rejected
rather than silently misinterpreted. The same fractional entry applies
to the page-properties resolution fields and to the blank/dark-page and
unpaper white/black thresholds, so e.g. 300 dpi and
0,9 are shown exactly as typed.jbig2 encoder is installed. JPEG 2000 usually produces
smaller files than JPEG for scanned pages but is slower to encode and
less widely supported by viewers; JBIG2 typically halves the size of a
bilevel page compared with Group 4. scantpaper always uses JBIG2’s
lossless mode (see JBIG2 and the
Xerox character-substitution bug), so the saved bitmap is unchanged;
the exact output size depends on the installed encoder version, and like
G4 it thresholds grayscale and colour pages to bilevel first. The
Bilevel compression preference in Edit →
Preferences (Group 4 by default, JBIG2 when available) tells
Automatic how to encode bilevel pages, so a user who
never changes the preference keeps Group 4 output. The
Quality spin-button steers both JPEG and JPEG 2000, and
lower values shrink those streams. Unmodified imported JPEG pages are
embedded byte-for-byte, while pages that scantpaper generated by
scanning or editing are re-encoded at the configured quality, so
lowering Quality shrinks the PDF. A lossless encoding
(Automatic’s Group 4 or Flate handling, or the explicit
Flate option) is preserved through the PDF/A
conversion, so the page image is not silently transcoded to JPEG, on
every supported OCRmyPDF version. Saving to a file that already exists
asks for confirmation first: declining leaves the existing file
untouched and keeps the save dialog open, and a save that fails reports
the error instead of silently doing nothing. When saving as PDF, pages
are processed one at a time and page images are decoded on demand, so
saving a document with many high-resolution pages no longer keeps every
decoded page in memory and no longer exhausts the memory of the
process.gtk_css_node_insert_after
Gtk-CRITICAL that GTK 4.24 printed in its place on the first such
keystroke.JBIG2 is a format for bilevel images (ITU-T T.88 / ISO/IEC 14492), not a single compression mode. It defines lossless and lossy coding. In the lossy mode an encoder can replace similar-looking shapes with one shared symbol and discard the difference; if that matching is done badly, a 6 can decode as an 8. That is what happened in 2013 on some Xerox WorkCentre and ColorQube devices, whose firmware used lossy pattern matching and substitution. The fault was in the encoder, not in JBIG2 as a format; David Kriesel, who reported the bug, concluded that “the error cause is not JBIG2 itself”.
scantpaper always uses the lossless path. It converts only bilevel pages, one image at a time, with no shared symbol dictionary and no lossy substitution, and it never enables OCRmyPDF’s lossy JBIG2 option. The JBIG2 step therefore reproduces the bilevel bitmap exactly. OCRmyPDF 17 removed its lossy JBIG2 support for the same reason and now encodes each image independently and losslessly.
“Lossless” describes the compression step, not the whole scan. If you choose JBIG2 for a grayscale or colour page, scantpaper thresholds it to bilevel first, exactly as it does for Group 4, and that thresholding is where detail is lost. No compression setting can repair a scan that was already wrong.
PDF/A does not require lossless compression. ISO 19005 permits lossy compression, and PDF/A allows JBIG2 in both its lossless and lossy forms, so a PDF/A stamp is not evidence that a scan used the lossless mode. scantpaper’s PDF/A output is lossless because scantpaper asks for the lossless encoder path, not because PDF/A forces it. Some archival guidelines are stricter still: Germany’s BSI TR-03138 for replacement scanning forbids pattern matching and substitution, and also soft pattern matching; together those rule out JBIG2’s symbol-coding modes, including the lossy pattern-matching-and-substitution method behind the Xerox bug. That is a scanning policy, not a PDF/A rule, so check the recipient’s requirements if you scan for one.
Further reading:
| Action | Shortcut |
|---|---|
| New | Ctrl+N |
| Open | Ctrl+O |
| Scan | Ctrl+G |
| Save | Ctrl+S |
| Email as PDF | Ctrl+E |
| Ctrl+P | |
| Quit | Ctrl+Q |
| Undo | Ctrl+Z |
| Redo | Ctrl+Shift+Z |
| Cut | Ctrl+X |
| Copy | Ctrl+C |
| Paste | Ctrl+V |
| Delete | Del |
| Select | |
| All | Ctrl+A |
| Odd | Ctrl+1 |
| Even | Ctrl+2 |
| Invert | Ctrl+I |
| Blank | Ctrl+B |
| Dark | Ctrl+D |
| Modified | Ctrl+M |
| View | |
| Clear image selection | Esc |
| Zoom in | + |
| Zoom out | − |
| Rotate 90° clockwise | Ctrl+Shift+R |
| Rotate 180° | Ctrl+Shift+F |
| Rotate 90° anticlockwise | Ctrl+Shift+C |
| Help | Ctrl+H |
%i - input filename%o - output filename%r - resolutionBy default a user-defined tool is run once per selected page, with
%i (and %o/%r where used)
substituted for that page. Selecting “Process selected pages at once” in
the user-defined tools dialog instead passes every selected page to a
single invocation: %i expands to all the working files,
space-separated, so a tool that edits files in place (such as
mogrify -negate %i) can process the whole selection in one
go. Only the pages whose file actually changed are replaced, and the
whole batch is a single undo step, so one undo restores every page.
%o and %r are not available in batch mode; a
tool that uses them must be run one page at a time.
Tesseract can recognise several languages at once. In the scan window
and the Tools → OCR dialog, pick one Language
to recognise (the primary language) and tick any
Additional languages in the popover. The resulting
specification is stored in the ocr language setting as a
+-joined list, for example deu+eng. The
primary language is listed first, so put the main language of the page
first; tesseract prioritises the first language, and adding more
languages makes recognition slower and can reduce accuracy, so select
only the languages you need.
Next to the language, two selectors control how tesseract runs:
Both are stored and restored with the other OCR options. Do not use
tessedit_pageseg_mode or
tessedit_ocr_engine_mode in the advanced field instead:
those variables are set by these controls, so the field rejects
them.
The Advanced Tesseract engine parameters expander
accepts one name=value engine parameter per line, for
example:
user_words_file=/home/me/words.txt
tessedit_char_whitelist=0123456789
These are the values of tesseract’s -c option, passed
straight to the engine, and cover options that have no dedicated
control, such as a user word list
(user_words_file) or a user pattern list
(user_patterns_file). A leading -c is
optional, so -c user_words_file=/path works too.
Press Tab to leave the field for the next control, so OK (or Scan) stays reachable without the mouse. Ctrl+Tab and Ctrl+Shift+Tab move focus forward and backward too. A literal tab character is not needed.
Do not paste tesseract command-line options such as
--user-words: the field expects engine parameters, and the
error names the parameter to use instead:
| Command-line option | Engine parameter |
|---|---|
--user-words |
user_words_file |
--user-patterns |
user_patterns_file |
--dpi |
user_defined_dpi |
Lines beginning with # are comments; blank lines are
ignored; a value may itself contain =. Paths are not
expanded, so use absolute paths. A user_words_file or
user_patterns_file that does not exist or cannot be read is
reported before OCR starts, and the file paths that will be applied are
written to the log. Invalid or unknown options are reported before any
page is modified.
The Available parameters button below the field
opens a browser of the engine parameters that the installed Tesseract
reports, with each parameter’s name, default value, and description.
Nothing is bundled with the application, so the list follows the
installed engine. Type in the filter to narrow the list by name or
description; matching is case-insensitive, and a filter that matches
nothing says so. Select a parameter and press Enter or
Insert to write name= at the cursor in the
advanced field and return to the field with the cursor after the
=, ready for the value. If there is already text on the
current line, the new parameter starts on its own line.
tessedit_pageseg_mode and
tessedit_ocr_engine_mode stay visible but are annotated
with the control that sets them and cannot be inserted. If the list
cannot be read, the browser says so and OCR still runs with the
parameters already in the field.
The tessdata_best language models give more accurate OCR
but are larger and notably slower, and they only support the LSTM
engine. Point the Tessdata directory control (see
below) at their directory to use them; the TESSDATA_PREFIX
environment variable and replacing the system models also work.
The Tessdata directory control at the top of the OCR
options chooses the directory that holds the tesseract language data
(the .traineddata files). Leave it empty for the automatic
directory, shown in the entry as Default (system
tessdata); Browse picks a folder and
Default clears the choice.
The directory is resolved in this order:
TESSDATA_PREFIX environment variable, if it is
set;A chosen directory must directly contain at least one
.traineddata file. This also applies to
TESSDATA_PREFIX: point it at the directory that contains
the files, not at a parent such as /usr/share. A broad
value makes tesseract search the whole tree and hang, so it is rejected
with an error instead of being used. Nothing falls back to another
directory: a wrong path is reported before OCR starts.
Changing the directory rebuilds the language list from it. If the current language is not present in the new directory it stays selected and is marked (not installed), and OCR refuses to start until a language from that directory is chosen.
The selected directory is stored with the other OCR options and
restored on the next start. Tesseract 3’s m_data_sub_dir
parameter no longer exists in Tesseract 4 and later, so it is rejected
like any other unknown engine parameter.
scantpaper supports the following options:
--device <device> [...] Specifies the
device(s) to use, instead of getting the list from the SANE API. Useful
for remote scanners. May be repeated, or given multiple space-separated
devices.
--help
Displays help and exits.
--log=<log-file>
Specifies a file to store logging messages. On exit, the log is
compressed to <log-file>.xz.
--debug, --info, --warn,
--error, --fatal
Defines the log level. Defaults to --debug if a log file is
specified, otherwise --warn.
--import=<PDF|DjVu|images>
Imports the specified file(s). For multi-page documents, a window is
displayed to select required pages.
--import-all=<PDF|DjVu|images>
Imports all pages of the specified file(s).
--locale=<directory> Sets the directory
containing translated messages. See Translations.
--version
Displays the program version and exits.
$ scantpaper --version
scantpaper X.Y.Z(Replace X.Y.Z with your installed version.)
$ scantpaper --help
usage: scantpaper [-h] [--device DEVICE [DEVICE ...]]
[--import IMPORT_FILES [IMPORT_FILES ...]]
[--import-all IMPORT_ALL [IMPORT_ALL ...]] [--locale LOCALE]
[--log LOG] [--version] [--debug] [--info] [--warn]
[--error] [--fatal]
A GUI to produce PDFs or DjVus from scanned documents
options:
-h, --help show this help message and exit
--device DEVICE [DEVICE ...]
--import IMPORT_FILES [IMPORT_FILES ...]
--import-all IMPORT_ALL [IMPORT_ALL ...]
--locale LOCALE
--log LOG
--version show program's version number and exit
--debug
--info
--warn
--error
--fatal
Please see /usr/share/doc/C/scantpaper/documentation.html for more detail# Import every page of a PDF, letting you edit before saving
scantpaper --import-all ~/scans/document.pdf
# Import a PDF, choosing the pages to import in a dialog
scantpaper --import ~/scans/document.pdf
# Use a remote scanner
scantpaper --device "net:scanner.example.com:6566"Scanning is handled with SANE. PDF conversion uses
img2pdf and ocrmypdf. TIFF export uses
libtiff.
Saved PDFs are marked as created by scantpaper: the document info
Creator field and the XMP creator tool begin with
scantpaper v<version>, followed by the OCR toolchain
used, e.g.
scantpaper v3.0.16 / OCRmyPDF 16.13.0 / Tesseract OCR-hOCR 5.5.0.
The Producer field continues to name the library that wrote
the file.
To diagnose errors, start scantpaper from the command line with logging enabled:
PYTHONPATH=src python3 -m scantpaper.app --debugscantpaper creates a config file at
~/.config/scantpaperrc. The directory can be changed by
setting $XDG_CONFIG_HOME. Preferences are usually set via
Edit → Preferences.
If the config file cannot be read as JSON (for example after a
partial write or a hand edit), scantpaper rescues as many recognised
settings as it can and resets the rest to their defaults. The unreadable
original is kept as a backup at ~/.config/scantpaperrc.old,
and the start-up message tells you which settings were kept. Wrongly
typed values (e.g. a string where a number is expected) are corrected
when possible; each correction is written to the log rather than shown
in the start-up message. Only values that cannot be corrected are
reported. The original file is never silently overwritten: saving on
exit waits until the message has been acknowledged.
Scan options saved in the configuration file by gscan2pdf 2.x (whose
default-scan-options block has no frontend
list) are recognised and applied to the scan dialog on startup, and are
kept when the file is written back.
An unsaved session’s data (pages, edits, OCR, annotations) is stored
in an SQLite database in a temporary directory named
scantpaper-????????, created under $TMPDIR (or
/tmp) by default. The database is a sibling of that
directory: /tmp/scantpaper-????????.sdb, not a file inside
it. You can change the temporary location in Edit →
Preferences.
On a normal exit the temporary directory, the database and its sidecars are removed. If scantpaper crashes, they survive. The next start opens a Crashed sessions picker, and File → Open crashed session opens the same picker later. A snapshot that another instance is using is not listed.
The picker offers Restore, Discard and Cancel:
A snapshot whose latest action removed every page is still listed, because its undo history may contain recoverable pages. Restoring it opens an empty document; discarding it removes it.
To remove a leftover snapshot manually, quit scantpaper first and delete both the database and its directory:
rm -rf /tmp/scantpaper-???????? /tmp/scantpaper-????????.sdb*A session saved as an .sdb file (File → Save
As) can be reopened with File → Open. The
opened file itself becomes the working session: edits are written to it
as you work, so they persist even if you quit without saving the session
again, and quitting does not warn about unsaved pages. Saving over an
existing .sdb file asks for confirmation before the old
file is replaced; the replacement is atomic, so a failed save leaves the
previous file intact. Because the opened file is the source of truth, an
in-place session is not offered for restoration as a crashed session
after a crash.
To reuse corrected OCR when rebuilding a session from the original images, save the old session’s pages as hOCR, import the images into a new session in the same page order, and choose File → Import hOCR… for the selected pages or all pages. The import matches hOCR pages to pages by position, checks each page box against the target page’s pixel size, and replaces only the text layer as one undo step. Pages without OCR are exported as empty page boxes so they keep their position; importing an empty page box clears that page’s text layer. Annotations are not part of hOCR and are not transferred. The TIFF or other image files must be imported in the old session’s page order, because a wrong-order page of the same size cannot be detected from its page box.
A session with no recorded actions opens as an empty document, and a session whose latest action removed every page reopens empty. The first page scanned into a restored session is recorded as its first undo step.
Package names below are the Debian package names. Equivalent packages for other distributions are given in the wheel file installation instructions.
uv, but must be installed manually when running from a
tarball or the repository.Ubuntu 22.04 (jammy), with GTK 4.6 and PyGObject 3.42, is supported until 2027-06. Ubuntu 24.04 (noble) and newer toolchains are also supported.
Debian sid should automatically have the latest
version.
sudo apt update
sudo apt install scantpaperUbuntu users can use the PPA:
sudo apt-add-repository ppa:jeffreyratcliffe/ppa
sudo apt update
sudo apt install scantpaperIn either case to remove scantpaper afterwards:
sudo apt remove scantpaperDownload the RPM files from Github and install them all in a single transaction:
sudo dnf install *.rpmThe release provides scantpaper together with locally rebuilt
versions of python3-sane, python3-tesserocr,
python3-cysignals and python3-iso639 (packaged
as RPMs because the Fedora versions do not satisfy scantpaper’s
dependencies, and with a python3-sane patch for Epson
epsonscan2 scanners). scantpaper requires these packages by
their python3dist(...) names, so they must be installed in
the same transaction: installing scantpaper first fails with
nothing provides python3.14dist(...).
To remove scantpaper afterwards:
sudo dnf remove scantpaperWeak dependencies such as qpdf are installed by default,
but can be skipped with --setopt=install_weak_deps=0, in
which case the optional features they provide (e.g. PDF encryption) are
disabled.
Download the Leap 16.0 RPM files - the ones with the
.lp160.1 release suffix - from Github
into a directory of their own and install them all in a single
transaction:
sudo zypper install --allow-unsigned-rpm *.rpmThe release provides scantpaper together with locally rebuilt, Leap
16.0 versions of the Python dependencies that the distro does not
package (python313-sane, python313-tesserocr,
python313-cysignals, python313-python-iso639,
python313-pdfminer.six, python313-img2pdf,
python313-ocrmypdf, python313-setuptools-scm
and python313-hatch-vcs, with a python313-sane
patch for Epson epsonscan2 scanners).
Installing the set replaces any same-named packages you already have
from repo-oss or from a third-party repository such as
devel:languages:python. That is intended: the rebuilt
packages carry fixes the repository builds do not, the
python313-sane snap() patch being the
important one. Three of them are versioned so that a later system update
keeps your build instead of reverting it to the distribution’s.
To remove scantpaper afterwards:
sudo zypper remove scantpaperThe RPMs are unsigned (the workflow builds them from source), hence
the --allow-unsigned-rpm. That is the only flag needed.
PostScript export needs tiff2ps, which is not packaged on
Leap 16.0, so the PostScript save type is not available there.
Download the Tumbleweed RPMs - the files without the
.lp160.1 release suffix - into a directory of their own and
install them in one transaction:
sudo zypper install --allow-unsigned-rpm *.rpmOnly the patched python-sane is rebuilt; every other
Python dependency comes from the Tumbleweed repositories. The patched
package replaces Tumbleweed’s unpatched 2.9.2 build, so
Epson epsonscan2 scanners get the snap() fix.
A later Tumbleweed python-sane release will sort above it
until the fix is upstreamed. PostScript export has the same limitation
as Leap: tiff2ps is not packaged on Tumbleweed
(tiff 4.7.2), so the PostScript save type is not
available.
Download .whl from Github.
# Install the C-libraries that pip cannot handle:
# For Debian/Ubuntu
sudo apt update
sudo apt install libgirepository-2.0-dev libcairo2-dev libsane-dev pkg-config python3-dev gir1.2-glib-2.0
# For Fedora
sudo dnf install gobject-introspection-devel cairo-devel sane-backends-devel pkgconf-pkg-config python3-devel
# For openSUSE
sudo zypper install gobject-introspection-devel cairo-devel sane-backends-devel pkgconf-pkg-config python3-devel
# For Arch
sudo pacman -S gobject-introspection cairo sane pkgconf python
# For Homebrew
brew install pygobject3 gobject-introspection cairo sane-backends pkg-config
# Possibly upgrade pip
python3 -m pip install --upgrade pip
# Install from the wheel file, automatically including python dependencies
pip install scantpaper-x.x.x-py3-none-any.whlIf you haven’t already, you will then probably have to add
~/.local/bin to your path in order to find the new
executable, after which you can start it with:
scantpaperTo then remove it:
pip uninstall scantpaperuvTo install the runtime dependencies with uv:
uv syncor with the additional development dependencies:
uv sync --extra testAfter which you can start it with:
uv scantpaperDownload .tar.gz from Github.
After installing the dependencies listed
above:
tar xvfz scantpaper-x.x.x.tar.gz
cd scantpaper-x.x.x
PYTHONPATH=src python3 -m scantpaper.appBrowse the code at Github. After installing the dependencies listed above:
git clone https://github.com/carygravel/scantpaper.git
cd scantpaper
PYTHONPATH=src python3 -m scantpaper.appIn either of the above two cases, just delete the source directory to remove it.
scantpaper --log=log
with your report. On exit the log is compressed to log.xz,
so submit that file.scantpaper is partly translated into several languages. We’d like to
organise the translations on Launchpad
Rosetta — but if that’s too much effort for you, attaching a
.po file to an issue or a
merge request is just as welcome.
Missing strings are seeded as #, fuzzy (needs review)
entries and fall back to English until a translator confirms them, so
only confirmed translations reach users. The catalogs live in the
po/scantpaper/ template directory, one
po/scantpaper/<language>.po per language next to the
generated po/scantpaper/scantpaper.pot — the layout Rosetta
expects, so the directory can be tarballed and uploaded as-is.
To test updated .po files:
python3 dev/compile_mo.py --src po/scantpaper --out src/scantpaper/locale --domain scantpaper
PYTHONPATH=src python3 -m scantpaper.app --log=log --locale=localeSet locale variables as needed (e.g., for Russian):
LC_ALL=ru_RU.utf8 LC_MESSAGES=ru_RU.utf8 LC_CTYPE=ru_RU.utf8 LANG=ru_RU.utf8 LANGUAGE=ru_RU.utf8 PYTHONPATH=src python3 -m scantpaper.app --log=log --locale=localepython3 dev/check_launchpad.pyreports whether anything has moved on Launchpad since
the last sync, by comparing the current state of the translation
template and of every language against the committed baseline
po/launchpad-state.json. Two triggers are watched: the
template (the message set / .pot), read from the Launchpad
JSON API with a conditional request, and each language’s last-changed
time, which exists only on the public series translation page. A
language is reported when its last-changed time is newer than the
baseline, when a new locale appears, or when the template changes.
It also reports the reverse direction: whether the local template
generated from source is stale relative to the last pot uploaded to
Launchpad. The local message set is compared against the
uploaded_pot fingerprint recorded in the baseline; a
difference (new, removed or changed strings) is reported as
Local pot is stale: naming the added and removed strings,
and the exit code is unaffected unless --fail-on-change is
given.
The check is advisory by default: it exits successfully whether or
not it found changes, and it never downloads a catalog (Launchpad
requires a login to export one). --fail-on-change makes a
detected change non-zero for CI. The one always-non-zero outcome is a
state the check could not read, so a broken check is loud rather than
mistaken for “nothing new”. The committed baseline is advanced
deliberately, after the catalogs have been synced:
python3 dev/check_launchpad.py --updateAfter a new template has been uploaded to Launchpad, record its message set so the next run no longer reports the pot as stale:
python3 dev/check_launchpad.py --record-potThe translations-check
workflow runs the same check daily and on demand
(workflow_dispatch), and files one labelled issue when it
detects upstream changes and a separate one when new strings are ready
to upload.
python3 dev/check_po.pyThis needs xgettext, msgcat and
msgfmt on PATH. intltool is not
needed: the translatable strings in the .ui files are
extracted with the GtkBuilder ITS rules vendored in dev/,
so the template does not depend on the gettext version installed. CI
runs the same script via test_po_files.py.
dev/check_po.py compares those vendored rules against the
copies gettext installed and reports any drift as an advisory.
Errors fail the script; advisories are printed but do not. A release is therefore never blocked by an advisory, but they are worth reading.
The hard checks catch defects that break a catalog or a translation:
a bad format specifier, an unbalanced or renamed placeholder, a plural
entry with the wrong number of forms, a Plural-Forms header
that disagrees with CLDR, an obsolete-entry count over the catalog’s
ceiling, and counted strings written as %(plural)s rather
than ngettext.
The advisories fall into two groups. Source-side ones report strings
that are hard to translate, so they are best fixed by rewording: a
concatenation fragment that fixes English word order into a translatable
string, a msgid differing from another only by case, a doubled space, a
msgid over 200 characters, and a μs written with GREEK
SMALL LETTER MU. Catalog-side ones report mistranslations that only a
native speaker can judge: a msgstr copied verbatim from a
different msgid (the signature of a bad fuzzy match), and two sibling
controls such as Select Odd and Select Even
sharing one translation.
dev/check_po.py has a --pot option that
points it at an existing template instead of generating one, which the
test suite uses to exercise the source-side checks.
It may not be supported by SANE or your scanner. If you see it in
scanimage --help but not in scantpaper, send the output to
the maintainer.
Enable “Allow batch scanning from flatbed” in Preferences. Some scanners require additional settings. Switching to the flatbed starts # Pages at one page; choose a finite count, or choose All to scan until you cancel.
Cut off the binding and scan every sheet one at a time, always aligning the untorn outer edge to the same rim. Scan in landscape to save time, set the rotation for the front pages in the Postprocessing tab, then tick Alternate rotation every 2nd page so the backs (which are rotated 180 degrees relative to the fronts) can be scanned too without manual rotation. For one-page-at-a-time flatbed scanning, the toggle remains active across consecutive scans so every second scan is flipped automatically; turn it off to reset parity.
The required package may not be installed (e.g., xdg-email, unpaper, imagemagick).
Set “# Pages” to “1” and “Batch scan” to “No”.
Only changelogs from official Ubuntu builds are shown.
If the scanner is remote and not found automatically, specify the device:
scantpaper --device <device>Use pdftotext or djvutxt to extract text.
Many viewers support searching the embedded text layer.
The selection rectangle is styled with the .rubberband
CSS class. scantpaper ships a default rule for it that follows the
active GTK theme, so the selection is visible out of the box. To use a
colour of your own, create or edit
~/.config/gtk-4.0/gtk.css (a user stylesheet overrides the
application default):
.rubberband {
border: 1px solid #2a76c6;
background-color: rgba(42, 118, 198, 0.2);
}
#scantpaper-ocr-output {
color: black;
}“scant” (https://en.wiktionary.org/wiki/scant) in this sense means “short (of)”, as I am trying to digitalise my paperwork, and I liked the play on “scan”.
Saving more than approximately 250 uncompressed scanned pages (at 300 dpi, 8-bit grayscale) produces a PDF exceeding 2 GiB. At that size, three tools in the save pipeline overflow 32-bit file offsets and produce truncated or corrupt output:
| Component | Tested version | Overflow point | Symptom |
|---|---|---|---|
| img2pdf (pikepdf engine, linearization) | 0.6.2 | 32-bit xref offsets in linearized output | Truncated PDF; first pages readable, later pages missing |
| Ghostscript (PDF/A conversion via ocrmypdf) | 10.07.1 | 32-bit file access in gs interpreter | Ghostscript error or corrupt output |
| pikepdf / qpdf (xref-stream linearization, metadata save) | pikepdf 10.5.0, qpdf 12.4.0 | 32-bit offsets in xref streams | “unable to find /Root dictionary”; PDF unopenable |
Scantpaper now estimates the output size before conversion and refuses to save when it would exceed 2 GiB, showing an error message suggesting fewer pages.
When updating dependencies, re-test by saving ~250 high-resolution uncompressed pages (e.g., 7000×5000 px grayscale TIFFs) and verifying the output PDF opens correctly in a PDF viewer. Note that Ghostscript’s 64-bit integer support (needed for >2 GiB files) is build-dependent — see the Ghostscript documentation on word size.
I started writing gscan2pdf as a Perl & Gtk2 project
in 2006. Version 2 switched to Gtk3, but kept the basic software
architecture. This stored the pages as temporary files with hashed
names, which had a couple of major disadvantages:
Therefore I decided in 2022 to completely rewrite
gscan2pdf in Python and renamed it for v3
scantpaper. The rewrite:
ocrmypdf to write PDFsSee also the changelog for detailed release notes.
Jeffrey Ratcliffe (jffry at posteo dot net)
img2pdf and ocrmypdf,
without which this would have been much harder.See contributing.
Copyright © 2006–2026 Jeffrey Ratcliffe jffry@posteo.net
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.