diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3dde5d2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## 0.2.0 - 2026-09-19 + +This release is maintained in the fork [izzyboyno/typst-screenplay](https://github.com/izzyboyno/typst-screenplay) and is based on the original project by [Daniel F.H.](https://github.com/danielFHcode). + +### Added + +- Added the `fountain` helper for converting basic Fountain screenplay text into Typst screenplay layout. +- Added support for Fountain title, author, credit, and date metadata. +- Added support for scene headings, dialogue, transitions, and centered notes. + +### Changed + +- Replaced the single `Courier` default with a portable `Courier New` / `DejaVu Sans Mono` fallback stack. +- Documented how to select a specific screenplay font. +- Preserved compatibility with the existing `capitalized-headings` option spelling while documenting the canonical `capitalize-headings` name. diff --git a/README.md b/README.md index e261474..628f8e2 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,18 @@ A template for writing a film/TV script in screenplay format. This is a mix of the format used by final draft and the official format suggested by the oscars, with some slight changes. +This project is a fork of [danielFHcode/typst-screenplay](https://github.com/danielFHcode/typst-screenplay), originally created by Daniel F.H. The upstream project and author are credited here; this fork adds the changes documented in [CHANGELOG.md](CHANGELOG.md). + ## Usage -> Before you start please make sure to download and install either the font `Courier` or `Courier New`. +The package defaults to a Courier-like fallback stack (`Courier New`, `DejaVu Sans Mono`), but if you want the classic screenplay look, install `Courier` or `Courier New` and set it explicitly: + +```typ +#set text(font: "Courier New") +``` ```typ -#import "@preview/its-scripted:0.1.0": * +#import "@preview/its-scripted:0.2.0": * #show: screenplay.with() @@ -78,6 +84,18 @@ Suddenly a PTERODACTYL flies through the coffee-shop window. #close[TO BE CONTINUED] ``` +## Fountain support + +Fountain markup can be converted into screenplays with the `fountain` helper. + +This release includes the initial Fountain import helper and improved font fallback handling for broader compatibility. + +```typ +#import "@preview/its-scripted:0.2.0": fountain + +#fountain("Title: Example\nAuthor: Jane Doe\n\nINT. OFFICE - DAY\n\nJANE\nHello there.\n") +``` + ## Documentation Check [docs.md](docs.md) for the full documentation. diff --git a/docs.md b/docs.md index 4ec53e9..05501ba 100644 --- a/docs.md +++ b/docs.md @@ -2,9 +2,13 @@ ## Fonts -Please make sure to download and install either the font `Courier`, `Courier New`, or some other Courier clone, because the builtin mono-spaced fonts on typst are horrific. +The package defaults to a Courier-like fallback stack (`Courier New`, `DejaVu Sans Mono`) so it works across common systems. If you want the classic screenplay look, install `Courier` or `Courier New` and set it explicitly: -`Courier` and `Courier New` are natively supported, but you can use another font by inserting a `set` rule like so: +```typ +#set text(font: "Courier New") +``` + +You can also use another monospace font by inserting a `set` rule like so: ```typ #show: screenplay.with() @@ -20,14 +24,14 @@ Usage: ```typ #show: screenplay.with( - capitalized-headings: true, + capitalize-headings: true, dir: ltr, ) ``` Named arguments: -- `capitalized-headings: bool` +- `capitalize-headings: bool` Determents whether or not to auto-capitalize all of the headings in your document. @@ -43,6 +47,20 @@ Named arguments: **Default:** `text.dir`, or `ltr` if `text.dir` has not been specified. +## The `fountain` Function + +Converts a basic Fountain screenplay into the package's standard screenplay layout. + +Usage: + +```typ +#import "@preview/its-scripted:0.2.0": fountain + +#fountain("Title: Example\nAuthor: Jane Doe\n\nINT. OFFICE - DAY\n\nJANE\nHello there.\n") +``` + +The converter understands basic Fountain metadata (title, author, credit, and date), scene headings, character cues, dialogue, transitions beginning with `>`, and centered notes starting with `@`. + ## The `maketitle` Function Creates the title page for your screenplay. This automatically inserts a page break after the title, so you don't have to start a new page manually. @@ -125,7 +143,7 @@ Any additional positional arguments will be added bellow the draft. Scene headings are simply regular document headings. The level of the heading doesn't matter, as all headings are set to the same text size (though you should choose your heading levels consistently for improved code readability and accessibility). -When the `capitalized-headings` option in the `screeplay` function is set to `true`, headings are automatically capitalized. +When the `capitalize-headings` option in the `screenplay` function is set to `true`, headings are automatically capitalized. Usage: diff --git a/lib.typ b/lib.typ index b4d0754..6ccdb4f 100644 --- a/lib.typ +++ b/lib.typ @@ -1,4 +1,9 @@ -#let screenplay(it, capitalize-headings: true, dir: auto) = context { +#let screenplay(it, capitalize-headings: true, dir: auto, ..args) = context { + let extra = args.named() + let should-capitalize = capitalize-headings + if extra.at("capitalized-headings", default: none) != none { + should-capitalize = extra.at("capitalized-headings") + } let dir = dir if dir == auto { dir = text.dir @@ -9,7 +14,6 @@ set text( size: 12pt, font: ( - "Courier", "Courier New", "DejaVu Sans Mono", ), @@ -45,7 +49,7 @@ show heading: set text(size: 12pt) show heading: set block(above: 1em, below: 1em) show heading: it => { - if capitalize-headings { + if should-capitalize { upper(it) } else { it @@ -212,3 +216,117 @@ ), ) } + +#let fountain(body) = { + if type(body) != str { + return body + } + + let source = body + let lines = source.split("\n") + let pieces = () + let metadata = (:) + let i = 0 + + while i < lines.len() { + let line = lines.at(i).trim() + if line == "" { + i += 1 + continue + } + + if line.contains(":") and not line.starts-with(" ") and not line.starts-with("\t") { + let parts = line.split(":") + let key = parts.first().trim() + let value = parts.slice(1).join(":").trim() + if key != "" { + metadata.insert(lower(key), value) + i += 1 + continue + } + } + + break + } + + while i < lines.len() and lines.at(i).trim() == "" { + i += 1 + } + + let title = metadata.at("title", default: none) + let authors = metadata.at("author", default: metadata.at("authors", default: none)) + let date = metadata.at("date", default: metadata.at("draft date", default: none)) + let info = metadata.at("credit", default: metadata.at("source", default: none)) + + if title != none or authors != none or date != none or info != none { + pieces.push(maketitle( + title: title, + authors: authors, + date: date, + info: info, + )) + } + + while i < lines.len() { + let line = lines.at(i).trim() + if line == "" { + i += 1 + continue + } + + if line.starts-with(">") { + let transition = line.slice(1).trim() + if transition != "" { + pieces.push(close(capitalize: false)[#transition]) + } + i += 1 + continue + } + + if line.starts-with("@") { + let note = line.slice(1).trim() + if note != "" { + pieces.push(block(align(center)[#note])) + } + i += 1 + continue + } + + if line.starts-with("INT.") or line.starts-with("EXT.") or line.starts-with("INT/") or line.starts-with("EST.") or line.starts-with("I/E") { + pieces.push(heading(line)) + i += 1 + continue + } + + if line == upper(line) and line.contains(" ") and not line.starts-with("#") and not line.starts-with("!") { + let speaker = line + let dialogue = () + i += 1 + while i < lines.len() { + let next-line = lines.at(i).trim() + if next-line == "" { + i += 1 + break + } + if next-line == upper(next-line) and next-line.contains(" ") and not next-line.starts-with(">") and not next-line.starts-with("@") { + break + } + dialogue.push(next-line) + i += 1 + } + pieces.push(dialog[ + == #speaker + + #dialogue.join("\n") + ]) + continue + } + + pieces.push(block[#line]) + i += 1 + } + + for piece in pieces { + piece + } +} diff --git a/typst.toml b/typst.toml index 4b927cc..a2c8061 100644 --- a/typst.toml +++ b/typst.toml @@ -1,11 +1,11 @@ [package] name = "its-scripted" -version = "0.1.0" +version = "0.2.0" entrypoint = "lib.typ" -authors = ["Daniel F.H."] +authors = ["Daniel F.H.", "izzyboyno"] license = "LGPL-3.0-only" description = "A template for writing movie/tv/theater-scripts in screenplay format." -repository = "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/danielFHcode/typst-screenplay" +repository = "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/izzyboyno/typst-screenplay" keywords = [ "screenplay", "movie-script",