# Builds tiny EPUB and PDF files for tools/books-test.js and manual checks: python3 tools/mkbooks.py import sys, zipfile, os out = sys.argv[1]; os.makedirs(out, exist_ok=True) with zipfile.ZipFile(os.path.join(out, 'test.epub'), 'w') as z: z.writestr('mimetype', 'application/epub+zip', compress_type=zipfile.ZIP_STORED) z.writestr('META-INF/container.xml', '') z.writestr('OEBPS/content.opf', 'x1Rabbit TalesA. Writeren') z.writestr('OEBPS/nav.xhtml', '') para = ' '.join(['The quick brown rabbit jumps over the lazy dog and keeps hopping through the green meadow.'] * 40) for i, n in ((1, 'One'), (2, 'Two')): z.writestr(f'OEBPS/c{i}.xhtml', f'{n}

Chapter {n}

' + ''.join(f'

{para} Marker{i}x{k}.

' for k in range(8)) + '') # minimal 3-page PDF objs = [] def add(s): objs.append(s); return len(objs) add('<< /Type /Catalog /Pages 2 0 R >>'); add('<< /Type /Pages /Kids [3 0 R 5 0 R 7 0 R] /Count 3 >>') for i in range(3): pid = add(f'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {4+i*2} 0 R /Resources << /Font << /F1 9 0 R >> >> >>') text = f'BT /F1 24 Tf 72 700 Td (Page {i+1} of the rabbit manual) Tj 0 -40 Td (Carrots are great on page {i+1}.) Tj ET' add(f'<< /Length {len(text)} >>\nstream\n{text}\nendstream') add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>') data = '%PDF-1.4\n'; offs = [] for i, o in enumerate(objs): offs.append(len(data)); data += f'{i+1} 0 obj\n{o}\nendobj\n' x = len(data); data += f'xref\n0 {len(objs)+1}\n0000000000 65535 f \n' + ''.join(f'{o:010d} 00000 n \n' for o in offs) + f'trailer\n<< /Size {len(objs)+1} /Root 1 0 R >>\nstartxref\n{x}\n%%EOF\n' open(os.path.join(out, 'test.pdf'), 'w').write(data) # minimal MOBI 6 (uncompressed text, no images), enough to open in a reader. Set DRM=1 in the environment to mark it encrypted. import struct def mkmobi(path, drm=False): html = 'Mobi Rabbits

Chapter One

' + ''.join('

The rabbit hops through the clover field number %d, nibbling as it goes.

' % i for i in range(30)) + '

Chapter Two

' + ''.join('

Carrots are the second chapter topic, paragraph %d.

' % i for i in range(30)) + '' text = html.encode('utf8'); recs = [text[i:i+4096] for i in range(0, len(text), 4096)] title = b'Mobi Rabbits'; hdrlen = 232 exth = b'' def item(t, v): return struct.pack('>II', t, 8 + len(v)) + v body = item(100, b'M. Author') + item(503, title); exth = b'EXTH' + struct.pack('>II', 12 + len(body), 2) + body exth += b'\0' * (-len(exth) % 4) r0 = struct.pack('>HHIHHHH', 1, 0, len(text), len(recs), 4096, 2 if drm else 0, 0) mobi = bytearray(hdrlen) mobi[0:4] = b'MOBI'; struct.pack_into('>I', mobi, 4, hdrlen); struct.pack_into('>I', mobi, 8, 2); struct.pack_into('>I', mobi, 12, 65001); struct.pack_into('>I', mobi, 16, 1234); struct.pack_into('>I', mobi, 20, 6) for off in range(24, 64, 4): struct.pack_into('>I', mobi, off, 0xFFFFFFFF) name_off = 16 + hdrlen + len(exth) struct.pack_into('>II', mobi, 68 - 16, name_off, len(title)); struct.pack_into('>I', mobi, 72 - 16 + 4, 9) struct.pack_into('>I', mobi, 0x5C - 16 + 0, 0) struct.pack_into('>I', mobi, 0x6C - 16, 0xFFFFFFFF); struct.pack_into('>I', mobi, 0x70 - 16, 0xFFFFFFFF) struct.pack_into('>I', mobi, 0x80 - 16, 0x40) for off in (0x90 - 16, 0x94 - 16, 0x98 - 16, 0xA0 - 16): struct.pack_into('>I', mobi, off, 0xFFFFFFFF) struct.pack_into('>I', mobi, 0xE0 - 16, 0xFFFFFFFF); struct.pack_into('>H', mobi, 0xF2 - 16, 0) rec0 = r0 + bytes(mobi) + exth + title + b'\0\0' allrecs = [rec0] + recs; n = len(allrecs); off = 78 + 8 * n + 2; offs = [] for r in allrecs: offs.append(off); off += len(r) hdr = b'MobiRabbits'.ljust(32, b'\0') + struct.pack('>HHIIIIII', 0, 0, 0, 0, 0, 0, 0, 0) + b'BOOKMOBI' + struct.pack('>IIH', 1234, 0, n) hdr = hdr[:78 - 0] if len(hdr) >= 78 else hdr.ljust(78, b'\0') lst = b''.join(struct.pack('>IBBBB', o, 0, 0, 0, i & 255) for i, o in enumerate(offs)) open(path, 'wb').write(hdr + lst + b'\0\0' + b''.join(allrecs)) mkmobi(os.path.join(out, 'test.mobi')); mkmobi(os.path.join(out, 'drm.mobi'), True)