Add Fountain screenplay projects, exported straight to PDF
A screenplay is its own project rather than a file type mixed in with prose, so this is one switch in Manuscript details, not a second file extension threaded through order.rs and the file panel. Files stay `.md` and keep the same editorial header; only the draft below the marker is read as Fountain. They list, reorder and header-strip exactly as before. src/fountain.rs parses the format. Almost nothing in Fountain is marked up -- what makes a line a character cue is that it is in capitals with something directly beneath it, and what makes the same words a transition is a blank line below instead. The forcing characters are all there for where that is not enough, along with notes, the boneyard and inline emphasis. Sections and synopses are parsed and kept but never printed: they are the writer's scaffolding. src/pdf.rs writes the PDF by hand, for the reason odt.rs writes ODT by hand -- no pandoc, no LibreOffice at runtime. It costs no dependency either: a screenplay is set entirely in Courier, which is one of the fourteen faces every reader must provide, so there is no font to embed and no metrics to parse. Streams are left uncompressed; a feature script is a few hundred kilobytes that way and stays readable when something needs debugging. src/screenplay.rs does layout. The geometry is the conventional one -- 55 lines of 12pt on US Letter, action at 1.5", dialogue 2.5", parentheticals 3.1", cues 3.7", transitions flush to 7.5" -- because a page only reads as a minute of screen time if it is. A speech broken by a page boundary is marked (MORE) and resumed under a repeated cue, a `^` cue sets two speeches side by side, and a scene heading is never left stranded at the foot of a page. Two faults the tests missed and measuring the rendered PDF caught. A hard-wrapped action paragraph was getting a blank line between every source line, which on the page reads as a beat the writer never wrote; consecutive lines are now one paragraph, with the breaks kept. And reserving one line after a scene heading did not stop it stranding, because every element that can follow a heading is separated from it by a blank -- the reserve has to cover both. Both now have tests. Verified beyond the unit tests: pdffonts confirms base-14 Courier with nothing embedded, pdftotext -bbox puts every indent within a hundredth of an inch of standard, and the export was driven through the real UI against a scratch workspace and an isolated config. Not built, because they were offered and never asked for: rendering Fountain in the Preview pane, and exporting a manuscript as a .fountain file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Bq2fUZNgksdPp3zeHqzSw
This commit is contained in:
@@ -150,6 +150,12 @@ pub struct Config {
|
||||
/// agent or editor expects a submission in.
|
||||
#[serde(default)]
|
||||
pub manuscript_standard_format: bool,
|
||||
/// Whether this project is a screenplay. The files keep the same editorial
|
||||
/// header as a prose project, but the body below the draft marker is read
|
||||
/// as Fountain rather than markdown, and the manuscript exports as a
|
||||
/// paginated screenplay PDF instead of an ODT.
|
||||
#[serde(default)]
|
||||
pub manuscript_screenplay: bool,
|
||||
}
|
||||
|
||||
/// The toolbar and the file list are what the window opens with; hiding either
|
||||
@@ -302,6 +308,7 @@ impl Default for Config {
|
||||
manuscript_contact: String::new(),
|
||||
manuscript_title_page: false,
|
||||
manuscript_standard_format: false,
|
||||
manuscript_screenplay: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,6 +483,24 @@ mod tests {
|
||||
assert_eq!(joined, PathBuf::from("/tmp/The Winter Gate/06-First Draft"));
|
||||
}
|
||||
|
||||
/// A config.json written before screenplay projects existed. Same risk as
|
||||
/// the tests above: a missing `#[serde(default)]` would fail the whole
|
||||
/// parse and reset every setting the user had.
|
||||
#[test]
|
||||
fn config_without_the_screenplay_field_still_loads() {
|
||||
let old = r####"{
|
||||
"workspace": "/home/writer/Manuscript",
|
||||
"export_path": "/home/writer/Manuscript/book.odt",
|
||||
"manuscript_author": "A. Writer",
|
||||
"manuscript_standard_format": true
|
||||
}"####;
|
||||
let cfg: Config = serde_json::from_str(old).expect("old config must still parse");
|
||||
assert_eq!(cfg.manuscript_author, "A. Writer");
|
||||
assert!(cfg.manuscript_standard_format);
|
||||
// A project is prose unless it says otherwise.
|
||||
assert!(!cfg.manuscript_screenplay);
|
||||
}
|
||||
|
||||
/// A saved subfolder choice must win over the built-in default, so changing
|
||||
/// the default never silently moves an existing user's projects.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user