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:
- A Kindle-friendly Mushaf — dense enough to avoid endless page-turns, but still legible on a small e-ink screen.
- 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:
- Infinite scaling — no raster upsampling artifacts at print resolution.
- No screenshot degradation — no JPEG blocking, no anti-aliasing softness from a second-generation capture.
- Preserved line composition — the original mushaf’s exact word-spacing and justification per line survives untouched, which matters for Quranic typesetting where line breaks are traditionally fixed.
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
| Format | What it actually is | Why it mattered here |
|---|---|---|
| XAPK | A ZIP containing a base APK + config APKs + asset packs | Large media assets live in the asset pack, not the base APK |
| APK | A ZIP archive | No Android-specific tooling needed to inspect contents |
| SVG (vs. PNG) | Vector line art | Scales 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:
- Kindle track: 6 lines per page — dense enough to keep page-turns reasonable on e-ink, matching roughly how many lines fit comfortably in a Kindle’s readable area without shrinking the calligraphy past a comfortable reading size.
- Print track: 4 lines per page — because the print target wasn’t a full page at all. It was A7 (74mm × 105mm), a paper size roughly the dimensions of a large postcard. At that physical size, 4 lines per “page” was the ceiling before the Arabic script became too small to read comfortably.
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:
- 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.
- 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) |
|---|---|
| 1 | 8 |
| 3 | 6 |
| 5 | 4 |
| 7 | 2 |
| 9 | 16 |
| 11 | 14 |
| 13 | 12 |
| 15 | 10 |
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:
- Shift the image within the cell (add a positioning offset) — cosmetically fixes centering, but the underlying asymmetric whitespace remains part of the image data.
- Crop the source image asymmetrically before placing it — removes the actual excess pixels, so the border truly is centered, not just visually nudged.
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:
- Guided-cut version: preserves each page’s aspect ratio, adds thin grid lines between cells as physical cutting guides, and supports configurable top/bottom margins. Built for precision — cut exactly on the line, every mini-page comes out identically sized.
- Edge-to-edge version: stretches each source page to completely fill its cell, with zero gaps, zero margins, and no grid lines. Built for maximum use of the physical page, at the cost of a small amount of aspect-ratio distortion per page.
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:
- Inspect before you build. The entire project pivoted on one
findcommand revealing SVGs instead of the assumed text-rendering pipeline. Time spent confirming the actual shape of your data before writing logic against it is rarely wasted. - Common container formats are usually just ZIP. XAPK, APK — both unzip with standard tools. It’s worth checking whether a “special” format is actually a familiar one in disguise before reaching for specialized tooling.
- Imposition (print layout) bugs are invisible until printed. Column-mirroring on a physical page flip is the kind of error that’s completely correct-looking in a code review and completely wrong on paper. When the failure mode only shows up physically, validate physically — a cheap test print catches what code review cannot.
- State the physical end-state explicitly, before coding it. The pairing logic only converged once the requirement was phrased as “what page is physically behind what page after I cut this,” rather than “what order should the array be in.” Translating a physical constraint into an explicit, page-by-page description first made the eventual implementation almost mechanical.
- Small utility flags matter at scale.
split’s default two-character suffix limit is invisible on a small dataset and a hard failure on a large one. Tooling defaults are usually tuned for the common case, not your case. - Precision fixes and cosmetic fixes aren’t interchangeable, even when they produce a similar-looking result. Cropping vs. repositioning both “center” an image, but only one actually removes the asymmetry.
Current Status
Working:
- XAPK / asset pack extraction
- SVG line asset harvesting and HTML preview generation
- Kindle-track pagination (6 lines/page)
- Print-track A7 imposition pipeline: correct grid orientation, correct rotation, correct double-sided odd/even pairing
- Two print variants: guided-cut (grid lines, margins) and edge-to-edge (borderless, stretched)
Still in progress:
- Kindle-optimized PDF/EPUB export packaging
- Automated asymmetric-crop detection (currently measured manually per sample page and scaled to source resolution)
- Batch validation across the full asset set to confirm consistent line height/spacing before mass tiling
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.