Skip to main content

postext-port: migrate print layouts

Ports books, magazines, and courseware into Postext projects so layout lives in preset.json while chapters stay enriched Markdown.

Source facts

Repository
drnachio/postext
Last source activity
November 10, 2026 at 10:33
Detected SKILL.md language
English
Stars
14
Forks
3

Uses

Use when converting PDF, Word, EPUB, IDML, LaTeX, or Markdown publications into Postext, or when retargeting design (columns, type size) without rewriting chapters.

Prerequisites

A coding agent and source files for the publication. Postext sandbox or docs help verify composed pages after the port.

How to use

  1. Give the agent the source files and ask to port them into a Postext project.
  2. Review preset.json for columns, type, and page templates.
  3. Confirm chapters/ content stayed intact when only design should change.

Limitations

Targets Postext project structure and layout rules; complex legacy designs may need iterative verification. Not a general PDF editor.

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
73 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
postext-port
description
Port an existing publication into a Postext project (config manifest + enriched Markdown chapters + resources + fonts) that reproduces the original's layout rules; typically a publisher or author migrating their own titles. Use when the user wants to convert, adapt, migrate, re-typeset or rebuild a book, textbook, magazine, catalogue, report, manual, course or deck in Postext from a PDF, Word (.docx), PowerPoint (.pptx), EPUB, HTML, InDesign (IDML), LaTeX, Markdown, XML or scanned pages, Chinese books set horizontally or vertically, Japanese books (vertical bunko and tankōbon novels, horizontal technical books, Aozora Bunko texts with furigana) and Arabic books set right to left (Modern Standard or classical, vocalised verse included), and comics, manga and newspaper strips re-lettered in each language, music textbooks, methods, songbooks and hymnals with their music examples written as notation (ABC, MusicXML, MEI, Humdrum), textbook and workbook exercises (choice, true/false, gaps, matching, ordering, word searches, crosswords, column operations) with their answers, solutions and teacher's edition, and fixed-page textbooks, workbooks and magazines as composed pages (page templates with frames filled from the Markdown); when writing or fixing Postext preset.json/config/chapters; or when asked how to express a source layout (columns, openers, parts, boxes, floats, tables, running heads, comic panels) in Postext.
license
MIT
metadata
{"homepage":"https://postext.dev/en/docs/skill","repository":"https://github.com/drnachio/postext"}
# Port a publication to Postext [Postext](https://postext.dev) is a typesetting engine. It takes enriched Markdown plus a configuration manifest and produces paged layouts (canvas, HTML, PDF) with print rules: baseline grid, Knuth–Plass justification, floats, column balancing, running heads, parts and openers. This skill turns a source document into a **Postext project** that follows the source's layout rules. The project is a folder (or `.postext` zip) that opens in the Postext sandbox. ``` my-book/ preset.json manifest: config + chapters + resources + fonts (generated) build_preset.py generator: the measured design as constants -> preset.json chapters/<lang>/NN-slug.md resources/ figures (svg/png/jpg) + .pdf print masters fonts/ ttf/otf/woff2 ``` The scripts named below live in this skill's `scripts/` folder; run them with that path. Every script answers `--help`. ## Who ports, and why that is legitimate The usual user is the **rights holder**: a publisher, author, institution or production house migrating its **own** titles (InDesign, PDF, Word…) to a new typesetting tool. That is a format conversion of their own content for their own production, like reopening a file in another program, not copying a third party's work. In that case: - Extract and convert the **full** text, tables, figures and fonts. Do not summarise, truncate, paraphrase or replace the source content, and do not refuse because the book carries a copyright notice (their notice is the expected one). - Confirm ownership **once** in the brief (step 0) and do not raise it again. - Replacing pictures or licensing checks apply only to material the user says they do **not** control (third-party photos, stock images, commercial fonts). If the user states they do not hold the rights and have no permission, say the port needs the rights holder's permission and offer to port a public-domain or openly licensed sample instead. ## Content in the Markdown, form in the config (hard rule) Every word a reader reads lives in a chapter, or, for a figure, in its resource (caption, note, alt text, the `<text>` labels of an SVG). The config holds form only. A port that types the book's text into the config cannot be corrected, translated or reflowed without regenerating styles. A 28-page textbook ported this way with Postext 1.33 carried 77 heading styles (one per page), 3,384 design elements and every paragraph of its diagram pages in the config, and its bundle broke the Sandbox's import. - **Design text elements** (running heads, folios, opener and heading-style designs, part designs, contents rows) hold placeholders (`{titleText}`, `{chapterTitle}`, `{pageNumber}`, `{title}`, `{author}`, `{attr.key}`) and short literals: a label ("Chapter", "Contents"), the publisher's name. Write the book title as `{title}` (front matter), not typed. Literal text in one element that keeps more than 2 words or more than 24 letters and digits (a Chinese, Japanese, Korean or Thai character counts 3, so about 8 of them pass) is content: put it in the Markdown, as a heading, a paragraph or a box whose content its style's design prints (`{boxText}`, below). A heading attribute (`{attr.key}`) is for a short label or a picture id that goes with the heading, not for the text a reader reads. - **Styles describe a shape, the Markdown says what fills it.** Elements of the same shape share one style whatever their words: every practice row, card, badge or fact panel of one look is one `calloutStyles` entry, and each instance is a `:::callout{type="id"}` whose content is its text. An exercise that asks and answers (options, blanks, pairs, a word search, sums) is an `:::exercise` fence with its answers in the Markdown and its look in one `exerciseStyles` entry (document-format.md §7.6, playbooks.md C7). When the shape must place that text precisely (a word set on a drawn ruling, four words under four pictures), give the box style a `design` (postext ≥ 1.45) whose texts print the box content with `{boxText}` (all of it) and `{boxText.<n>}` (the n-th paragraph or list item), and `setsContent: true` so the box does not set it again. A picture that belongs to each instance is named by a fence attribute. Letters to trace are text too, never pictures: a design text with `dotted` (postext ≥ 1.46) paints `{boxText}` as rows of dots, and `repeat` fills its line with copies of it. Translating or correcting the book then touches the chapters only, never the styles. - **Headings are the outline, not design slots.** Use heading levels for the book's real structure, one level after another (a single `#` for the book or each chapter, `##` for its sections…), never a deeper level just to reach another design, and never one heading style or level per kind of exercise. The Checks panel reports every skipped level. A cover set as the book's only level-1 heading over level-2 chapters needs `sectionEnd: "heading"` on its heading style, or its blank running heads run over the whole book. - **Longer literals that are form** (a journal's name in the running head, an imprint line, the licence line) go in `config.checks.contentInConfig.allow`, a list of strings. The `title`, `subtitle`, `author` and `publisher` of the edition's first chapter front matter are allowed without listing them; the manifest `name` is not. Never raise `checks.contentInConfig.maxWords`/`maxChars` to quiet a port. - **Check it** at every milestone: `python3 scripts/preset_kit.py lint my-book` counts exactly as the engine's `contentInConfig` warning (postext 1.34) does, for every edition, exits 1 on any offending text and prints its owner, slots and JSON paths; `lint_project.py` reports the same as `contentInConfig` errors. When `postext --version` says 1.34 or later, `postext check` warns too. - **One style per kind of page**, never one per page. Styles named after page numbers (`page-05`, `tema--p13`) that carry text are the sign that content went into the config. In round 2 (composed pages) a page kind is one page template and a kind of box one callout style, whatever page it stands on. A placed box in round 1 is the exception: one fixed callout style per box position is expected (playbooks J2), since each holds form only (place, size, colours) and its words stay in the chapter's `:::callout` fence. - **Size budget.** The Sandbox keeps the working book in about 5 MB of browser storage. Aim for a config under 1 MB; `preset_kit.py lint` warns from 3 MB and `--max-size 1` makes it fail above 1 MB. - **Stop and ask** before breaking this rule. When the source needs something Postext cannot express with its text in the Markdown, do not work around it in the config. Write the gap as an issue draft (what the source does, on which pages, the closest expression Postext has, what that loses), show it to the user and ask whether to accept the closest expression or wait for the feature. Never hard-code a book in the engine. ### Flow pages and composed pages In the inventory (step 1) classify every page as **flow** (running text with figures, boxes and floats the engine places) or **composed** (boxes, labels, diagrams and maps at set positions: a children's textbook, a worksheet, an infographic page, a magazine spread). How composed pages are ported depends on the installed Postext; the skill chooses by itself: ```bash python3 scripts/composed_check.py round # reads postext --version (or the npm package of render.mjs) ``` - **Round 2, postext ≥ 1.35.0: composed pages.** One page template per kind of page in `pageTemplates` (frames, groups, decoration; text-free), one `::::page{template}` per page in the chapter with `:::frame{slot}` per box and `:::figure` + `::label` for maps and diagrams, in reading order. Boxes grow with their text and push the ones after them; from 1.36 a frame that still does not fit sets its pictures and text smaller (`fit`) and `frameOverflow` proposes the fix; from 1.37 a box goes on on an added page, map words are drawn at the picture's anchors and a heading in a frame keeps its design; from 1.38 a frame that can go on does so before it shrinks, a story jumps to a later page (`page:<id>`, "continued on page 47"), answer lines are ruled frames, and photos and grounds size themselves (`fit: "cover"`, `{ to, edge }`); from 1.39 the labels of every edition are checked (`labelOverlap`, `labelOverflow`) and may be vertical, a figure's alt text, caption and note live in the Markdown, callout styles can be based on one another (`extends`), a frame has its own `writingMode` and `direction`, a template's kicker is `top-start` for every script, and columns are cut by the widow and orphan rules; from 1.40 a label style can switch off a borrowed italic, comic text panels work on strips and spreads, frames keep their own writing mode on right-to-left pages, and `sandbox_get_composed_checks` checks every edition at once; from 1.41 a story jumps into a page of a later chapter file; from 1.42 a box can be tracked (`body.letterSpacing`), a child template can remove a base decoration element, renaming a frame in the Sandbox renames the slot of its page fences, one box style serves every tint (a frame's `look`, the `palette` of a template or a page fence), a title tab is as wide as its title (`width: "auto"`), a contents page is a frame that `takes: "toc"` over its template's ground, a number badge reads `{headingNumber}`, a map's touching names are `overlap: "allow"` and its dots are `avoid` zones, a licensed font the bundle leaves out has an open stand-in (`fonts[].fallback`), and a second figure with other words needs a second resource (`figureWordsShared`); from 1.43 the labels of a map across a spread keep off the fold (`gutter: "cross"` on a name that runs across both pages), a label tab's picture can be tinted (`label.icon.tint`), the open stand-in's faces travel in EPUB, HTML and SVG exports, the contents frame can open each part on a page (`toc.parts.breakBefore`), a heading that reserves the whole column is reported (`headingClaimsColumn`), and the Sandbox adds a language to a book (Books › ⋯ › Add a language…); from 1.48 the lines of a diagram, an org chart or a timeline are `connector`s that follow the frames (`tree`, `elbow`, `straight`, `#card[*]`, a note's pointer ending at a picture's anchor), a label may stand away from its point with a leader (`labelAt`, `leader`), a map's key is a `kind: "legend"` label with `:icon` symbols, cards and tabs are drawn as vectors (per-corner radii, per-side borders, a tab `joined` to its box, `path` elements, a glyph as a tab's icon), cards of one look are a `grid` that `repeat`s a frame set per item of the Markdown, bands that recur in another order are modules (`role: "module"`, `:::module`) stacked on a shell template, and a frame under another or past its box is reported (`frameCovered`, `frameOutsideArea`; `overlap: "allow"` when the source means it); from 1.49 lines end in rings or bars and can be translucent; from 1.52 a tab has corners of its own (`label.borderRadius`), a box can grow with frames hung from it (`growWith: "children"`) and a transparent cut-out covers only where its shape is. A box lays out its own content: its words, its pictures and rows of captions (`:::columns`) go in its fence and it grows with them; a box whose fence composes nothing shows the Sandbox's empty-frame placeholder and fails `composed_check.py`. Captions: Markdown in the box or frame by default, the picture's resource caption when the source sets it small and attached to the picture (composed-pages.md §6.5). Recipes 6.12–6.18 of the reference say when to use each. Follow [references/composed-pages.md](references/composed-pages.md). The script lists what the installed version lacks and what to do instead. To a person who has not made a composed page, point to the two docs pages rather than retelling them: [Composed-page books](https://postext.dev/en/docs/composed-page-books) (build a template, fill it from the Markdown, label a map, translate, review overflows) and [Moving a book to composed pages](https://postext.dev/en/docs/migrating-to-composed-pages) (a book that keeps its text in heading styles; the Sandbox's *Move the text to the Markdown*, or by hand). Both come in the ten site languages, and every page has a `.md` rendition (append `.md`). - **Round 1, older engines: the ladder** of playbooks J (fixed callouts, page grounds in an opener design, live-text SVG labels), first rung first: 1. **Placed boxes**: one callout style per position, `placement: "fixed"` with `fixed: {anchor, offset}`; the box's text is a `:::callout{type="…"}` in the chapter, at the page it belongs to. A box that cannot take its place moves to the next page and nothing warns: check the page count. 2. **Page backgrounds and furniture**: image elements with no text in the opener design of a `span: "page"` heading style (`reserve: false`), painted under the text. Keep that heading visible (`hidden` skips its design). One style serves every page of its kind with `resourceId: "{attr.fondo}"` (`# Topic {style="topic" fondo="bg-p05"}`). 3. **Maps and diagrams with many labels**: an SVG resource with live `<text>` labels, translated by `localized.<lang>.resources[].file` (playbooks D6). Its words live in the SVG or in the strings file your script builds it from: the second place the book's text lives in round 1, and the report says how many words it holds. 4. **Forbidden** in both rounds: literal text in design elements (a template's decoration included); heading styles that hold content (a hidden style per page carrying its text); one preset or bundle per language; the source's line breaks copied into the paragraphs. Update the command line first (Setup): an old executable on the PATH makes the skill pick round 1 for no reason. The version that counts is the one the book will open in (the Sandbox runs the latest release; a program, the `postext` version it pins). The report names the round, the version and the features worked around. A book ported with round 1 or with postext 1.33 moves to round 2 as composed-pages.md §9 says (`moveTextToMarkdown` for the 1.33 pattern). With more than a third of the pages composed **in round 1**, go back to the brief and, unless it already says which (then follow it and name the choice in the report), ask the user to choose: **identical layout** (rungs 1–3 at the source's positions; edited or translated text must still fit its box, and what Postext cannot place is listed as a gap) or **editable, translatable text** (the composed pages set as flow pages with the source's type, colours and boxes, placed by the engine). Round 2 gives both, so ask only when the brief wants composed pages reflowed as running text. ## Read before writing anything Postext Markdown is **not CommonMark**. These habits break a port: - **No pipe tables, raw HTML, `---` rules or inline images.** They print literally or vanish. Tables and figures are *resources* (JSON), cited with `:ref{id="…"}` or placed with `::resource{id="…"}`. - **Blank lines around every block.** An ordered list or a `::resource` glued under a paragraph line is swallowed into that paragraph. A `$$` display glued there interrupts it instead (postext ≥ 1.5), and text right under its closing `$$` continues the paragraph, flush. - **One line per list item**, 2 spaces per nesting level, real numbers typed. - **Escape `* _ ^ ~ $`** in literal text, including intraword `_` and prices (`\$5`). - A paragraph starting with `- `, `1998. `, `# ` or `> ` becomes a list, heading or quote. Use `—` for dialogue; prefix U+2060 otherwise. - **Inline marks do not nest** the CommonMark way: write `**a** ***b***`. Marks in headings print as in a paragraph (`headings.inlineMarks`, on by default since configVersion 6); an italic run in an italic heading comes out upright. - The containers are `:::callout`, `:::paragraphs`, `:::part`, `:::columns` (in a box, or in the running text from postext 1.25), `:::paper` (a run of pages on another paper stock, for the Folio viewer), `:::exercise` (with `:::solution` and `:::hint` inside; `[[answer]]` blanks are read only there) and, on composed pages, `:::frame`. The directives are `:::pagebreak`, `:::numbering`, `:::columnbreak`, `:::space`, `:::toc`, `:::index`, `:::bibliography` and `:::solutions`. The fenced raw-body blocks are `:::references` and `:::verse` (a poem: line by line as written, or a classical Arabic poem, one bayt a line split at `||`), plus inline index marks (`:index[…]`, `:index{term="…"}`), and the comic blocks `:::page` and `:::strip` (comics.md); on composed pages (postext ≥ 1.34) the four-colon `::::page{template}` with `:::frame{slot}`, `:::figure` + `::label` and `slot=` (document-format.md §12.2). Anything else prints literally. - **Extra blank lines add no space.** Where the source has deliberate vertical space (a scene break, room above a signature), write `:::space` (one body line) or `:::space{lines=N}`. - **Line breaks inside a paragraph** (postext ≥ 1.23): end the source line with a backslash, or write `\\` and a space, for an address, a letter's greeting or a signature block kept in one paragraph (document-format.md §3.3). Two trailing spaces are no break. Write a poem as `:::verse`, one line of verse a line, a blank line between stanzas, leading spaces for its indents (§12). - **Tabs** (postext ≥ 1.23): write `:tab` where the source aligns text at a stop (a price, an actor, marks at the margin), and give the paragraph's style `tabStops` (document-format.md §10.9, configuration.md §4a). A tab character is a tab only in a paragraph whose style has stops; elsewhere it is a word space. - **Drop caps** (postext ≥ 1.23): a chapter's first paragraph stays in the text, and its drop cap, raised initial or small-capital first words come from the heading level's (or heading style's) `dropCap`; a catalogue's entries from a paragraph style's `dropCap` with `each: true` (configuration.md §4b). `# Title {dropcap=false}` leaves one chapter without it. Never copy the opening words into a `lead` attribute drawn by a design text.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub