PDF to Handwriting is the second most visited tool on ihatepdf. You give it a PDF or some typed text and it gives back pages that look written by hand, either on notebook paper or in place on the original document with every image, table and margin where it was. It runs entirely in the browser: no model on a server, no upload, no account.
This post explains how it works. None of it is machine learning. It is glyph metrics, a seeded random number generator, a lot of canvas work, and one small use of OCR.
The pipeline at a glance
- Get the text. Typed text comes straight from the text box. For a PDF, pdf.js extracts every text run with its position, size and transform.
- Get a glyph for every character. Each character resolves to a stored image with a width, a height and a baseline offset: drawn by the user, learned from a photo of their handwriting, or rasterised from a handwriting font as a fallback.
- Lay the glyphs out. Notebook mode flows the text onto ruled paper. Keep-the-layout mode writes each run into the exact box the typed run occupied.
- Add variation. Every glyph gets a small, seeded change in rotation, baseline, scale and ink opacity, and repeated letters can use different variants.
- Write the PDF. Each page canvas is encoded as an image and placed on a page of the same size with pdf-lib.
A glyph is an image plus three numbers
The first version stored each letter as a bitmap and scaled every bitmap to the line height. It looked wrong immediately: a lowercase x came out as tall as a capital H, and descenders on g, p and y sat on the line instead of below it. Handwriting is legible because letters keep their proportions to an invisible baseline.
So every glyph is captured on a fixed 560-pixel square with two guide lines: the ascender
line at 20% of the height and the baseline at 72%. The distance between them is the
reference cap height, and every glyph is stored as {canvas, w, h, baseline},
where baseline is how far the baseline sits from the top of that glyph's own
bounding box. At render time a single scale factor, font size divided by the reference cap
height, is applied to everything, and each glyph is drawn at
y = lineY - baseline * scale. Descenders fall below the line because their
baseline offset is smaller than their height. That one change fixed every proportion
problem at once.
Never fail a character: font fallback
Nobody draws 81 characters before trying a tool. If a character had no glyph, the first version left a gap. Now every missing character is rasterised from a handwriting font, Caveat, Patrick Hand or Dancing Script depending on the chosen style, into the same 560-pixel capture frame. The font is calibrated once so that its cap height lands exactly on the reference cap height, which means a font-generated "e" and a hand-drawn "R" share the same baseline and scale. Any character you do draw replaces the font's version for that character only.
One lesson from this part: we first used system font stacks, and on macOS all three styles silently resolved to the same installed font, so switching style changed nothing. The fonts are now loaded explicitly and the glyph cache is rebuilt once they are ready.
Variation that is random but repeatable
Handwriting is recognisable by its inconsistency. The renderer uses a small linear congruential generator seeded from the text and a user-controlled seed, and for each glyph draws a few numbers from it: a rotation around the slant angle, a baseline wobble of up to a few percent of the font size, a scale change of a few percent, and an ink opacity drop that reads as pen pressure. A slower "line wander" drifts the baseline across a line the way a real line of writing drifts. All of it is scaled by one "messiness" setting.
Seeding matters for two reasons. The live preview and the downloaded PDF must match exactly, and they do because they consume the same sequence. And a person who dislikes how a page came out can press Shuffle, which changes the seed and re-rolls every variation without changing anything else.
Each character can also hold up to three variants. When a page is laid out, a variant is chosen per occurrence before the width is measured, so the measurement and the drawing use the same glyph. Three different "e"s across a paragraph do more for realism than any amount of jitter on one.
Keeping the original layout
Most text-to-handwriting tools reflow everything onto lined paper. That is fine for notes and useless for a worksheet, a form or a slide. Keep-the-layout mode leaves the page alone and replaces only the typed text.
Each page is rendered with pdf.js at twice its size, capped at 4,000 pixels on the long
side to stay inside mobile canvas limits. For every text run from getTextContent
the renderer computes the run's box in canvas space, samples the paper colour just around it,
and paints over the typed text with that colour. A run on a white page gets white, a heading
on a coloured banner gets the banner's colour.
Then it decides the ink colour. Rather than writing everything in blue, it averages the pixels inside the run that differ clearly from the sampled background (a colour distance of more than 40), which gives the colour the typed text was printed in. White text on a dark banner stays white and grey captions stay grey.
Finally the handwriting has to fit. Each run is measured at its natural handwritten width and then compressed or stretched horizontally to the typed run's width, within limits of 0.32 to 1.18 times, so a line never spills into the next column or leaves a gap where a word was. The transform from pdf.js is applied first, so rotated and scaled text is written at the same angle and size.
Learning someone's handwriting from a photo
Drawing letters with a mouse is tedious, so the tool can also learn letters from a photo of a handwritten note. That part does use OCR, but only to find where letters are, not to generate anything.
The photo is cleaned first: a flat-field correction divides out uneven lighting and shadows, and Otsu's method picks the threshold between ink and paper. That gives two canvases, a transparent ink layer to cut glyphs from and a black-on-white copy for recognition. Tesseract.js reads the clean copy and returns a bounding box and confidence for each recognised symbol.
Cutting a letter out of a box is the easy part. Sizing it is the hard part. The first version normalised each line by its own cap height, which mis-sized any line written mostly in lowercase. The current version classifies every character typographically: x-height letters extend 0.54 of the reference height above the baseline, ascenders and capitals 1.0, descenders 0.54 above and 0.36 below, and punctuation has its own rules. Each glyph is scaled from its own ink bounds according to its class, so a photographed "n" and a photographed "B" come out in the right proportion. A connected-component pass removes strokes from neighbouring letters that strayed into a box, while keeping the dots on i and j.
The results go through a review step where the user can relabel or skip any letter before it is saved. Several photographed copies of the same letter become its variants.
What it does not do
- Scanned PDFs have no text layer, so there is nothing to convert until OCR has been run on them.
- The output pages are images, which is how the handwriting looks real. The text is not selectable afterwards.
- The fallback fonts cover Latin script. Other scripts render only for characters you draw or photograph yourself.
Everything above runs in the page you are reading it from, which is why there is no upload step and no queue. The whole tool is at ihatepdf.cv/pdf-to-handwriting, and the typed-text version at ihatepdf.cv/text-to-handwriting.
Frequently asked questions
Does the PDF to handwriting tool use AI?
No generative model is involved. Letters come from your own drawings, from a photo of your handwriting, or from handwriting fonts, and realism comes from measured glyph proportions and seeded variation. OCR is used only to locate letters in a photo.
Why does the handwritten text keep the same layout as my PDF?
In keep-the-layout mode each typed run is painted over with the surrounding paper colour and the handwriting is fitted to that run's exact width, so images, tables and margins do not move.
Is the document uploaded anywhere?
No. Text extraction, rendering and PDF creation all happen in your browser, and the file never leaves your device.