Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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.
Expand Down
28 changes: 23 additions & 5 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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.

Expand All @@ -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.
Expand Down Expand Up @@ -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:

Expand Down
124 changes: 121 additions & 3 deletions lib.typ
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,7 +14,6 @@
set text(
size: 12pt,
font: (
"Courier",
"Courier New",
"DejaVu Sans Mono",
),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
6 changes: 3 additions & 3 deletions typst.toml
Original file line number Diff line number Diff line change
@@ -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",
Expand Down