XiKey™DESKTOP
العربيةDownloads

Learn

Command Line: xipdf-cli

xipdf-cli is XiKey PDF without the window. It reads, renders, searches, edits, splits, checks and OCRs PDF files from a terminal, a script, a server or a CI job, using the same engine as the app. It never opens a window, needs no display and sends nothing to the internet.

Every example on this page was run on Windows x64 with the current XiKey PDF 0.8.0 build, against the fictional demo files that XiKey made for this guide: an Arabic annual report for a date-palm research centre (annual-report-ar.pdf, 6 pages with charts), a one-page scan of it (annual-report-ar-scanned.pdf), a scanned library notice (ar-scanned.pdf), a bilingual brochure (brochure-bilingual.pdf) and an Arabic/English VAT invoice (invoice-ar.pdf). Output is shown exactly as printed, shortened with "…" where it is long. In XiKey PDF 0.8.0 r1, help text and error messages may name the program differently; the commands and options are the same.

When to use it

  • Batch OCR of Arabic scans: turn a folder of scanned letters, invoices or archive pages into searchable PDFs overnight.
  • Text for search and indexing: pull the text of every PDF into .txt files for a search engine, a database or an AI pipeline.
  • Servers and scripts: split, combine, extract, rotate and retitle documents as part of another program.
  • CI and release checks: fail a build when a PDF cannot be opened or rendered, or when a "redaction" is only a black box over text that is still there.
  • Automation and AI agents: call any of the app's automation tools (run), run a JSON script of steps (run --script), serve the tools to an AI assistant over MCP (mcp), or drive a running app window (ui).

Where it is after installation

Platform Package Where xipdf-cli is
Windows x64 MSI installer (the file XiKey Launcher installs) C:\Program Files\XiKey PDF\xipdf-cli.exe, next to XiKeyPDF.exe
Windows x64 Portable zip XiKey-PDF-<version>-windows-x64-portable.zip In the unzipped folder, next to XiKeyPDF.exe
macOS Separate zip XiKey-PDF-cli-<version>-macos-<arch>.zip xipdf-cli at the top of the unzipped folder
Linux DEB or RPM package /usr/bin/xipdf-cli
Linux XiKey-PDF-cli-<version>-linux-<arch>.tar.gz xipdf-cli at the top of the unpacked folder
Linux AppImage Inside the image at usr/bin/xipdf-cli; running the AppImage itself starts the app

What is published today: the beta channel on download.xikey.com carries XiKey PDF 0.8.0 r1 as the Windows x64 MSI, the macOS x64 and arm64 DMGs and the Linux x64 AppImage. The portable zip, the DEB and RPM packages and the two CLI-only archives are produced by the release recipe but are not on the beta channel yet. The macOS app bundle in the DMG does not contain the command-line tool; on macOS it comes only in the CLI zip.

The Windows installer does not add its folder to PATH. Call the tool by its full path, or add the folder for the current session:

& "C:\Program Files\XiKey PDF\xipdf-cli.exe" --version
$env:PATH = "C:\Program Files\XiKey PDF;$env:PATH"    # this PowerShell session only
xipdf-cli --version

Behaviour common to every command

  • Exit codes. 0 means success and 1 means failure; there are no other codes. Some commands fail on purpose so that scripts can react: audit exits 1 when it finds a faux redaction, batch exits 1 when any file failed, text exits 1 when any page could not be read (after printing the pages it could). check is the exception: once it has run it exits 0 even when files fail, so read its JSON report (see the CI recipe below).
  • Errors go to standard error as one line prefixed with the program name, for example xipdf-cli: the password is incorrect. Standard output carries only results, so it is safe to redirect or parse.
  • Output formats. info, tools, run, batch and ui print indented JSON. ocr and audit print one line of JSON. text prints plain UTF-8 text. split prints the files it wrote, one per line. render, edit, combine and extract print nothing on success (render reports on standard error). Closing the output early, as in xipdf-cli tools | head -1, still exits 0.
  • Options are checked before any file is read. A misspelled option fails at once instead of being ignored: xipdf-cli info samples/invoice-ar.pdf --pasword x gives xipdf-cli: info: unknown option --pasword.
  • Page numbers start at 1. Lists are comma-separated (1,3,5); ranges such as 2-4 are refused with bad page number "2-4".
  • Passwords. info, text, render, ocr, edit, extract, split and audit accept --password PW (the open password, or the owner password where permissions matter). combine has no password option. A password typed on the command line can end up in shell history and process lists; prefer reading it from a protected variable in scripts.
  • Existing files. render, edit, combine, extract and split overwrite their output. ocr refuses to overwrite (ocr: output already exists; choose a new file). batch never replaces a file; it adds a number to the name instead.
  • Help. xipdf-cli --help (also -h, help, or xipdf-cli <command> --help) prints the full usage and exits 0. Running xipdf-cli with no command prints the usage as an error and exits 1.

Commands

info: what is in a document

xipdf-cli info <file.pdf> [--password PW]

Prints a JSON summary: PDF version, file size, page count, the first page's size in points, title, author, producer and creator, whether the file is encrypted or tagged, JavaScript, XFA forms, and counts of bookmarks, annotations, form fields, links, layers and attachments, plus the first eight page labels and any warnings.

$ xipdf-cli info samples/annual-report-ar.pdf
{
  "annotations": 0,
  "attachments": 0,
  "author": null,
  "bookmarks": 0,
  "creator": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Edg/155.0.0.0",
  "encrypted": false,
  "fields": 0,
  "file": "samples/annual-report-ar.pdf",
  "file_size": 1658510,
  "first_page_pt": [
    594.9599609375,
    841.9199829101562
  ],
  "javascript": false,
  "layers": 0,
  "links": 0,
  "page_labels": [
    "1",
    "2",
    "3",
    "4",
    "5",
    "6"
  ],
  "pages": 6,
  "pdf_version": "1.4",
  "producer": "Skia/PDF m155",
  "tagged": true,
  "title": "مركز نخلة الواحة لأبحاث التمور — التقرير السنوي ٢٠٢٥",
  "warnings": [],
  "xfa": null,
  "xfa_layout": null
}

Exit code 0. A page of 595 × 842 points is A4.

text: extract the text

xipdf-cli text <file.pdf> [--page N] [--password PW]

Prints the text in reading order. Without --page it prints every page, with a form-feed character (U+000C) on its own line between pages. Arabic comes out in logical order, ready to search or paste.

$ xipdf-cli text samples/annual-report-ar.pdf --page 2
مركز نخلة الواحة لأبحاث التمور
الملخص التنفيذي
EXECUTIVE SUMMARY · ٠١
الملخص التنفيذي
شهد عام ٢٠٢٥ تحوّلًا نوعيًا في عمل المركز؛ إذ انتقلنا من التجارب الحقلية المحدودة إلى برامج إنتاج واسعة تخدم المزارعين في أربع مناطق
زراعية، وركّزنا جهودنا على ثلاثة محاور: تحسين الأصناف، وترشيد مياه الري، ونقل المعرفة إلى المزارع الصغيرة.
٢٤٠ ألف
٢٢٪
…

Exit code 0. A page that does not exist fails:

$ xipdf-cli text samples/annual-report-ar.pdf --page 9
page 9: page 9 does not exist
xipdf-cli: text: page(s) 9 could not be read

Exit code 1. A scanned page has no text layer, so text prints an empty line for it; run ocr first. If a document's permissions forbid copying, text refuses and asks for the owner password with --password.

render: a page as an image

xipdf-cli render <file.pdf> --out FILE [--page N] [--dpi 96] [--password PW]
Option Meaning Default
--out Output image; the extension picks the format: .png, .jpg/.jpeg (quality 90), .tif/.tiff, or .pam (raw RGBA) required
--page Page to render 1
--dpi Resolution in dots per inch 96
$ xipdf-cli render samples/annual-report-ar.pdf --page 3 --dpi 150 --out out/report-p3.png
rendered page 3 at 150 dpi: 1240×1754 px in 13 ms

Exit code 0. The PNG is 1240 × 1754 pixels and 253,593 bytes; the Arabic text, the bar chart and the Arabic-Indic digits render as on screen. At the default 96 dpi, xipdf-cli render samples/invoice-ar.pdf --out out/invoice.jpg gives a 794 × 1123 pixel JPEG of 202,533 bytes. Other extensions are refused (render: --out out/invoice.bmp: use a .png, .jpg, .tif or .pam name), and so is a resolution beyond the renderer's limit: 2000 dpi on an A4 page fails with … exceeds renderer limits (maximum scale 9.730141), which is about 700 dpi.

ocr: make scans searchable

xipdf-cli ocr <in.pdf> --out out.pdf [--lang ara+eng] [--dpi 300] [--pages 1,3] [--all-pages] [--password PW]
Option Meaning Default
--out New searchable PDF; must not exist yet required
--lang (or --language) Tesseract languages: ara, eng or ara+eng your default from Preferences → OCR, otherwise eng
--dpi Resolution pages are read at: 200, 300 or 400 Preferences → OCR, otherwise 300
--pages Only these pages all pages
--all-pages Also read pages that already have reliable text off

OCR adds an invisible text layer over the unchanged page image. The output is written to a temporary file and moved into place only when complete. For Arabic documents, always pass --lang ara+eng.

$ xipdf-cli ocr samples/ar-scanned.pdf --lang ara+eng --out out/ar-searchable.pdf
{"growth_percent":2.5210255990505193,"input_bytes":146607,"output":"out/ar-searchable.pdf","output_bytes":150303,"pages":[{"low_confidence":false,"mean_confidence":89.24462890625,"page":1,"skipped":null,"words":56}],"seconds":1.0661024000000001,"seconds_per_page":1.0661024000000001,"words":56}

Exit code 0: 56 words in about a second, mean confidence 89 out of 100, and the file grew by 2.5 %. The result now has Arabic text:

$ xipdf-cli text out/ar-searchable.pdf
مواعيد مكتبة الحى
تفتح المكتبة أبوابها من الساعة التاسعة صباحا حتى الخامسة elas
ويمكن للأعضاء استعارة BW كتب Baa) أسبوعين.
قاعة المطالعة هادئة ومكيفة, وتتوفر فيها خدمة Wi-Fi مجانية, ولا
يسمح بإدخال الطعام إليها.
آخر موعد لإعادة الكتب قبل الإغلاق بنصف ساعة.
…

Recognition is good but not perfect: in this scan a few words came out as Latin letters (elas, BW, Baa)). Check names, numbers and dates in anything important.

The JSON reports, per page: words, mean_confidence (0 to 100), low_confidence (true when the mean is below 70 or a number was read with confidence below 60, a sign to check the page), and skipped with the reason when a page was left alone. The one-page scan of the annual report, which mixes charts and text, gives 312 words with "low_confidence":true and a mean confidence of 73.5: usable for search, but worth checking.

What the language choice does:

$ xipdf-cli ocr samples/ar-scanned.pdf --out out/ar-default.pdf
{… "pages":[{"low_confidence":true,"mean_confidence":29.60999298095703,"page":1,"skipped":null,"words":60}] …}
$ xipdf-cli text out/ar-default.pdf
> cso seglo
elas duaBl Lis loo deli] dell yo Lgoolldeisel] pay
…

Without --lang and without a default language in Preferences, the command line reads English (eng), and an Arabic page comes out as Latin letters with a confidence of 30. --lang ara alone works for pure Arabic pages; ara+eng also keeps English words, codes and Latin digits.

edit: rotate, delete, move, insert, retitle

xipdf-cli edit <in.pdf> --out out.pdf [--rotate PAGES:DEGREES] [--delete PAGES] [--move PAGES:TO]
               [--insert-blank N] [--title T] [--author A] [--full] [--password PW]
Option Meaning
--rotate 1,3:90 Rotate pages clockwise by a multiple of 90 degrees
--delete 2,4 Delete pages
--move 5:1 Move pages so they start at position 1
--insert-blank 1 Insert a blank US Letter page (612 × 792 points) so it becomes page 1
--title, --author Set the document title and author
--full Write a complete new file instead of appending the changes to the original bytes (the default)

Options run in the order you give them, and each page number refers to the document as the earlier options left it. Options can repeat.

$ xipdf-cli edit samples/annual-report-ar.pdf --out out/report-edited.pdf --rotate 3:90 --delete 6 --move 5:1 --insert-blank 1 --title "التقرير السنوي ٢٠٢٥ — نسخة المراجعة" --author "قسم الاتصال المؤسسي"
$ xipdf-cli info out/report-edited.pdf
…
  "author": "قسم الاتصال المؤسسي",
…
  "pages": 6,
…
  "title": "التقرير السنوي ٢٠٢٥ — نسخة المراجعة",
…

Both exit 0. Step by step: page 3 is rotated; page 6 (the outlook) is deleted, leaving 5 pages; page 5 (the programmes chapter) moves to the front; a blank page is inserted before everything. Page 1 is now blank and page 2 starts with البرامج والمشروعات. The default save appends to the original (1,667,248 bytes); --full rewrites the file (1,456,269 bytes for a retitled copy).

Mistakes fail before anything is written:

$ xipdf-cli edit samples/annual-report-ar.pdf --out out/x.pdf --rotat 3:90
xipdf-cli: edit: unknown option "--rotat" (expected one of: --out, --password, --full, --rotate, --delete, --move, --insert-blank, --title, --author)
$ xipdf-cli edit samples/annual-report-ar.pdf --out out/x.pdf --delete 7
xipdf-cli: --delete: page 7 does not exist; after the earlier options the document has 6 pages
$ xipdf-cli edit samples/invoice-ar.pdf --out out/x.pdf --rotate 1:45
xipdf-cli: rotation must be a multiple of 90 degrees

combine: join files

xipdf-cli combine <a.pdf> <b.pdf> … --out combined.pdf

Joins two or more PDFs in the order given. Each source file gets a top-level bookmark named after it; links, form fields, layers and attachments come along.

$ xipdf-cli combine samples/invoice-ar.pdf samples/brochure-bilingual.pdf samples/annual-report-ar.pdf --out out/combined.pdf
$ xipdf-cli info out/combined.pdf
…
  "bookmarks": 3,
…
  "pages": 9,
…

Exit code 0: 1 + 2 + 6 pages and three bookmarks. A single input fails with combine: give at least two input files.

extract: copy pages into a new file

xipdf-cli extract <in.pdf> --pages 1,3,5 --out out.pdf [--password PW]
$ xipdf-cli extract samples/annual-report-ar.pdf --pages 1,3,5 --out out/report-highlights.pdf

Exit code 0; the new file has 3 pages. Pages keep the order you list, so --pages 6,1 puts the outlook before the cover.

split: one file into several

xipdf-cli split <in.pdf> (--every N | --before PAGES) [--out-dir DIR] [--password PW]

--every N starts a new part every N pages; --before 3,7 starts a new part at each listed page. Give exactly one of them. Parts are named <name>-p<first>-<last>.pdf (or -p<n>.pdf for one page) in --out-dir (default: the current folder, created if missing), and each written path is printed.

$ xipdf-cli split samples/annual-report-ar.pdf --every 2 --out-dir out/split-every
out/split-every\annual-report-ar-p1-2.pdf
out/split-every\annual-report-ar-p3-4.pdf
out/split-every\annual-report-ar-p5-6.pdf
$ xipdf-cli split samples/annual-report-ar.pdf --before 2,6 --out-dir out/split-chapters
out/split-chapters\annual-report-ar-p1.pdf
out/split-chapters\annual-report-ar-p2-5.pdf
out/split-chapters\annual-report-ar-p6.pdf

Both exit 0. Giving both options fails with split: give either --every N or --before PAGES.

check: can every file be opened and rendered?

xipdf-cli check <files or folders…> [--timeout 20] [--dpi 36] [--json report.json]

Finds every .pdf in the files and folders given (folders are searched recursively), then opens, inspects and fully renders each one (up to 500 pages, both pixels and text) in a separate process with a time limit, so a file that hangs or crashes is reported instead of stopping the run.

Option Meaning Default
--timeout Seconds allowed per file 20
--dpi Render resolution for the check 36
--json Write the full report to this file none

Problems and a summary go to standard error:

$ xipdf-cli check samples
checked 5 files: {"ok": 5}
$ xipdf-cli check out/ci --json out/ci-check.json
[1/2] open-error:the-file-is-not   out/ci\broken.pdf  the file is not a readable PDF: no readable page tree or cross-reference data was found

checked 2 files: {"ok": 1, "open-error": 1}

Both exit 0, even the second (here broken.pdf is a text file renamed to .pdf). The report lists every file:

[
  {
    "detail": "the file is not a readable PDF: no readable page tree or cross-reference data was found",
    "failed_pages": 0,
    "file": "out/ci\\broken.pdf",
    "ms": 0,
    "pages": 0,
    "status": "open-error:the-file-is-not",
    "warnings": 0
  },
  {
    "detail": "",
    "failed_pages": 0,
    "file": "out/ci\\invoice-ar.pdf",
    "ms": 12,
    "pages": 1,
    "status": "ok",
    "warnings": 0
  }
]

status is ok, page-errors (some pages failed; detail says which), open-error:<reason>, timeout, crash or harness-error. check exits 1 only when it cannot run at all, for example check: no PDF files found.

audit: find fake redactions

xipdf-cli audit <file.pdf> [--password PW]

A faux redaction hides text only visually: a black rectangle drawn over text that can still be selected, copied or extracted. audit reports two kinds:

  • covered-text: an opaque filled rectangle, dark enough to make the text unreadable, painted after the text it covers;
  • unapplied-mark: a redaction mark that was placed but never applied, so the text underneath is untouched.

It prints JSON and exits 1 when it finds anything, so a script or CI job can stop on it. Real redaction, which removes the content, never triggers it. The audit also fails when it cannot read part of the file, rather than reporting "nothing found". Its known blind spots: covers drawn as arbitrary shapes, shadings or patterns, semi-transparent covers, text inside form XObjects, and pages with /Rotate may be missed or misplaced.

A clean file passes:

$ xipdf-cli audit samples/invoice-ar.pdf
{"count":0,"file":"samples/invoice-ar.pdf","findings":[]}

Exit code 0. A copy of the invoice where the seller's VAT number was only marked for redaction (made with the run --script example below, without redact_apply) fails:

$ xipdf-cli audit out/invoice-marked-only.pdf
{"count":2,"file":"out/invoice-marked-only.pdf","findings":[{"covered_text":"VAT 310987654300003","kind":"unapplied-mark","page":1,"rect":[55.2976188659668,763.0709838867188,127.25257873535156,771.0659790039062]},{"covered_text":"310987654300003","kind":"unapplied-mark","page":1,"rect":[427.0898132324219,624.9105224609375,495.1648254394531,633.7080078125]}]}
xipdf-cli: audit: 2 faux redaction(s) found

Exit code 1. The same copy after redact_apply passes with "count":0.

batch: convert, OCR and optimize a whole folder

xipdf-cli batch <files or folders…> --steps convert,ocr,optimize --out-dir DIR
               [--recursive] [--root DIR] [--stop-on-error]
               [--ppi 150] [--quality 60] [--dpi 300] [--language en] [--ocr-all-pages]

Runs one to three steps on every file, one file at a time, and writes each result into --out-dir with the same relative path and a .pdf extension. The sources are never changed and an existing output is never replaced (a numbered name such as report (2).pdf is used instead).

Step What it does Its options
convert Turns images (png, jpg, jpeg, tif, tiff, gif, bmp, jp2, j2k, jpx) and text files (txt, text) into PDF. Must be the first step. Without it, only PDFs are processed and other files are skipped. none
ocr Adds a text layer with the built-in English (Latin-script) recognizer --dpi (72–600, default 300), --language (only en), --ocr-all-pages
optimize Reduce File Size: downsamples images above 1.5 × the target and recompresses them as JPEG; keeps the original bytes if the result is not smaller --ppi (9–1600, default 150, so images above 225 ppi), --quality (1–100, default 60)

Other options: --recursive also descends into subfolders (off by default), --stop-on-error skips the remaining files after the first failure, and --root DIR confines every path to that folder. Discovery is limited to 10,000 entries and 64 folder levels, inputs to 512 MiB, and links and special files are skipped. The command prints a JSON report and exits 1 if any file failed.

$ xipdf-cli batch samples --steps optimize --ppi 100 --quality 50 --out-dir out/batch-small
{
  "completed": 5,
  "failed": 0,
  "files": [
    {
      "bytes": 69075,
      "output": "\\\\?\\V:\\…\\out\\batch-small\\annual-report-ar-scanned.pdf",
      "path": "\\\\?\\V:\\…\\samples\\annual-report-ar-scanned.pdf",
      "status": "ok",
      "steps": [
        {
          "bytes_after": 69075,
          "bytes_before": 330442,
          "step": "optimize"
        }
      ]
    },
    …
  ],
  "skipped": 0,
  "stopped": false
}

Exit code 0. The scanned report shrank from 330,442 to 69,075 bytes and the library scan from 146,607 to 27,919. Converting a folder of images works the same way: xipdf-cli batch out/images --steps convert,optimize --out-dir out/batch-converted turned invoice.jpg into invoice.pdf (exit 0).

Important for Arabic: the ocr step of batch uses only the built-in English recognizer. --language ara+eng is refused (unsupported language "ara+eng"), and on an Arabic scan the step reports success while producing Latin-letter noise (283 "words" on the scanned report). For Arabic scans, use the ocr command in a loop, as in the recipe below. The built-in recognizer's models ship with the installed app; without them the step fails with the text recognition models are not installed (reinstall XiKey PDF, …).

Settings that need a missing step are refused, for example --dpi without ocr: dpi/language/skip_text_pages settings need the ocr step.

tools and run: every automation tool from the command line

xipdf-cli tools
xipdf-cli run <tool> [key=value …] [--root DIR] [--out image.png]
xipdf-cli run --script steps.json [--root DIR]

tools prints every automation tool as JSON: name, description, read_only, the app command it matches, and its input_schema (JSON Schema). This build has 163 tools, from doc_open, text_find and page_render to redaction, forms, signatures, comments, accessibility, PDF/A and comparison.

run calls one tool. Each key=value becomes an argument; the value is read as JSON when it can be (pages=[1,3], degrees=90, full=true) and as a string otherwise. A tool that returns an image saves it to --out. Each run is a fresh session, so a single call suits tools that take file paths:

$ xipdf-cli run doc_combine 'paths=["samples/invoice-ar.pdf","samples/brochure-bilingual.pdf"]' out=out/run-combined.pdf
{
  "bytes": 231589,
  "path": "out/run-combined.pdf"
}

Exit code 0. Tools that work on an open document (doc) need a script, because a document id lives only for one session.

run --script runs a JSON array of steps in one session. Each step is {"tool": …, "args": {…}}, plus "out" for an image. The first opened document is id 1, the next id 2, and so on. This script finds the invoice's tax-number labels, removes both VAT numbers for good, saves a copy and renders it:

[
  {"tool": "doc_open", "args": {"path": "samples/invoice-ar.pdf"}},
  {"tool": "text_find", "args": {"doc": 1, "query": "الرقم الضريبي"}},
  {"tool": "redact_mark", "args": {"doc": 1, "words": ["310987654300003", "300123456700003"]}},
  {"tool": "redact_apply", "args": {"doc": 1}},
  {"tool": "doc_save", "args": {"doc": 1, "path": "out/invoice-redacted.pdf"}},
  {"tool": "page_render", "args": {"doc": 1, "page": 1, "dpi": 72}, "out": "out/invoice-redacted.png"}
]
$ xipdf-cli run --script out/redact-invoice.json
{
  "dirty": false,
  "doc": 1,
  …
  "name": "invoice-ar.pdf",
  "pages": 1,
  …
}
{
  "complete": true,
  "count": 2,
  "matches": [
    {
      "page": 1,
      "rects": [
        [
          499.8208312988281,
          208.2119903564453,
          543.5091552734375,
          217.00949096679688
        ]
      ],
      "text": "الرقم الضريبي"
    },
    …
  ],
  …
}
{
  …
  "marked": 3,
  "marks_pending": 3,
  "matched_words": [
    {
      "marked": 2,
      "word": "310987654300003"
    },
    {
      "marked": 1,
      "word": "300123456700003"
    }
  ],
  …
}
{
  "applied": 3,
  …
  "marks_pending": 0,
  …
}
{
  "bytes": 75952,
  …
  "incremental": false,
  "path": "out/invoice-redacted.pdf"
}
{"height":842,"image":"out/invoice-redacted.png","width":595}

Exit code 0. Each step prints its result. text on the saved copy no longer contains either number, audit passes, and the rendered page shows black boxes where they were. A failing step stops the script with step N (tool): <message> and exit code 1.

Coordinates in tool arguments and results are in points from the top-left of the page. With --root DIR, every path a tool or step names, "out" included, must be inside DIR, and relative paths resolve inside it:

$ xipdf-cli run doc_combine 'paths=["invoice-ar.pdf","brochure-bilingual.pdf"]' out=../out/rooted.pdf --root samples
xipdf-cli: ../out/rooted.pdf is outside the allowed directory \\?\V:\…\samples

The automation ocr_recognize and ocr_status tools use the built-in English recognizer, like batch. For Arabic OCR, use the ocr command.

mcp: serve the tools to an AI assistant

xipdf-cli mcp [--root DIR] [--compact]

Starts a Model Context Protocol server on standard input and output (JSON lines; protocol versions 2025-06-18, 2025-03-26 and 2024-11-05). It opens no network port, never starts by itself, and stops when its input closes. It offers the same tools as run; --compact lists a core set of 15 (doc_open, doc_info, doc_save, doc_close, page_render, text_extract, text_find, doc_combine, doc_split, edit_undo, command_list, command_run, command_batch, doc_inspect, render_preview) plus tool_search and tool_call to reach the other tools, instead of all 163. Always give --root so the assistant can only touch one folder. A startup line goes to standard error.

The MCP server is in the standard builds (Windows, macOS and Linux). A build made without the mcp feature answers mcp: this build leaves out the MCP server.

A typical client configuration:

{
  "mcpServers": {
    "xikey-pdf": {
      "command": "C:\\Program Files\\XiKey PDF\\xipdf-cli.exe",
      "args": ["mcp", "--root", "D:\\Documents\\PDF", "--compact"]
    }
  }
}

Tested exchange (--root samples --compact): after initialize and tools/list (17 tools), the call doc_open {"path": "invoice-ar.pdf"} returned document 1, and text_find {"doc": 1, "query": "فاتورة ضريبية"} returned one match on page 1. Exit code 0 when the input closed.

ui: drive a running app window

xipdf-cli ui --control FILE <method> [key=value …] [--out shot.png]

ui talks to an XiKey PDF window that was started with --control FILE. The app then listens on a random port on 127.0.0.1 only, and writes the port and a random token to FILE; every connection must present the token. Keep that file in a folder only you can write. The command refuses a control file that is a link or not a regular file, and on Linux and macOS also one owned by another user or open to other users.

# Windows: start the app with a control file
& "C:\Program Files\XiKey PDF\XiKeyPDF.exe" --control "$env:LOCALAPPDATA\xipdf-control.json" samples\annual-report-ar.pdf
# then, from another terminal
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" state
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" screenshot --out window.png
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" set key=language value=ar
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" quit

On Linux the app is xipdf; on macOS it is "XiKey PDF.app/Contents/MacOS/XiKeyPDF".

Not run for this guide. The window-control examples above were not executed: the test machine's XiKey PDF window was in use by another test at the time. They are written from the app's control-channel reference and are not a recorded transcript. What was run is the error handling: with no running app, ui --control FILE state exits 1 with can't reach the app on port …: … (is it still running?), and with no method it lists them.

Methods (the ui. prefix is optional; parameters are key=value, read as JSON when possible):

Method Does
state Open documents, active document, page, zoom, mode, panels, dialog, window size
inspect query=… role=… limit=… Widgets with id, role, label, value, position and state
commands, command id=… List the app's commands, or run one as the menu would
open path=… Open a file
set key=… value=… View options and preferences, such as language, theme (light, dark, system), panel, select, default-mode
click id=… / label=… / x=… y=…, focus Click or focus a widget or a point
type text=…, key key=… modifiers=… Keyboard input
move, drag from=[x,y] to=[x,y] Pointer movement
resize width=… height=… Window size (320–8192 points)
screenshot (with --out shot.png) PNG of the window; without --out the image is returned as base64
quit Close normally; with unsaved changes the app asks first instead of quitting

Each request waits up to 40 seconds. Input and screenshots need the window to be drawn; a minimized or hidden window answers with an error asking you to bring it to the front.

--version

$ xipdf-cli --version
xipdf-cli 0.8.0
Community: https://xikey.com  (help and feedback)
Website: https://desktop.xikey.com/products/xipdf/

Exit code 0.

OCR in detail

  • Engine and languages. The ocr command uses Tesseract 5.3.0 with the tessdata_best 4.1.0 models for Arabic (ara) and English (eng). Combine languages with +; ara+eng is the right choice for Arabic documents that also contain English, codes or Latin digits. Recognition runs entirely on your computer.
  • Windows x64: Tesseract and both language models are installed with XiKey PDF, in C:\Program Files\XiKey PDF\ocr\tesseract\. OCR works offline from the first run.
  • Linux: install Tesseract and the language packs from your distribution. The DEB and RPM packages declare them as dependencies; the AppImage does not include them.
# Debian / Ubuntu
sudo apt install tesseract-ocr tesseract-ocr-ara tesseract-ocr-eng
# Fedora / RHEL
sudo dnf install tesseract tesseract-langpack-ara tesseract-langpack-eng
# openSUSE
sudo zypper install tesseract-ocr tesseract-ocr-traineddata-ara tesseract-ocr-traineddata-eng
  • macOS: Tesseract is not bundled. Install it with Homebrew; Arabic is in tesseract-lang, not in the base formula. Then check that tesseract --list-langs lists both ara and eng.
brew install tesseract tesseract-lang
  • How Tesseract is found: the path chosen in Preferences → OCR → Locate; then ocr/tesseract/ next to xipdf-cli; then the XiKey PDF OCR folder in your user profile; then PATH. Language data is read from that user folder's tessdata, from Tesseract's own data folder, and from TESSDATA_PREFIX when set. The user OCR folder is %APPDATA%\XiPDF\ocr on Windows and ~/.local/share/XiPDF/ocr on Linux and macOS ($XDG_DATA_HOME/XiPDF/ocr when that is set).
  • Defaults from the app. The command line reads the default language and resolution you set in the app's Preferences → OCR. With no saved default, it uses eng at 300 dpi. (The app itself picks Arabic + English for scans; the command line does not guess, so pass --lang.)
  • --dpi is the resolution pages are read at: 200, 300 or 400. A scanned page is never read above its own image resolution, because enlarging a scan only blurs the letters.
  • Pages that already have text. By default, ocr leaves alone every page whose text layer is reliable and reports it as "skipped": "the page already contains text". Pages whose text is damaged, garbled or missing are recognized even then. If every selected page is skipped, the command fails and writes nothing:
$ xipdf-cli ocr samples/annual-report-ar.pdf --lang ara+eng --out out/report-text-ocr.pdf
xipdf-cli: ocr: no pages recognized: the page already contains text; the page already contains text; …
  • --all-pages also reads pages that have good text, to catch words inside pictures such as chart labels. Words the page already has are not added a second time. On page 3 of the annual report, which has Arabic text and two charts, it added 9 words:
$ xipdf-cli ocr samples/annual-report-ar.pdf --lang ara+eng --pages 3 --all-pages --out out/report-p3-all.pdf
{"growth_percent":0.19149718723432407,"input_bytes":1658510,"output":"out/report-p3-all.pdf","output_bytes":1661686,"pages":[{"low_confidence":true,"mean_confidence":76.6895751953125,"page":3,"skipped":null,"words":9}],"seconds":3.0978103,"seconds_per_page":3.0978103,"words":9}
  • --pages limits recognition to the listed pages; a page beyond the end fails with ocr: page number is outside this document.

Recipes

Batch-OCR a folder of Arabic scans

Bash (Linux, macOS, or Git Bash on Windows). It skips files already done, so it can be re-run after an interruption, and keeps each page report as JSON:

#!/usr/bin/env bash
# Make every scanned PDF in scans/ searchable, into searchable/.
set -u
mkdir -p searchable
status=0
for f in scans/*.pdf; do
  out="searchable/$(basename "$f")"
  if [ -e "$out" ]; then echo "skip    $f (already done)"; continue; fi
  if xipdf-cli ocr "$f" --lang ara+eng --out "$out" > "$out.json"; then
    echo "ok      $f"
  else
    echo "FAILED  $f" >&2
    rm -f "$out.json"
    status=1
  fi
done
exit $status
$ bash ocr-folder.sh
ok      scans/annual-report-ar-scanned.pdf
ok      scans/ar-scanned.pdf
$ bash ocr-folder.sh
skip    scans/annual-report-ar-scanned.pdf (already done)
skip    scans/ar-scanned.pdf (already done)

Both runs exit 0. PowerShell (Windows):

# Make every scanned PDF in scans\ searchable, into searchable-ps\.
New-Item -ItemType Directory -Force searchable-ps | Out-Null
$failed = 0
Get-ChildItem scans -Filter *.pdf | ForEach-Object {
    $out = Join-Path searchable-ps $_.Name
    if (Test-Path $out) { "skip    $($_.Name) (already done)"; return }
    xipdf-cli ocr $_.FullName --lang ara+eng --out $out | Set-Content "$out.json"
    if ($LASTEXITCODE -eq 0) { "ok      $($_.Name)" } else { Write-Warning "FAILED  $($_.Name)"; $failed++ }
}
exit [int]($failed -gt 0)
PS> .\ocr-folder.ps1
ok      annual-report-ar-scanned.pdf
ok      ar-scanned.pdf

Exit code 0. Expect about one to three and a half seconds per page on a desktop computer.

Extract the text of every PDF to .txt

#!/usr/bin/env bash
# Write the text of every PDF in the given folder to text/<name>.txt (UTF-8).
set -u
src="${1:-.}"
mkdir -p text
for f in "$src"/*.pdf; do
  name="$(basename "${f%.*}")"
  xipdf-cli text "$f" > "text/$name.txt" || echo "FAILED  $f" >&2
done
# Write the text of every PDF in the given folder to text-ps\<name>.txt (UTF-8).
param([string]$Source = '.')
# xipdf-cli writes UTF-8; tell PowerShell, so Arabic stays intact whatever the console code page.
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
New-Item -ItemType Directory -Force text-ps | Out-Null
Get-ChildItem $Source -Filter *.pdf | ForEach-Object {
    $txt = Join-Path text-ps ($_.BaseName + '.txt')
    xipdf-cli text $_.FullName | Set-Content -Encoding UTF8 $txt
    if ($LASTEXITCODE -ne 0) { Write-Warning "FAILED  $($_.Name)" }
}

Both were run on the samples folder (exit 0). The invoice's text file starts with مشغل السدو للحرف التراثية. The two scans give empty files: OCR them first. Windows PowerShell 5.1 writes UTF-8 with a byte-order mark and CRLF line ends; the bash version writes the tool's output unchanged.

Split a report by chapters

Look up where each chapter starts (for example with xipdf-cli text <file> --page N), then split before those pages. In the annual report, chapters ٠١ to ٠٥ start on pages 2 to 6, after the cover:

$ xipdf-cli split samples/annual-report-ar.pdf --before 2,3,4,5,6 --out-dir chapters
chapters\annual-report-ar-p1.pdf
chapters\annual-report-ar-p2.pdf
chapters\annual-report-ar-p3.pdf
chapters\annual-report-ar-p4.pdf
chapters\annual-report-ar-p5.pdf
chapters\annual-report-ar-p6.pdf

Exit code 0. For a document with bookmarks, the doc_split tool can also split at top-level bookmarks ("bookmarks": true) from a run --script.

A CI gate with check and audit

#!/usr/bin/env bash
# CI gate: fail the build if any PDF in $1 is unreadable or has a faux redaction.
set -u
dir="${1:?usage: pdf-gate.sh DIR}"
fail=0

# 1. Robustness: check always exits 0 once it has run, so read its JSON report.
xipdf-cli check "$dir" --timeout 30 --json check.json || exit 1
python3 - check.json <<'PY' || fail=1
import json, sys
bad = [r for r in json.load(open(sys.argv[1], encoding="utf-8")) if r["status"] != "ok"]
for r in bad:
    print(f'check: {r["status"]}: {r["file"]}: {r["detail"]}')
sys.exit(1 if bad else 0)
PY

# 2. Redactions: audit exits 1 when it finds a faux redaction (or cannot read the file).
for f in "$dir"/*.pdf; do
  xipdf-cli audit "$f" > /dev/null || fail=1
done
exit $fail

Run on a folder with the redacted invoice and the brochure, the gate passes (exit 0). Run on a folder with the original invoice, the marked-but-not-applied copy and a broken file, it fails (exit 1):

$ bash pdf-gate.sh release-bad
[1/3] open-error:the-file-is-not   release-bad\broken.pdf  the file is not a readable PDF: …

checked 3 files: {"ok": 2, "open-error": 1}
check: open-error:the-file-is-not: release-bad\broken.pdf: the file is not a readable PDF: …
xipdf-cli: release-bad/broken.pdf: not a PDF file (no %PDF- header)
xipdf-cli: audit: 2 faux redaction(s) found

Troubleshooting

OCR fails with "Tesseract is not installed" or "OCR language … is not installed". The engine or a language pack is missing. On Windows, repair or reinstall XiKey PDF; the engine belongs in ocr\tesseract\ next to xipdf-cli.exe. On Linux, install the distribution packages listed above; on macOS, brew install tesseract tesseract-lang. Then check tesseract --list-langs. Asking for a language that is not installed gives, for example, ocr: no pages recognized: text recognition failed: OCR language fra is not installed; download it in Preferences → OCR.

The built-in recognizer is missing (batch --steps ocr or --lang en fails with the text recognition models are not installed (reinstall XiKey PDF, …)). These models ship in the models folder of the installed app; a copy of xipdf-cli alone does not have them. Reinstall, or run the command from the installed folder.

A password-protected file. Without a password:

$ xipdf-cli info out/invoice-protected.pdf
xipdf-cli: the document is protected by a password
$ xipdf-cli info out/invoice-protected.pdf --password wrong
xipdf-cli: the password is incorrect
$ xipdf-cli text out/invoice-protected.pdf --password demo-open-2025
مشغل السدو للحرف التراثية
…

The first two exit 1, the third 0. If text says the permissions don't allow copying, pass the owner (permissions) password. combine cannot open protected inputs; extract the pages with --password first.

OCR text is Latin gibberish on an Arabic scan. The wrong language was used: without --lang the command line reads English. Re-run with --lang ara+eng, or set Arabic + English as the default in Preferences → OCR. The batch OCR step cannot read Arabic at all; use the ocr command.

Words are wrong even with the right language. Check mean_confidence and low_confidence in the JSON. Clean, straight scans at 300 dpi read best; faint, skewed, handwritten or decorative text produces more errors. Try --dpi 400 only for small print on scans that have that resolution.

Linux: the app does not start, with an error about libxkbcommon-x11. That library is needed by the window only. xipdf-cli does not use the window system, so it runs on servers without a display or keyboard libraries. To run the app, install libxkbcommon-x11-0 (Debian/Ubuntu) or libxkbcommon-x11 (Fedora/RHEL).

Arabic shows as question marks in a Windows console. The tool writes UTF-8. In PowerShell set [Console]::OutputEncoding = [System.Text.Encoding]::UTF8, or redirect to a file and open it in an editor.

Sources