# Editing a paragraph that mixes bold, italic and regular as one block — Edit My PDF

> A textarea holds one font. A paragraph with a bold lead-in needs a contentEditable editor that owns its DOM, maps every edit into styled spans and keeps the page's line layout.

Canonical: https://editmypdf.dev/blog/mixed-face-paragraphs/
Published: 2026-10-14

14 October 2026 · 4 min read

Most text on a page is set in one face. The exceptions are the interesting ones: a paragraph with a bold lead-in, a sentence with an italic title in it, a definition whose term is set in small caps. A reader sees one paragraph. A textarea can hold only one font. So a paragraph that mixes faces needed its own editor.

## The model: spans, not runs

When Edit My PDF reads a page it collects text runs, the pieces pdf.js reports, each in one font. For a line, adjacent runs in one face are joined into a span; a gap wider than a sliver becomes a space; whitespace collapses the way the text would read. A span records its family, weight, style, size, colour, the PDF font it came from, and its own letter spacing when the page tracked it apart from its neighbours. It also keeps the page box its glyphs occupy, because the colour is read from the rendered pixels under that box.

Then the lines of a block are joined, and neighbouring spans in the same face whose colours are as good as equal (within 20 on each channel) are merged, so a paragraph read line by line edits as a few runs rather than one per line.

**Revenue.** Revenue grew nine per cent to $4.2 billion, with growth in *every* region.

One paragraph, four spans. Each span carries its face, size, colour and PDF font name; the text between them is one string with the spans laid over it.

## Deciding what belongs together

Which lines flow into one paragraph? Two lines can continue each other when they share a dominant face, or when the next line's face already appears in the previous one. That second rule is what joins a bold lead-in to the regular text that follows it.

The reverse pairing is deliberately left apart: a single-face line above a mixed line that merely contains its face. That is what a heading followed by a paragraph with a bold lead-in looks like, and pdf.js cannot tell a heading face from a bold one when the weight is baked into the font outlines. Getting this wrong would swallow headings into paragraphs.

## The editor owns its DOM

The mixed-face editor is a `contentEditable` block with one `<div>` per line and one `<span>` per styled run. React does not render its contents; the component does, from the model, and it intercepts every edit before the browser applies it.

A `beforeinput` event tells us what the browser is about to do: insert typed text, paste, drop, insert a line break, delete backward or forward. The affected character range is read from the event's target ranges, the change is applied to the spans in the model, the lines are drawn again, and the caret is put back. Inserted text takes the face of the character before the caret, so a new word typed after a bold run stays bold, and a span that loses all its text disappears, except the last one, which stays so typing can continue.

Input methods that compose text (Japanese, Chinese, dead keys) cannot be intercepted this way. For those the editor lets the browser apply the change and then reconciles: it reads the DOM text back, finds the common prefix and suffix against the model, and applies the difference as one edit.

The editor keeps its own undo history of two hundred steps and handles Cmd+Z and the browser's `historyUndo` input itself, so undo moves through spans rather than through whatever the browser's contentEditable undo would do to our DOM.

## Lines where the page put them

Because the component draws the lines, it can also place them. Each line `<div>` gets the page's indent for that line (as a text indent on the first line, as padding on the others), a top margin for its baseline offset from an even pitch, and the page's own word spacing for that line. A line the page gave a word gap is already exactly as wide as printed and is never stretched; only a line that flows again after an edit in a justified block is justified by the browser.

Caret positions are character offsets into the whole text. A small helper maps between those offsets and DOM selection points, with the one subtlety that the newline between two line `<div>`s has no node of its own.

## Letting the text flow

A paragraph opens with every line break of the page. Breaks that only exist because the column ran out of room are marked soft. The first edit anywhere in the paragraph turns the soft breaks from that point onward into spaces, so the text reflows inside the paragraph's width, while the lines above the edit keep their printed breaks, gaps and positions. Offsets are preserved through this change so the caret does not jump.

## Export

Each span's face is embedded separately on export, and each word is placed in its own span's face at the position the editor showed. The same layout code serves the editor, the painted fallback and the real-text export, which is the only reason they agree.

[28 October 2026 · 4 min read

### Exporting edits as real text, in the page's own font

How Edit My PDF removes the original strings from a page's content stream, embeds a subset of the font pdf.js decoded, and writes your edit as positioned text rather than a picture.

 [export] [fonts] [pdf-lib]Read](https://editmypdf.dev/blog/exporting-real-text/)[16 September 2026 · 6 min read

### Why editing text inside a PDF is hard

No paragraphs, subset fonts with no space glyph, spacing operators that leak, phantom spaces, mixed faces and browsers that round: the real reasons a click can shift a line.

 [fonts] [pdf]Read](https://editmypdf.dev/blog/why-editing-pdf-text-is-hard/)[21 October 2026 · 3 min read

### What the editor leaves alone

Covered text, tables, Type 3 fonts, form XObjects, phantom spaces and the lines you did not touch: the deliberate limits of editing a PDF in place.

 [limits] [design]Read](https://editmypdf.dev/blog/what-the-editor-leaves-alone/)

[Try it on a PDF](https://editmypdf.dev/app/) [All posts](https://editmypdf.dev/blog/)

---

Markdown version of https://editmypdf.dev/blog/mixed-face-paragraphs/. Every page of Edit My PDF has one at the same address with .md appended; the index is https://editmypdf.dev/llms.txt.
