patx/makeitpdf

# MakeItPDF

Combine PDFs, images, Word documents, Excel workbooks, Markdown, and CSV files
into one PDF. Everything runs in the browser: no upload, account, server-side
converter, or third-party runtime request.

## Run it

Serve the directory over HTTP:

```bash
npm run serve
```

Open <http://127.0.0.1:8000>. The app uses a Web Worker and lazy-loaded local
fonts, so opening `index.html` directly with a `file:` URL is not supported.

The production app is static and all runtime libraries and fonts are committed
under `vendor/`. `npm install` is only needed to run the browser integration
test or refresh vendored assets.

## Supported files

- PDFs: `.pdf`
- Images: JPEG, PNG, WebP, GIF, BMP, TIFF
- Documents: DOCX
- Spreadsheets: XLSX and UTF-8 CSV
- Text: Markdown (`.md` and `.markdown`)

Select or drop up to 10 files, reorder them, and convert them into one PDF.
Files begin new PDF sections in queue order. Legacy DOC/XLS and encrypted
Office files are rejected with a specific message.

### Combining PDFs

Every page from each input PDF is copied into the result in queue order. Page
dimensions, rotation, vector artwork, and searchable text are retained rather
than rasterized. PDFs can be combined with one another or placed anywhere in a
mixed queue alongside the formats below. Password-protected and malformed PDFs
are rejected with a specific message.

### Images keep their physical size

Each image gets a borderless page sized from its pixel dimensions and embedded
print resolution:

```text
page points = image pixels / image DPI × 72
```

Resolution is read from JPEG JFIF/EXIF, PNG `pHYs`, TIFF resolution tags, and
BMP pixels-per-metre fields. Images without valid resolution metadata use 96
DPI. EXIF rotation is applied before page dimensions are calculated.

Compatible JPEG and PNG data is embedded directly when no rotation is needed.
Other image types and rotated images are decoded at their original pixel grid;
they are never reduced to an A4 raster. Pages over the common PDF limit of 200
inches per side are proportionally capped without discarding source pixels.

Animated GIFs use their first frame. Multi-page TIFFs currently use their first
image directory.

### DOCX and Markdown

DOCX conversion is semantic rather than a clone of Word's print engine. It
preserves ordinary headings, paragraphs, lists, tables, links, embedded images,
and inline emphasis. Exact Word pagination, installed fonts, text boxes,
headers/footers, tracked changes, and floating layouts are not reproduced.

Markdown supports headings, emphasis, links, lists and task lists, quotes,
fenced code, tables, rules, and strikethrough. Raw HTML is disabled. Embedded
data-URI images are allowed; remote and relative images are represented by alt
text and their URL and are never fetched.

### XLSX and CSV

Every visible XLSX worksheet is included in workbook order. Hidden and very
hidden worksheets stay out of the PDF. Displayed cell values, cached formula
results, merges, and stored column widths are retained where available.
Formulae are not recalculated, and charts, macros, shapes, conditional
formatting, and Excel print settings are not reproduced.

Spreadsheets use landscape A4. Wide tables are divided into consecutive column
groups before type becomes too small to read. Workbooks and CSV files are
limited to 50,000 rendered cells.

## Privacy and safety

- Runtime code, UI fonts, PDF fonts, and conversion libraries are local files.
- DOCX and XLSX parsing runs in a worker with a 30-second timeout.
- Mammoth output is converted through an element and URL allowlist and is never
  inserted into the live page.
- Links created from DOCX and Markdown are limited to HTTP, HTTPS, and mailto;
  imported PDF pages retain their existing content.
- A failed input aborts the whole conversion; no partial PDF is downloaded.
- Limits are 10 files and 25 MB per file.

The PDF font set covers Latin, Greek, Cyrillic, Arabic, Hebrew, Devanagari,
Chinese, Japanese, and Korean. Bidirectional text is reordered for display and
tagged with its logical source text for copying and search.

## Tests

Install development dependencies, then run:

```bash
npm test
npm run test:browser
```

The unit suite covers image geometry and metadata, TIFF detection, URL safety,
UTF-8 handling, and CSV parsing. The headless-Chrome integration test combines
two PDFs with a PNG, DOCX, XLSX, Markdown file, and CSV into one PDF and verifies:

- imported PDF page order, page dimensions, and searchable text are retained;
- the image page has its exact 96-DPI fallback dimensions;
- text from all four text-based formats is searchable;
- hidden worksheets are excluded;
- no external network request occurs.

## Project layout

```text
index.html          form and result markup
app.js              intake, queue, workers, and UI states
pages.js            pure image metadata and page geometry
documents.js        safe CSV/HTML/Markdown normalization
pdf-builder.js      PDFKit layout, imported page copying, and final assembly
office-worker.js    isolated DOCX/XLSX parsing
style.css           responsive visual system
tests/              node and real-browser coverage
vendor/             offline libraries, fonts, and licences
```

Runtime libraries are PDFKit, pdf-lib, blob-stream, Mammoth, SheetJS CE,
markdown-it, bidi-js, UTIF, and pako. SheetJS is vendored from its official
0.20.3 release rather than the outdated npm registry build.