# Turning a PDF into a cbook book A companion guide for an AI assistant. Version 1.0, 2026-11-13. From the cbook project, (c) 2026 Bill Mandel. Free to download and to use with an AI assistant to make reading editions of PDFs for the cbook reader. ## 0. Read this first A person has attached this file, the guide "Making a book for the cbook reader" (the authoring guide), and a PDF to a chat. They want the PDF turned into an EPUB book that reads well on a phone in the cbook reader (a web page at https://friendsservermb.mine.nu/cbooks/) and in other EPUB readers. This is a **reading edition**: the same words as the PDF, set so they flow to any screen. Read the authoring guide first. Everything in it still applies: ask first, propose an outline, be honest, the Markdown subset, the builder script, the check script and the report. This guide only adds what is different when the words already exist. Where the authoring guide says "write", here you **transcribe**: you do not write the book, you carry over someone else's. If the person did not attach the authoring guide, ask for it before you start. Without it you do not have the builder. You may be an assistant with no access to the person's computer. The script in section 6 is optional help. If you cannot run code, you can still read the attached PDF yourself, page by page, and write the chapters as text. Say plainly what you can and cannot do, and do not pretend to have made a file. Tell the person in plain words what you are doing at each step. Keep your messages short. ## 1. Rights first Before any work, ask: **whose PDF is this, and what do you want to do with the book?** Then say, in two or three sentences, what follows. - A PDF of someone else's work is usually all rights reserved. A reading edition is a copy of it. For the person's own reading that is the ordinary reason to make one. To share it with anyone, put it on a website, or give it away, the owner's permission is needed, unless the work is in the public domain or carries a licence that allows it. You cannot decide that for the person. Say so, and say that you are not giving legal advice. - Keep the original copyright line in the book, on the About page and in the `rights` field. Never remove or change a copyright notice, an author's name or a publisher's name. - If the PDF is locked with a password or copy protection, do not try to get around it. Ask the person for an unlocked copy, or for the text. - Make the book private by default. Say in the book that it is a private copy. - If the work is the person's own (they wrote it) or is public domain or openly licensed, say which, and put that in the rights line. ## 2. Ask first After the rights question, ask these in one short message and wait for the answers. - Which pages? The whole PDF, or only some parts? For a long PDF, agree the scope now (section 5). - Faithful or reworked? The default is **faithful**: the author's words, unchanged. If the person wants a light copy edit (spelling, small slips, one style), agree what is allowed, and you must then list every kind of change in the book (section 4). If they want a summary or a rewrite, that is a new book, not a reading edition: use the authoring guide for it and say so. - Do they want anything **added** that the author did not write (for example short "In plain words" boxes explaining old terms, or "Since then" notes on what has changed)? Default: nothing added. If they say yes, see section 4. - The title, the author and the rights line as printed in the PDF. You can read these from the first pages. Do not guess a name: copy it as printed. Ask if it is not clear. - Is this a new book, or a later edition of one they already made from this PDF? For a later edition, ask for the identifier (authoring guide, section 7). If the person says to infer the answers from their first message, do that and state your assumptions in a few lines. Faithful, nothing added, whole PDF, and the names as printed is a sound set of assumptions. ## 3. Look at the PDF before you start PDFs come in three kinds, and a single file can mix them. Decide for each page. - **A text layer**: the words are stored as text. Extract them, then correct them against the page picture. Extraction mangles line breaks, columns, hyphens and tables. - **A scan**: each page is one picture and there is no text. Read the picture yourself and type the words. Text recognition programs (OCR) make errors in numbers and names, so if one is available, use it only as a first draft and still check every line against the picture. - **Pictures, charts and tables**: neither of the above carries these well. See section 4. If you can run Python, save the script in section 6 as `pdf_tool.py` and run `python3 pdf_tool.py probe FILE.pdf`. It says how many pages there are, which pages have a text layer and which are pictures, and what repeats on every page (a running header or a page number). If you cannot run code, look at the PDF yourself and decide the same things. Then tell the person in two or three lines what the PDF is (pages, kind, anything hard: two columns, charts, poor scan) and how you will handle it. ## 4. How to transcribe **Words** - Carry over the author's words, facts, numbers, units, names and instructions exactly. Do not summarise. Do not correct facts. Do not bring a safety instruction or a recommendation up to date: if something has changed, that belongs in a clearly marked added note, never in the author's sentence. - Read every number twice, against the page picture. Numbers, part names and units are the commonest transcription mistakes. - Join words split at the end of a line. Join lines of a paragraph. Keep real paragraph breaks. - Drop running headers, footers and page numbers. Drop a page's decorative border and a scanner's marks. - Two columns: read the whole of the left column, then the right. Check the order against the page picture. - Keep the original headings. Each main heading becomes a chapter with a number and a name ("3. Stability"). A heading of a lower level becomes a `##` section. If the opening has no heading, add a short one ("Introduction") and say you did. For a very short PDF with only a few headings, use fewer, longer chapters. - Footnotes: put each at the end of its paragraph as a short sentence beginning "Note:", or gather them in a list at the end of the chapter. Say which you did in "About this book". - If you cannot read a word after looking again at a larger picture, write your best reading and put it on a **doubtful list** with the page number. Do not leave a placeholder in the book. Never invent a missing word, number or sentence. - A page reference in the original ("see page 5") has no meaning in a book with no fixed pages. Change it to a pointer to the chapter or figure ("see chapter 2", "see Figure 3") and count that as an edit to list. - Spelling, punctuation and wording stay as printed unless the person agreed to a copy edit. With a copy edit, make a list of exact old and new wording as you go, and give every kind of change in the book. **Tables, charts and equations** - A small table becomes a list, one item per row ("Engine A8-0: 2.5 ounces; delay none"). The reader has no wide tables. - A large table, a chart or a graph is kept as a picture (a crop of the page) with a caption and alt text. In the alt text, give the main facts a person who cannot see it needs. Do not read values off a graph and present them as numbers: say what the graph shows, and that the values are in the picture. - A simple equation: write it in plain text with the real symbols (x = a / b, v squared, the multiplication sign). A long or stacked equation is kept as a picture of that part of the page, with alt text that says what it is. **Pictures** - Each drawing or photo becomes a picture in the book. Extract it with `pdf_tool.py images`, or draw the page with `pdf_tool.py render` and crop the part you want (the Python library Pillow can crop). Look at each finished picture: it must hold the whole drawing and no stray text or page border. - Scans often show the back of the sheet through the paper. If so, turn the picture to grey and raise the white point (Pillow's `ImageOps.autocontrast` with a small cutoff, or a levels curve) until the background is clean. Do not retouch the drawing itself. - Keep the original figure number and caption. If it has none, write a short caption of your own. Every picture needs alt text (the authoring guide, section 4). Labels inside a drawing stay as printed. - Drawings that are letter-tagged and laid out apart (Figure 1A, 1B, 1C) go in as separate pictures, each with its own caption. - Do not redraw drawings. A chart whose numbers are printed in the text can be redrawn as a clean picture only if the person agrees, and the caption then says it is redrawn. **Added material (only if the person asked for it)** - Put it in a callout: `:::note Since the original` or `:::note In plain words`. Mark every added box as not by the author, and say so on the About page. - Only add a fact you checked in this chat against a source you opened, with the link, or write it from general knowledge and say so. Say what you did not read. The authoring guide's rules on honesty (section 2) apply in full to anything you add. - Keep added text to a small share of the book. The author's text stays the book. ## 5. A long PDF - Agree the scope with the person (all of it, or chapter by chapter). A 4 page report is one sitting. A 45 page report with 20 chart pages is not. - Work in batches the size of a chapter. Write each batch to its own file (`ch01.md`, `ch02.md`) before you start the next, and build with `"md_file"` as in the authoring guide, so nothing is lost if the chat is long. - Keep a running list of which PDF pages each chapter covers. At the end, every page must be accounted for: transcribed, kept as a picture, or left out on purpose (a blank page, an advertisement, an index) with the reason. - Index pages and tables of contents of the original are not copied: the reader makes its own contents. Say so in "About this book". ## 6. The helper script This script is optional. It uses whichever of PyMuPDF, pypdf or the poppler programs the computer has, never changes the PDF, and does no text recognition. Save it as `pdf_tool.py`. **Copy it exactly**, as with the builder. Commands: - `python3 pdf_tool.py probe FILE.pdf` tells what the PDF is, page by page. - `python3 pdf_tool.py text FILE.pdf OUT 3-12` writes the text layer of pages 3 to 12, one file each. - `python3 pdf_tool.py render FILE.pdf OUT 3-12 150` writes a picture of each of those pages (150 is the resolution, use 200 or more to read small print). - `python3 pdf_tool.py images FILE.pdf OUT 3-12` writes the pictures stored inside the PDF. The script (tested with Python 3 on 2026-11-13 with PyMuPDF, with pypdf and with the poppler programs; it has not been tried in every assistant's sandbox): ```python #!/usr/bin/env python3 # cbook PDF helper. Looks at a PDF and pulls out its text, page pictures and embedded pictures. # Uses whichever of these the computer has: PyMuPDF ("fitz"), pypdf, or the poppler programs pdftotext and pdftoppm. # It never changes the PDF. It does no text recognition (OCR): a scanned page has to be read from its picture. # # python3 pdf_tool.py probe FILE.pdf what the PDF is, page by page # python3 pdf_tool.py text FILE.pdf OUTDIR [PAGES] the text layer, one file per page (p001.txt ...) # python3 pdf_tool.py render FILE.pdf OUTDIR [PAGES] [DPI] one picture per page (p001.png ...), DPI default 150 # python3 pdf_tool.py images FILE.pdf OUTDIR [PAGES] the pictures stored inside the PDF (p001_1.png ...) # PAGES is a list such as 3-12,15 (page numbers as printed by a PDF viewer, starting at 1). Default: all pages. import collections, os, re, shutil, subprocess, sys BACKEND = os.environ.get('PDFTOOL_BACKEND', '') # for testing only: fitz, pypdf or cli fitz = pypdf = None if BACKEND in ('', 'fitz'): try: import fitz except Exception: fitz = None if BACKEND in ('', 'pypdf') and not fitz: try: import pypdf except Exception: pypdf = None HAVE_CLI = bool(shutil.which('pdftotext') and shutil.which('pdftoppm')) if not (fitz or pypdf or HAVE_CLI): sys.exit('No way to read PDFs here: none of PyMuPDF, pypdf or poppler (pdftotext, pdftoppm) is installed. ' 'Read the attached PDF yourself, page by page, and tell the person what you can and cannot do.') def pages_of(spec, n): if not spec: return list(range(n)) out = [] for part in spec.split(','): a, _, b = part.strip().partition('-') out += range(int(a) - 1, int(b or a)) return [p for p in out if 0 <= p < n] def page_count(path): if fitz: return len(fitz.open(path)) if pypdf: return len(pypdf.PdfReader(path).pages) m = re.search(r'^Pages:\s+(\d+)', subprocess.run(['pdfinfo', path], capture_output=True, text=True).stdout, re.M) return int(m.group(1)) def page_text(path, p): if fitz: return fitz.open(path)[p].get_text('text') if pypdf: return pypdf.PdfReader(path).pages[p].extract_text() or '' return subprocess.run(['pdftotext', '-f', str(p + 1), '-l', str(p + 1), '-layout', path, '-'], capture_output=True, text=True).stdout def image_count(path, p): if fitz: return len(fitz.open(path)[p].get_images(full=True)) if pypdf: try: return len(pypdf.PdfReader(path).pages[p].images) except Exception: return -1 out = subprocess.run(['pdfimages', '-list', '-f', str(p + 1), '-l', str(p + 1), path], capture_output=True, text=True).stdout return max(0, len(out.strip().splitlines()) - 2) def probe(path): n = page_count(path) print(f'{path}: {n} pages. Reader used: ' + ('PyMuPDF' if fitz else 'pypdf' if pypdf else 'poppler programs')) if fitz: md = fitz.open(path).metadata or {} print('Title/author in the file (may be wrong or empty):', md.get('title') or '-', '/', md.get('author') or '-') first, last, kinds = collections.Counter(), collections.Counter(), [] for p in range(n): t = page_text(path, p).strip(); ims = image_count(path, p) lines = [l.strip() for l in t.splitlines() if l.strip()] if lines: first[re.sub(r'\d+', '#', lines[0])] += 1; last[re.sub(r'\d+', '#', lines[-1])] += 1 kind = 'text layer' if len(t) > 200 else ('picture page (a scan, a chart or a cover)' if ims else 'little or no text') kinds.append(kind) print(f' page {p + 1:>3}: {len(t):>5} characters of text, {ims} picture(s): {kind}') print('Pages with a text layer:', kinds.count('text layer'), ' picture pages:', sum(k.startswith('picture') for k in kinds), ' blank or other:', kinds.count('little or no text')) print('A picture page with almost no text has to be read from its picture (render it). A picture page with some text may be a cover or a chart with labels.') for label, c in (('first line', first), ('last line', last)): for line, k in c.items(): if k >= max(3, n // 2): print(f'Repeats on {k} pages as the {label} (probably a running header or page number to drop): {line[:70]!r}') print('Text order is the PDF\'s own: for two-column pages, check it against the page picture (use render).') def text(path, out, spec): os.makedirs(out, exist_ok=True) for p in pages_of(spec, page_count(path)): open(f'{out}/p{p + 1:03d}.txt', 'w', encoding='utf-8').write(page_text(path, p)) print('wrote text files to', out) def render(path, out, spec, dpi): os.makedirs(out, exist_ok=True) for p in pages_of(spec, page_count(path)): dest = f'{out}/p{p + 1:03d}.png' if fitz: fitz.open(path)[p].get_pixmap(dpi=dpi).save(dest) elif HAVE_CLI: subprocess.run(['pdftoppm', '-f', str(p + 1), '-l', str(p + 1), '-r', str(dpi), '-png', '-singlefile', path, dest[:-4]], check=True) else: sys.exit('Cannot draw pages here (needs PyMuPDF or pdftoppm). Look at the attached PDF yourself.') print('wrote page pictures to', out) def images(path, out, spec): os.makedirs(out, exist_ok=True); k = 0 for p in pages_of(spec, page_count(path)): if fitz: doc = fitz.open(path) for i, im in enumerate(doc[p].get_images(full=True), 1): pix = fitz.Pixmap(doc, im[0]) if pix.n - pix.alpha >= 4: pix = fitz.Pixmap(fitz.csRGB, pix) pix.save(f'{out}/p{p + 1:03d}_{i}.png'); k += 1 elif pypdf: for i, im in enumerate(pypdf.PdfReader(path).pages[p].images, 1): open(f'{out}/p{p + 1:03d}_{i}_' + im.name.replace('/', '_'), 'wb').write(im.data); k += 1 else: subprocess.run(['pdfimages', '-png', '-f', str(p + 1), '-l', str(p + 1), path, f'{out}/p{p + 1:03d}'], check=True) print('wrote embedded pictures to', out) if __name__ == '__main__': a = sys.argv[1:] if len(a) >= 2 and a[0] == 'probe': probe(a[1]) elif len(a) >= 3 and a[0] == 'text': text(a[1], a[2], a[3] if len(a) > 3 else '') elif len(a) >= 3 and a[0] == 'render': render(a[1], a[2], a[3] if len(a) > 3 else '', int(a[4]) if len(a) > 4 else 150) elif len(a) >= 3 and a[0] == 'images': images(a[1], a[2], a[3] if len(a) > 3 else '') else: sys.exit('usage: python3 pdf_tool.py probe FILE.pdf | text FILE.pdf OUTDIR [PAGES] | render FILE.pdf OUTDIR [PAGES] [DPI] | images FILE.pdf OUTDIR [PAGES]') ``` If it says that nothing can read PDFs here, read the attached PDF yourself and carry on without it. ## 7. Build and check Use the builder and the check script from the authoring guide (sections 6 and 7). For a reading edition, fill the BOOK block like this. - `title` and `author`: as printed on the PDF. If the author is a company, use the company. - `subtitle`: leave empty, or "A reading edition" if the person likes that. - `edition`: "Reading edition, Version 1". - `rights`: the original notice, then the private-copy line. For example: "Original text and drawings (c) 1963 Name of owner. All rights reserved. This reading edition is a private copy made for Name of person; all rights in the original remain with its owner." Use the real year and owner from the PDF. If the work is public domain or openly licensed, say so instead. - `made_by`: "Retyped from a PDF by an AI assistant (name it) for Name of person." - `review`: say exactly what is true, such as "Every number was read twice against the page pictures. Not checked by the original author or a subject expert." - `look`: `plain` for reports and reference works. `story` or `guide` only if the original is a story or a reading book. - `identifier`: leave it empty for a new book so a new one is made. Never copy an identifier from the PDF. - `from_memory`: list anything you added from general knowledge, not from a source you opened. For a faithful transcription with nothing added it is empty. - Chapters: "About this book" first, then the chapters in order, then a credits page only if there are pictures with credits. The builder makes the title page, contents and last page. The "About this book" chapter must say, in a few short paragraphs: 1. What the original is: title, author, publisher, year, number of pages, and what kind of PDF it was (a scan, or one with a text layer). 2. That this is a reading edition: retyped and reflowed, with the original page layout, columns and page numbers not reproduced. 3. What was changed, kind by kind (spelling, wording, headings added, page references turned into chapter references, footnotes moved, tables turned into lists, pictures cleaned), or "Nothing was changed except the layout." 4. What was added, if anything, and how it is marked as not by the author. Or "Nothing was added." 5. The rights: that it is a private copy and who owns the original. After the authoring guide's check (`verify_epub.py` and the checklist), do these, and fix what is wrong. - Open the finished chapters against the page pictures and compare at least five passages from different parts, word by word, plus every number in the book once more. Pay most attention to the first and last pages and to any page with a table. - Count: every PDF page accounted for (section 5), and every figure of the original present with its caption. - No placeholder text is left. The doubtful list is empty, or each item on it is in your report. ## 8. The report Give the authoring guide's report (section 7), and add: 1. **Coverage**: pages transcribed, kept as pictures, or left out, and why. 2. **The doubtful list**: every reading you were not sure of, with the page number, so the person can check those against the PDF first. 3. **What the person should check**: tables, numbers in tables and charts, pictures that were cropped, and anything else you were least sure of. 4. **Rights**: one line that the book is a private copy of someone else's work (if it is) and that sharing it needs the owner's permission. 5. A reminder to **keep the identifier** for a later edition (corrections or additions), with the same title, as in the authoring guide's section 8. ## 9. What this guide does not cover Scans of poor quality that no one can read, handwriting, maths-heavy books, music notation, forms and fill-in PDFs, comic books, and books in languages the assistant reads poorly were not tried with this guide. Say what you cannot do instead of guessing. This guide was tested on a four page scan and on mixed text and picture PDFs, and it has not been tried in every assistant. If the person has a very long or very difficult PDF, tell them honestly that the result will need more of their checking.