| | | | | linguist.page@gmail.com

Building a Print-Ready, Pocket-Sized Mushaf: From APK Teardown to A7 Booklet

A technical walkthrough of extracting vector Quran assets from an Android app and turning them into a double-sided, cut-and-bind A7 booklet — with every wrong turn included.

Why This Project Exists

I wanted two things that don’t normally come from the same source:

  1. A Kindle-friendly Mushaf — dense enough to avoid endless page-turns, but still legible on a small e-ink screen.
  2. A pocket-sized printable Mushaf — A7 paper, back-to-back, foldable into a mini book, sharp enough to actually read.

Most “compact Quran” PDFs online are screenshots of a screenshot — soft edges, visible compression artifacts, inconsistent margins. I didn’t want to build on top of degraded raster images. I wanted the source calligraphy.

That meant going straight to where a Quran app stores its rendering data, pulling it out, and building my own layout and print pipeline around it. This post documents that pipeline end to end: the teardown, the extraction, the layout math, and the double-sided print logic — including the mistakes that shaped the final design.


Part 1 — Getting the Source Assets

The Assumption That Turned Out to Be Wrong

My starting assumption was that the Quran app rendered verses as text, using a custom Arabic font. If that were true, the plan was simple: pull the font, pull the layout rules, re-render everything myself.

That assumption didn’t survive contact with the file system. Every line of the Mushaf turned out to be stored as an individual SVG asset — one vector file per line, not per page, not per surah. This changed the whole approach, and for the better: instead of fighting font rendering and line-justification rules to match the original mushaf layout, I could just re-assemble the original artwork.

Step 1 — Unpacking the XAPK

An XAPK is not a special format — it’s a ZIP container that bundles the base APK together with configuration APKs and asset packs, which is how Android apps ship large binary assets outside the main install package:

mv QuranApp.xapk QuranApp.zip
unzip QuranApp.zip -d QuranApp_bundle

Inside:

QuranApp_bundle/
├── asset_pack_0.apk        # ~474 MB — the interesting one
├── config.mdpi.apk
├── kw.gov.qsa.largequranapp.apk
└── manifest.json

The 474 MB asset pack was the obvious target. Base APKs are usually a few megabytes of code and UI chrome; anything measured in hundreds of megabytes is almost always media or asset data.

Step 2 — Unpacking the Asset Pack

APKs are themselves ZIP archives, so no specialized Android tooling was needed — just unzip:

mkdir asset_pack_0
unzip asset_pack_0.apk -d asset_pack_0

Step 3 — Confirming the Asset Format

find asset_pack_0 -type f | head
asset_pack_0/assets/lines/a03966.svg
asset_pack_0/assets/lines/a08833.svg
asset_pack_0/assets/lines/a01817.svg
...

That confirmed it: the app renders the Mushaf as one SVG per Qur’anic line, not as text and not as a monolithic page image. Compared to text rendering or screenshots, this had three concrete advantages:

Step 4 — Building a Quick HTML Viewer

Before committing to any layout logic, I needed to see the lines in sequence to sanity-check ordering and spacing:

<img class="line" src="asset_pack_0/assets/lines/a03966.svg">
body { margin: 0; padding: 0; }
img.line { display: block; margin-bottom: 3px; }

This is deliberately the simplest possible harness — no framework, no build step. When you’re validating raw extracted data, the fastest path to “does this look right” beats a polished tool every time.

Step 5 — Splitting a Giant Document Into Manageable Pages

Rendering every line in a single HTML document doesn’t scale — the browser chokes, and it’s impossible to reason about page boundaries. I extracted just the <img> references and split them into fixed-size chunks:

grep '<img class="line"' mushaf.html > lines_only.txt
split -l 22 -a 4 lines_only.txt page_

One detail that cost real debugging time: the default split suffix length is two characters, which caps you at 676 output files (aa through zz). With thousands of lines and small chunk sizes, that ceiling gets hit fast, and split fails outright rather than truncating gracefully. The -a 4 flag widens the suffix to four characters, avoiding the failure mode entirely — a small flag, but the kind of thing that only surfaces once your dataset is large enough to expose it.

What This Phase Taught Me

FormatWhat it actually isWhy it mattered here
XAPKA ZIP containing a base APK + config APKs + asset packsLarge media assets live in the asset pack, not the base APK
APKA ZIP archiveNo Android-specific tooling needed to inspect contents
SVG (vs. PNG)Vector line artScales cleanly to any print size with zero quality loss

The single biggest unlock in this whole project was realizing the source data was vector, not raster. It meant every downstream decision — line density per page, print resolution, page size — became a layout problem instead of an image-quality problem.


Part 2 — Two Very Different Targets, One Source

With clean SVG lines in hand, the next question was: how many lines per page?

The answer depended entirely on the output device, so I split the project into two parallel tracks from the same source assets:

The print track is where the project turned from “extract and view” into “build a print production pipeline” — and where most of the real engineering challenges showed up.


Part 3 — The A7 Booklet Problem

The Constraint That Shapes Everything

Nobody’s home printer prints A7. It prints A4. So the real task was never “print an A7 page” — it was: how do I tile many A7-sized pages onto a single A4 sheet, print both sides, and end up with something I can cut and assemble into a mini book, in the correct reading order?

This is a classic imposition problem — the same category of problem commercial print shops solve when laying out a signature for saddle-stitch binding. Doing it by hand with a single source PDF is trivial. Doing it programmatically, from a folder of individually-numbered page images, with correct pairing across a double-sided sheet, is not — and this is where the design went through several iterations before landing on something correct.

Attempt 1: Wrong Physical Orientation

The first version tiled 4 images per landscape A4 in a 2×2 grid. That was wrong on the most basic level: the source pages were portrait, and the target physical layout — confirmed against a reference photo of the desired cut sheet — was a portrait A4 divided into a 2×4 grid of 8 cells, not a 2×2 grid of 4.

Lesson: verify the physical target layout against a real reference before writing tiling logic. A grid dimension mismatch (2×2 vs. 2×4) isn’t a cosmetic bug — it silently produces the wrong number of pages per sheet, which cascades into every downstream calculation.

Attempt 2: Right Grid, Wrong Axis

Once the grid was corrected to 2 columns × 4 rows, a second problem appeared: the sheet needed to be landscape, not portrait, with the grid rotated to 4 columns × 2 rows. The source images were portrait A7 pages, but they needed to be rotated 90° to sit inside landscape cells — otherwise you’d need a portrait A4 with portrait sub-cells, which doesn’t use the paper efficiently and doesn’t match how the mini-book folds.

This is a distinction that’s easy to get backwards: the cell content is portrait, but the cell arrangement is landscape. Getting the rotation direction wrong (clockwise vs. counter-clockwise) doesn’t just look wrong — it puts the text upside-down relative to the fold, which you only notice after a test print.

Attempt 3: The Double-Sided Pairing Problem

This was the hardest part of the whole pipeline, and worth walking through in detail because it’s a genuinely non-obvious problem.

The requirement: print double-sided, cut the sheet into 8 mini-pages, and have each mini-page’s next page land directly on the back of it — so page 1’s reverse side is page 2, page 3’s reverse is page 4, and so on, exactly like a real book.

The naive approach — put pages 1–8 on the front, 9–16 on the back, in the same cell order — fails immediately once you think about what physically happens when you flip a sheet of paper over. Flipping a sheet horizontally mirrors its columns. A cell in the top-left position on the front is not behind the top-left position on the back — it’s behind whatever printed in the top-right, because that’s the position that ends up over it after the flip.

Two different pairing strategies were tried before the correct one:

  1. Sequential front/back with matching positions — pages 1–8 front, 9–16 back, same grid order. Wrong: after flipping, position 1 lands on top of position 2’s content, not position 9.
  2. Mirrored back layout — keep sequential 1–8 / 9–16 content, but reverse the column order only on the back page, so the physical flip re-aligns them. This got closer, but the actual product requirement (once fully specified) turned out to be simpler and cleaner than either of these.

The layout that actually worked: don’t split into “front sheet” and “back sheet” as separate physical passes at all. Instead, produce one continuous PDF where PDF page 1 holds the odd-numbered mini-pages in forward reading order, and PDF page 2 holds the even-numbered mini-pages in fully reversed order:

PDF Page 1 (odd pages)PDF Page 2 (even, reversed)
18
36
54
72
916
1114
1312
1510

Printed double-sided with a standard “flip on long edge” setting, this produces exact alignment: page 1 sits over page 2, page 3 over page 4, and so on — with zero manual re-loading of paper, because the printer’s own duplex mechanism handles the physical flip, and the reversed ordering on the even page compensates for it in software instead of requiring a second manual pass.

This is the kind of bug that’s invisible in the file and only appears once ink is on paper — which is exactly why the pipeline evolved through several corrected versions rather than being right on the first attempt. Each version was validated against an explicit description of the physical end state (what should be behind what, after cutting) rather than against how the code looked like it should behave.

Rotation Direction, Precisely

Once orientation and pairing were settled, one more subtlety remained: which way to rotate. Source pages were portrait; target cells were landscape. A 90° rotation was necessary either way, but clockwise and counter-clockwise are not interchangeable — one produces correctly-oriented Arabic script when the sheet is read normally, the other produces text that’s upside down relative to the reading direction once the booklet is assembled. This was resolved empirically: render a test sheet, hold it the way it will actually be read after cutting, and rotate until it’s right. No amount of reasoning about coordinate systems substitutes for a physical test print when the failure mode is “readable, just backwards.”

Margins and Cropping — Two Different Fixes for the Same Symptom

A late-stage visual issue: images looked slightly off-center within their cells, with more whitespace on one edge of the source page’s border than the other. Two fixes were possible, and they aren’t equivalent:

Cropping was the more correct fix, but it required knowing exactly how many pixels to trim — and here the source images being scaled for preview vs. their true print resolution mattered. A margin measured on a downscaled preview image doesn’t transfer 1:1 to the original file; it has to be scaled by the same ratio as the resolution difference. Measuring an 84px asymmetry on a 2480×3509 source (vs. a smaller preview) is a different number than measuring it on the preview directly, and using the wrong one silently under- or over-crops.

Two Deliverables, Not One

The project ended up producing two variants of the tiling pipeline, because “correct” depends on what the booklet is for:

Neither is strictly “better” — they’re different trade-offs between print precision and print economy, and keeping both as separate, explicit outputs (rather than trying to parameterize one script to do both) kept each one simple to reason about.


Takeaways

A few things this project reinforced, independent of the Quran-specific context:


Current Status

Working:

Still in progress:

What started as “why is this app 474MB” turned into a full small-scale print production pipeline — built entirely from open command-line tools, with every layout decision traceable back to a specific, physical constraint.