Repository navigation
Releases: jonmichaels/ctrl-kd
Release list
v4.10.1
Fixed
-
-t markdown --pictures embednow exports and links the pictures, as
-t mdalways did. The command line checked the format name as typed, so
the long name skipped the picture export and the degradation note that the
short one gave. It now checks the format itself, whichever name was typed. -
A Printed document with footnotes, endnotes or annotations now lays out
exactly like one without, apart from the note area itself. The two had
separate paginators, and the one for notes had drifted: it ignored.psb/
.psa, the.pmfirst-line indent and the per-line.srroll, dropped a
print control's declared width when a heading restyled it, kept columns
only until the first wrap, never resolved the even/odd page offset
(.poe/.poo), stamped no per-page margin, length or orientation, and
gave every page the document's last running head instead of the one in
force on it. There is one paginator now; the note area (growth, the
three-line floor, continuation, endnotes at the end) is a feature it runs
when the document has notes. A note-bearing document'slayoutJSON now
carries each page's own running head and foot, and its trailing blank lines
are stripped like any other page's. A block that opens on a blank line
enters its first text line at the style's own leading, notes or not
(measured against real WordStar 7 on LYING.WS).
v4.10.0
Added
-
A page can declare its own sheet.
.pr or=(landscape/portrait)
measured against real WordStar 7: set at the top of a page it applies to
that page immediately; set in the middle of a page it waits for the next
page boundary, exactly as WS7 does. The Printed PDF now draws each page on
its own sheet size instead of one page size for the whole document, and
the running head/foot and automatic page number follow that page's own
height (a landscape page's number no longer resolves off the top of a
portrait document).layoutJSON version 13 publishes a page's own size
(width/height/orientation) only where it differs from the document's — the
same "omit unless it differs" rule every other per-page field here already
follows; a page that never changes size emits nothing new. Modern is
unchanged by design — it composes on one reflowed sheet regardless of what
the source document's pages asked for. -
The
layoutJSON says when a running head, foot or page number is off the
paper (version 12). WordStar commands such a row anyway and lets the printer
clip it — real WordStar 7 prints put the automatic number 14.4pt below the
bottom edge of a Letter sheet when.mbis smaller than.fm— so the PDF is
unchanged and still draws it there, with the page as its clip. A program
drawing pages from this JSON has no paper to clip with, so such a row now
carriesoff_sheet: trueand must not be drawn. The key is absent, not false,
on every row that is on the sheet.
Fixed
-
Page furniture is set in the document's own face, not in Times. Running
heads, running feet, the automatic page number and footnote/endnote text were
written in a hardcoded face and size in the RTF exports and in the Modern PDF
— Times New Roman 11 on a Printed page whose body is Courier 12, on every page
of a running-head document. They resolve through the same font chain body text
does now: a.h#/.f#that opens a font block prints in THAT face and size
(22 documents in the public test corpus, which the Printed PDF has always
honoured and the other three surfaces threw away); a head that declares
nothing takes the mode's body face — Courier in Printed, the reading face in
Modern, Courier in a document that declares itself non-proportional. Sizes
follow the same rule: Printed furniture is the body's size, Modern furniture
is the body size less 2pt (12pt against Modern's 14pt body, where the RTF and
the Modern PDF both used 11). Printed PDF is unchanged — it was already right. -
Modern: a list bullet and its text no longer read as one crowded word.
The square bullet was drawn on the same fixed-pitch cell Printed uses, but
the space after it was measured in the reading face — 3.5pt against the
cell's 7.2 — so the item's first word sat about half as far from the bullet
as it does in the Printed and Native views. It is now one cell, as it is
there: the first word starts two cells past the bullet in all three views.
Also fixed in the Modern RTF's hanging indent, which had computed a third
number again. Only bullets whose marker is a cp437 block glyph are
affected; a*or-marker is a real letter in the reading face and
keeps the spacing it had. -
Markdown: text that used to vanish now survives. A document that
literally says<SP>,<Enter>or<B>had that read as an HTML tag by
every Markdown renderer, which showed NOTHING — the word disappeared.
(<B>was worse: a real tag, switching bold on with nothing to switch it
off.)<and&are escaped now. Affects Sawyer's macro and patch
documentation heavily; none of the shipped samples. -
Markdown: strikeout no longer breaks where a bold or italic word sits
inside it. The emitter closed the strikeout and reopened it with nothing
in between (~~alpha~~~~**beta**~~~~gamma~~); four tildes are not
strikethrough in any flavour, so the strike was lost AND the tildes showed
up as text. The run is merged now. -
Markdown: a verse or stanza containing a blank line no longer emits a
line of invisible whitespace. The two-trailing-spaces hard break turned
such a blank into a line holding just two spaces, which CommonMark reads as
a blank line — so the paragraph split anyway, into pieces whose raw text
claimed otherwise. A blank line ends the unit now, which is what it always
rendered as. The dangling trailing spaces at a unit's end go too.All three are Modern-mode Markdown only. A Printed-mode body, and any
print-stream or ruler-driven document in either mode, is a fenced code
block whose content is literal and is untouched. -
ctrl-kd --versionno longer prints a PythonSyntaxWarningahead of its
own output. A docstring cited RTF's\margrcontrol word inside a plain
(non-raw) string, so Python read\mas an escape sequence it did not
recognise and warned about it. Nothing was broken, but the warning named a
file inside the package and was the first thing a new Homebrew or PyPI user
saw, which reads like a crash. It appeared only on a COLD bytecode cache —
the first run after an install — which is why no development machine ever
showed it. Every module is now compiled with warnings as errors by both the
test suite andtools/run-full-suite.sh, so it cannot come back. -
Printed RTF's text column is the document's own ruler width, not a
fixed arithmetic off the page margins. The column had been computed as
paper width minus twice the left margin, which has nothing to do with what
the author actually typed the page to hold; at the ordinary default it came
out one character narrower than a common 69-character line, so thousands of
facsimile lines across the public corpus arrived re-wrapped by the reader —
the one thing Printed mode exists to prevent. The column now follows the
widest ruler in the document (or the longest printed line itself, when box
art or a print stream runs past its own ruler), and the page grows to fit
it, up to Word's own maximum page size. A document whose lines are wider
than even that still re-wraps in the reader — there is no page any reader
accepts that would hold them, and that group shrank from 132 documents to
32 in a full sweep. -
A reverse-video banner ("white on black") is visible in RTF and HTML,
not white text on a white page. Both exporters kept WordStar's white
colour but never painted the black ground behind it, so the words were
simply not there — three documents in the public corpus lost a banner this
way, one of which says outright in its own text that it should read white
on black. The ground is drawn now; turning off inline styling removes it
along with the colour, same as it always has for every other colour. -
A Printed HTML page is no longer twice as tall as the document. The
facsimile block already breaks a line on its own newline; the emitter was
also writing a<br>at the end of every line, so each line was followed
by a blank one. A 57-page document rendered at double height with its line
grid gone. The extra break is removed. -
Newspaper-style columns (
.co) render as one column block per section,
in both Printed and Modern HTML, not one box per paragraph. Modern had
been opening a fresh pair of columns for every paragraph in a columnar
region, which balances each paragraph on its own and leaves ragged,
half-empty columns underneath; Printed dropped the columns entirely. Both
now open a single container for the whole section, closing it only where
the document itself ends the columns. -
A Modern HTML page no longer scrolls sideways on a phone. The Modern
stylesheet had no line-length limit at all — a wide window gave a line
roughly twice a comfortable reading length, and at phone width a long path,
a run of control words, or a wide row of block graphics pushed the whole
page off the edge of the screen. Modern HTML now keeps a reading-width
column and lets only the one unbreakable row (an embedded picture) scroll
inside itself, the way the Printed facsimile already does. Printed HTML is
untouched — it was already narrow enough.
Changed
-
Every quirk was renamed, and the names that shipped in 4.9.0 still work.
A quirk's name is text a reader reads and types —--list-quirksprints it,
--quirk/--no-quirktake it, thelayoutformat publishes it — so the six
now say what they do in plain words instead of naming the printer driver or
the code behind them:4.9.0 now driver-euro-signeuro-swaplj6dtp-typographysmart-punctuationlj6dtp-box-cornersbox-cornerslj6dtp-colour-as-graycolors-as-graylj6dtp-fill-patternsfill-patternsstray-style-strikeoutsawyer-strikeoutThe old names are still ACCEPTED wherever one can be typed or stored, so
nothing anyone has scripted or saved stops working. They are never PRINTED:
--list-quirks,quirks_applicable/quirks_appliedand every report say the
new name only, so one output can never show both spellings for one quirk. -
Descriptions and the quirk window's own row titles use US spelling: "Screen
colors as gray", "Colors 9–14 as hatch patterns". -
--headersand--page-numbersnow say outright, in their own--help
text, that they apply to PDF and RTF only. Text, Markdown and HTML are
unpaged by design and never carried running heads, feet or page numbers;
the flags are still accepted (and ignored, with no error) on those three
formats — nothing about how they convert has changed.
3,325 answer-key cells were re-recorded across this release's commits (a cell
can move more than once as later commits build on earlier ones): 796 for the
per-page-sheet schema bump (792 layout cells at the version number alone,
one document's pdf.printed/rtf.printed pair for its actual landscape
page, and that same d...
v4.9.0
A minor release: one new feature, three fixes.
Quirks mode. Some WordStar documents were printed through a printer
driver that was itself patched to depart from the file's literal bytes —
printing a euro sign where the byte says peseta, for instance, or drawing
card suits as box corners. ctrl-kd already reproduced these; now they're
named, and you can see and control them. --list-quirks lists every known
quirk; --list-quirks FILE says which apply to a given file and why;
--quirk NAME / --no-quirk NAME turn one on or off; --quirks off|auto|all sets the baseline. Six ship with this release — five on by
default (the file's own printer header is the evidence for them), one off
by default (ignoring a strikethrough set only by a paragraph style, never
by the writer). The layout output format now reports which quirks apply
to a document and which fired.
Fixes. A paragraph-style selection written at the very end of a line
was lost whenever the next line was a formatting command — a manuscript's
book list could print in the wrong typeface and spill onto an extra page,
with the rest of the document coming out right-aligned. Both fixed, and a
document's own explicit line-height setting now correctly overrides a
style's, matching WordStar. Strikethrough is now drawn as one continuous
line across each struck run of text, spaces included, rather than one
short stroke per word.
824 answer-key cells were re-recorded for this release: 792 for a
purely additive layout schema change (nothing else moved), and 32 for
the two layout/print fixes above.
Full changelog: v4.8.1...v4.9.0
v4.8.1
A patch release fixing two things.
A style whose typeface was an ordinary face — Courier, for one — but whose
character-set bits said "math" printed its whole text in the Symbol font:
Greek letters instead of words, in every export. A resolved typeface now
wins; the character-set bits only decide when the typeface itself is
unknown.
Strikethrough declared by a paragraph style now stays on until a later
style turns it off, which is what WordStar 7 prints — it previously ended
with the styled paragraph. Documents whose style sheet sets strikethrough
and never clears it now print struck through to the end, as on paper. An
opt-in "quirks" mode to read such documents cleanly is planned for the next
release.
40 answer-key cells in 5 reference documents were re-recorded for these two
fixes.
Full changelog: v4.8.0...v4.8.1
v4.8.0
Modern view, Modern PDF and Modern RTF now keep the page the document itself declares: landscape sheets, newspaper columns, and short sheets for envelopes, labels and Rolodex cards — which used to come out blank. Modern's right margin mirrors its left, instead of being forced to a flat inch.
Modern shows WordStar's automatic page number again, wherever the Printed view does, and running heads and footers land where the document aligns them: right, centre, or two on one row at opposite edges.
The automatic page number is now placed from the print offset in force on the page being numbered, so it sits in the right column on landscape templates and on documents that move the offset part-way through. Its row — and a footer's own text row — are counted in WordStar page lines, so numbers no longer vanish from documents set at more than 12pt leading.
A plain paragraph that happens to open "Note: " is no longer turned into a hanging definition-list entry; a definition list now needs at least two entries with different terms.
Documents that turn page breaks off with .pl 0 are written to RTF on a Letter page instead of a zero-height one that some word processors refuse to open.
Turning running heads off (--headers off) now applies to Modern PDF as well as Printed; the automatic page number stays governed by --page-numbers alone.
Full list in CHANGELOG.md.
v4.7.1
A patch release fixing one thing. A document that used footnotes and also
asked for no page numbers got a page number printed at the foot of every page
anyway — real WordStar 7 prints none on such a document.
The fix reaches the PDF, RTF and HTML exports and the page-layout views
alike. Nothing else changed since 4.7.0.
Full changelog: v4.7.0...v4.7.1
v4.7.0
This release finishes what 4.6.0 started: RTF and HTML now carry the
same page-level behaviour PDF got last time (page numbers, facing
headers/footers, columns, keep-with-next, matching tabs/indents, Modern
spacing) — checked against a real word processor and a real browser, not
just our own PDF. Plus another round of printed-page fixes measured
against real WordStar 7 (auto-leading, print-time reflow, header/footer
timing, comment lines, form-feed formatting loss, the driver's character
substitutions including the euro), and a new MailMerge data-file reader.
Full itemized list in the CHANGELOG.
Measured against real WordStar 7: named divergences against real WS7
LaserJet printouts down from 10,576 to 3,682; 132 of 194 test documents
now print byte-identical to the original.
Known limit carried forward: RTF columns/keep-with-next/sections are
Printed-view only — Modern has no column model, by design.
Full changelog: v4.6.0...v4.7.0
v4.6.0
ctrl-kd is the Python reference engine that reads WordStar 4–7 documents and
converts them to text, Markdown, HTML, RTF and PDF. This release is a large
accuracy pass on printed-page layout, measured page by page against real
WordStar 7 output, plus a set of Modern-view layout rules brought across from
the Soft Return app so that the app and the engines lay out the same lines.
Note before you upgrade: the RTF and HTML emitters do not yet carry the
new page-level behaviour described below. They are correct as text; the page
model is PDF and the on-screen views for now. That work is scheduled next.
Modern layout — the app's rules are now the engine's rules
The Soft Return app's "Modern" reading view had a number of typographic
refinements the engines did not share, so the same document could break lines
differently on screen and in an exported PDF. Those rules now live in the
engine:
- Verse and centred lines tighten up. Poetry, song lyrics and centred
display lines now use the same reduced line height the app uses, with the
matching bit of extra air above the first tightened line, instead of full
body leading on every line. - Definition lists hang properly. A definition-style row now hangs its
label at the left margin with the body text and every continuation line
aligned under the start of the body, across the full measure. Bulleted rows
hang on the bullet's own cell. - A one-sided left-margin change is no longer treated as a block indent.
A.lmwith no matching.rmused to push a paragraph a further inch and a
half into the page in Modern; it now sits at the margin, the way the printed
page shows it. - The page baseline model matches the app. The first line of every Modern
page now starts flush at the top margin rather than a full line below it, and
a page fits while the line's box is inside the margin. On documents that mix
type sizes this was worth a whole line per page. - Line height is measured from the font's real design metrics — corrected
natural line height for the body face, real vertical metrics from the font
tables, and a blank line following a tightened line keeps body leading
instead of inheriting the tightened value. - Centring that a document implies rather than declares is now detected and
centred. - Rows that must not wrap no longer wrap — legend rows, chart rows and
similar fixed-shape lines are kept on one line, and a colon inside such a row
is no longer taken as a break opportunity. - Line width is computed the same way the reference implementation computes
it, so a line that fits in one engine fits in the other. - Box and rule characters advance by the document's own fixed-pitch cell,
not by the size of the face they happen to be drawn in — rules built from
box-drawing characters now end flush at the margin instead of running off the
page. - Runs no font block covers keep the reader's face, and honour
"proportional spacing off". - Endnotes start a new page when the last page of the body already carries
footnotes. - Greek, mathematical and dingbat characters no longer fall back to a
question mark in Modern PDF, and box characters inside a symbol-mapped run
draw as real shapes.
The euro, and the peseta
Code 158 in the DOS character set is the peseta sign. Some WordStar 7 printer
definitions were patched in the late 1990s to print a euro there instead — a
well-known trick, documented in WordStar's own release notes. ctrl-kd now
models it the way WordStar did: if the document's last-used printer is one of
the three patched LaserJet definitions, code 158 prints as €; otherwise it
prints as the peseta ₧ — drawn as a real glyph where the base font set has no
slot for it. This replaces a blanket "always euro" fallback that turned genuine
peseta amounts in older documents into euros. The rule applies to body text and
to running heads and feet alike.
Printed-page layout, measured against real WordStar 7 printouts
- Newspaper columns (
.co) are implemented in printed PDF layout: columns
as wide as the block's right margin, filled down and then across, no
balancing, and leaving a column region breaks the page — WordStar's own
behaviour, verified against captures. - Full justification (
.oj on) is implemented, with the extra space split
between gaps the way WordStar splits it. - Even/odd page handling.
.h1e/.h1o/.f1e/.f1oconditional headers
and footers were never recognised at all (the pattern matched two of three
letters); they now work, and the even/odd page offset.poe/.pooreaches
the running head and foot, not just the body. - Line numbering (
.l#) is positional — a document can turn numbering on
and off down the page and the gutter numbers follow — instead of one flat
setting for the whole file. - Landscape pages. A page length that does not match a named paper size no
longer produces a square, clipped page; the document's own width is honoured. - Leading. A paragraph style's own line height now outranks a stale
document-wide setting; automatic leading inside a font block has a ceiling and
never governs its own line; page capacity prices each line at its real height
rather than the document default. - Indents. A typed run of leading spaces indents by the natural width of a
space when no paragraph-margin rate is in force, and by the paragraph margin
when one is; a style-driven first-line indent no longer double-counts a typed
one. - Tabs. A literal tab byte round-trips and expands to the next column stop
at layout time; a run of spaces before a tab shifts that stop correctly; a
ruler line (.rr) resets left, paragraph and right margins, not only the tab
stops, and consumes its own ruler-image line. - Headers and footers honour an embedded paragraph-style selection for
alignment, font and attributes, re-apply bullet substitution on the fast path,
and track a page offset only when it is a real page-geometry change. - Conditional text.
.if/.el/.eiare evaluated for constant
conditions, and a dot command immediately after a form feed is recognised. - Page breaks.
.cb/.cc/.cpare honoured in the footnote-aware
paginator; a trailing.paopens a new page only when real content follows;
.fiprints nothing when its target cannot be resolved. - Superscripts and subscripts in proportional text keep the surrounding
block's pitch and change only the drawn size, matching WordStar. - Pictures are included by default and sit at the right vertical offset,
with the descent gap before the image WordStar leaves.
Reading printer output
The tooling that decodes captured printer streams now tracks symbol-set
selection, so curly quotes, the copyright sign and Symbol-font runs decode
correctly; it implements transparent print data; it unwraps a vertical-position
overflow WordStar itself emits; and page-size and paper-source commands eject a
page. Four more WordStar typeface identifiers are mapped.
Test corpus and manifests
- Test material that was never meant to ship no longer appears anywhere in the
public test suite or tooling, and the public manifest no longer records stray
files found in a corpus tree. - Documents that cannot meaningfully be compared against a printout are now
excluded by name and by class rather than silently failing: mail-merge data
and format files, PostScript-driver documents, documents with form feeds
turned off, duplicate copies of the same document, and one degenerate file
that declares a zero-length page. - Coverage of the printed-output comparison expanded to a much larger capture
round, and the shared cross-engine answer key now covers the full corpus
rather than a hand-picked subset. - Tier-1 tests run on every push and pull request.
Other
- Plugin loading works again on Python 3.9.
- One environment variable per corpus, one shape each.
Known issues
- RTF and HTML exports do not yet carry the new page-layout rules. They are
correct as text, but the page-level behaviour added in this release — default
page numbers, even/odd page headers, page capacity, newspaper columns, tabbed
tables and the Modern verse tightening — is not reflected in those two formats
yet. PDF and the layout model are unaffected. Scheduled for the next release. - The printed-output comparison set grew faster than it has been triaged.
This release expanded the set of real WordStar 7 printouts we compare against
from a few dozen pages to a few hundred. On a substantial number of the newly
added documents the printed PDF still differs from the original printout, and
those differences have not yet been sorted into "a substituted font is a
different width" versus "we place something wrongly". They are measured and
reported by name in the test output rather than hidden; classifying them is
the next round of this work. The two engines agree with each other on
every one of those documents. - Documents that declare printer fonts we substitute — the LaserJet's
scalable faces — can break lines slightly differently from the original
printout, because the substituted face's character widths are not identical.
Laying the page out with WordStar's own printer width tables is a planned
improvement. - On one page of one archived Sawyer test document, eight words sit about a
third of a point to the left of where the original printer placed them. It is
far below anything the eye resolves and is recorded rather than chased.
v4.5.1
Compatibility
- Fixed a crash on Python 3.9:
load_plugins()calledimportlib.metadata.entry_points()
with agroup=keyword that Python 3.9 doesn't support there. ctrl-kd now works on
Python 3.9 again, not just 3.10+.
Printed-mode fidelity
This release is measured against real, factory-default WordStar 7 output (not just our
own previous exports), and fixes a batch of small placement bugs found that way:
- Superscript and subscript now use the correct size and horizontal spacing for each font
family, instead of one fixed value. - Header and footer lines can now contain inline style toggles (bold/italic/underline
partway through the line) without corrupting the text. - Running heads now honor a page's own left/right offset instead of always using page 1's.
- Right-aligned tabs in headers and footers now recompute their padding on every page
(previously only correct on the page it was first calculated for). - Footnote reference markers now join correctly to their note text.
- Page numbers are now on by default, matching stock WordStar 7 (previously off unless
requested). - Automatic single-line spacing is now 1.0x, matching WordStar 7's own default (previously
1.2x, borrowed from a customized install). - Top margin now uses the document's
.mtsetting alone, instead of double-counting it
with the header margin. - The footnote area reserves the same vertical space stock WordStar 7 does.
- Running head lines now use the page's default margins correctly.
- Fixed an off-by-one in right-tab column placement.
- A typed paragraph indent combined with a style-driven
.pmindent is no longer
double-counted. - HTML output: fixed a rendering glitch where a tab-and-leader run in a centered row
(e.g. a symmetric table-of-contents line) produced an oversized gap instead of a
single space.
Testing infrastructure
- New coordinate-level test tier that checks PDF output against real WordStar 7 printer
output, page by page. - New tooling to track and record page-by-page visual review against real printed pages
(paper scans), so a reviewed page is a recorded fact instead of tribal knowledge. - CI now runs the test suite on every push, not just manually.
Known state at release
- Reduced (public) test suite: 846/846 passing.
- Coordinate-level fidelity tier: 14 of 18 captured documents match real WordStar 7
output exactly. 4 have named, unresolved residuals:LJ6DTP— parked by design; its own printing-hack features are out of scope for
this tier.LYINGandWARPRAYR— small residual font-metric drift from a substituted font
(ctrl-kd never embeds fonts; the base-14 substitute doesn't have identical metrics
to the original).-SCREEN— a test-harness limitation matching short Greek/math symbol fragments
across a font switch, not a rendering bug.
- Paper-scan visual review: 69 pages catalogued, all recorded as unreviewed pending
human review — tracked and testable, not silently skipped.
v4.5.0
First official release from the fresh public history. WordStar for DOS (v4–v7) to modern formats: Modern RTF/HTML/Markdown/text and Printed PDF facsimile. New since 4.x: sentence-spacing control (--sentence-spacing), bundled sample documents (--samples DIR), the public three-tier test suite with the committed Sawyer-archive manifest, and the documented era model (ERAS.md).