<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ShikiShiki</title>
    <description>The latest articles on DEV Community by ShikiShiki (@shikishiki_38182cf4a65257).</description>
    <link>https://dev.to/shikishiki_38182cf4a65257</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4104665%2Fb90fedf5-a6e4-43d8-9485-1da15b64a6f3.png</url>
      <title>DEV Community: ShikiShiki</title>
      <link>https://dev.to/shikishiki_38182cf4a65257</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shikishiki_38182cf4a65257"/>
    <language>en</language>
    <item>
      <title>Paginating a screenplay is a fixed-point problem</title>
      <dc:creator>ShikiShiki</dc:creator>
      <pubDate>Wed, 02 Sep 2026 15:22:14 +0000</pubDate>
      <link>https://dev.to/shikishiki_38182cf4a65257/paginating-a-screenplay-is-a-fixed-point-problem-cd8</link>
      <guid>https://dev.to/shikishiki_38182cf4a65257/paginating-a-screenplay-is-a-fixed-point-problem-cd8</guid>
      <description>&lt;p&gt;A screenplay page holds about 55 lines of 12 point Courier. Break the script every 55 lines and you have pagination.&lt;/p&gt;

&lt;p&gt;That works until a block of dialogue lands on a boundary. Then the format demands two extra things. The bottom of the page gets a &lt;code&gt;(MORE)&lt;/code&gt;, and the top of the next page repeats the character name with a &lt;code&gt;(CONT'D)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    SARAH
          I told you this would happen. I
          told you in the car, I told you
                    (MORE)

          - - - - - page break - - - - -

                    SARAH (CONT'D)
          at the door, and I am telling you
          now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two markers are content. They take up lines on pages you already measured. So the break you just computed was computed against a line count that is now wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layout depends on its own output
&lt;/h2&gt;

&lt;p&gt;Pagination looks like a chunking problem, so that is how everybody writes it the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the version everybody writes first&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the real function is not &lt;code&gt;layout(content)&lt;/code&gt;. It is closer to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;continuations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pages&lt;/code&gt; appears on both sides. You are not chunking a list, you are looking for a fixed point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one extra pass is not enough
&lt;/h2&gt;

&lt;p&gt;The obvious patch is to run the chunker, see which dialogue blocks got split, insert the markers, and run it again. Two passes, done.&lt;/p&gt;

&lt;p&gt;It is better. It is still wrong, because the second pass creates splits the first pass did not have. You added lines, everything after the first split shifted down by two, and something that used to end comfortably mid-page now hangs off the bottom. New split. New markers. New shift.&lt;/p&gt;

&lt;p&gt;The churn is worse than it sounds, because the other formatting rules also move content down and never up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A scene heading needs at least two lines of the scene under it. Alone at the bottom of a page, it moves to the next one.&lt;/li&gt;
&lt;li&gt;A dialogue block that breaks must leave at least two lines on each side of the break. One orphan line is not allowed.&lt;/li&gt;
&lt;li&gt;You cannot break between a character cue and their first line.&lt;/li&gt;
&lt;li&gt;You cannot break inside a parenthetical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those pushes a block forward, and each push can open a gap that lets a different block fit where it did not fit before, which pulls it back, which pushes the next one forward. Implement the rules as a straight loop with no discipline and you can watch it oscillate between two layouts forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterating to a fixed point, and making it stop
&lt;/h2&gt;

&lt;p&gt;The trick that makes this terminate is monotonicity. Track the set of dialogue blocks known to break, and only ever add to it. Never remove one because a later pass made it fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxPasses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;splits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxPasses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withContinuations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;splits&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findSplitDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// every split we saw is one we already knew about: fixed point&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;observed&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;splits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;observed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;splits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pagination did not settle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The termination argument is short. &lt;code&gt;splits&lt;/code&gt; only grows, it is bounded by the number of dialogue blocks in the script, and every pass that does not return adds at least one member. So the loop ends.&lt;/p&gt;

&lt;p&gt;You give up something for that guarantee. A block marked as split on pass two might genuinely fit on one page once everything settles, and you keep the &lt;code&gt;(MORE)&lt;/code&gt; anyway. In exchange you get a layout that is stable and reproducible, which matters more than saving one line, because two people opening the same script have to see the same page numbers.&lt;/p&gt;

&lt;p&gt;In practice it settles in three or four passes. If yours needs eight, one of the widow rules is fighting another one and the bug is there, not in the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why anyone should care about two lines
&lt;/h2&gt;

&lt;p&gt;Because the page count is not cosmetic. The convention is one page, one minute, and the industry budgets against that number: shooting days, crew, insurance, the schedule. A 110 page script is a 110 minute film until someone proves otherwise.&lt;/p&gt;

&lt;p&gt;Do the arithmetic on a dialogue-heavy script. Forty split blocks, two lines each, is eighty extra lines. At 55 lines a page that is a page and a half of runtime a single-pass paginator never reports. Not a rounding error. A day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking your own numbers
&lt;/h2&gt;

&lt;p&gt;I keep a few of these as browser tools, no account needed, mostly because I got tired of opening a full editor to answer one question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.screenweaver.ai/tools/script-time-calculator" rel="noopener noreferrer"&gt;Script time calculator&lt;/a&gt;, page count and runtime, for a second opinion on what your paginator says.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.screenweaver.ai/tools/screenplay-format-checker" rel="noopener noreferrer"&gt;Screenplay format checker&lt;/a&gt;, for the widow and orphan rules above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.screenweaver.ai/tools/fountain-to-pdf" rel="noopener noreferrer"&gt;Fountain to PDF&lt;/a&gt;, which runs the fixed point described here and shows the breaks it landed on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.screenweaver.ai/tools/scene-extractor" rel="noopener noreferrer"&gt;Scene extractor&lt;/a&gt;, to pull the element list out of an existing script if you need test data.&lt;/p&gt;

&lt;p&gt;If you are building a renderer and want to compare page breaks against another implementation, the Fountain converter is the useful one. Feed it something with long speeches near a boundary and see whether you agree.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>parsing</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Screenplay format is a spec, and most editors quietly break it</title>
      <dc:creator>ShikiShiki</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:38:19 +0000</pubDate>
      <link>https://dev.to/shikishiki_38182cf4a65257/screenplay-format-is-a-spec-and-most-editors-quietly-break-it-5e3e</link>
      <guid>https://dev.to/shikishiki_38182cf4a65257/screenplay-format-is-a-spec-and-most-editors-quietly-break-it-5e3e</guid>
      <description>&lt;p&gt;A screenplay looks like a badly indented document. It behaves more like a fixed-width data format that happens to be readable by humans.&lt;/p&gt;

&lt;p&gt;Developers keep assuming they can render one with a couple of CSS rules. Then a producer opens the PDF, the page count is off, and the page count is the number everybody downstream actually uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  One page, one minute
&lt;/h2&gt;

&lt;p&gt;The convention is that a correctly formatted page runs roughly a minute of screen time. It is a rough rule and it lies for action sequences, but the industry budgets against it anyway. A 110 page script is a 110 minute film until someone proves otherwise. Scheduling, shooting days, insurance, all of it hangs off that number.&lt;/p&gt;

&lt;p&gt;So formatting is not cosmetic. Courier at 12 point is a load-bearing decision. Swap in Arial and every line gets narrower, dialogue reflows, the script "becomes" 96 pages, and the estimate you just handed a line producer is wrong by a quarter of an hour.&lt;/p&gt;

&lt;p&gt;The format is a small set of elements, each with its own fixed indentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scene heading (&lt;code&gt;INT. KITCHEN - NIGHT&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;action&lt;/li&gt;
&lt;li&gt;character cue&lt;/li&gt;
&lt;li&gt;parenthetical&lt;/li&gt;
&lt;li&gt;dialogue&lt;/li&gt;
&lt;li&gt;transition (&lt;code&gt;CUT TO:&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six element types, hard indents, monospaced font, about 55 lines to a page. That is the whole spec, and it has barely moved in decades because too much money sits downstream of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a word processor is the wrong tool
&lt;/h2&gt;

&lt;p&gt;Word processors let you fake it. You set tab stops, you get something that looks right, then someone opens the file on a machine with different font metrics and the pagination shifts under them.&lt;/p&gt;

&lt;p&gt;The deeper problem is that the structure lives only in the indentation. Nothing in the file says "this line is a character cue." You cannot query it, you cannot diff it usefully, and you cannot pull a scene list out of it without writing a parser for whitespace.&lt;/p&gt;

&lt;p&gt;Final Draft solved this with &lt;code&gt;.fdx&lt;/code&gt;, which is XML and perfectly parseable. It is also proprietary and tied to a paid desktop app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fountain
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fountain.io" rel="noopener noreferrer"&gt;Fountain&lt;/a&gt; is the plain text answer. A &lt;code&gt;.fountain&lt;/code&gt; file is Markdown-shaped: readable as-is, unambiguous enough to parse, and it lives happily in git.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INT. KITCHEN - NIGHT

ROSE stands at the sink, not washing anything.

ROSE
(quietly)
You said you would be back by six.

MARCUS
I said I would try.

CUT TO:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules that carry most of the weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a line starting with &lt;code&gt;INT.&lt;/code&gt;, &lt;code&gt;EXT.&lt;/code&gt;, &lt;code&gt;EST.&lt;/code&gt;, &lt;code&gt;INT./EXT.&lt;/code&gt; or &lt;code&gt;I/E.&lt;/code&gt; is a scene heading, and a leading &lt;code&gt;.&lt;/code&gt; forces one&lt;/li&gt;
&lt;li&gt;an uppercase line preceded by a blank line and followed by a non-blank line is a character cue, and &lt;code&gt;@&lt;/code&gt; forces one&lt;/li&gt;
&lt;li&gt;parentheses directly under a cue make a parenthetical&lt;/li&gt;
&lt;li&gt;an uppercase line ending in &lt;code&gt;TO:&lt;/code&gt; is a transition, and &lt;code&gt;&amp;gt;&lt;/code&gt; forces one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; after a character name marks dual dialogue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/* */&lt;/code&gt; is a boneyard and &lt;code&gt;[[ ]]&lt;/code&gt; is a note, neither of which renders&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part that bites
&lt;/h2&gt;

&lt;p&gt;That character cue rule is where naive parsers fall over. Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SLAM.

The door rattles in its frame.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SLAM.&lt;/code&gt; is uppercase, sits after a blank line, and is followed by a non-blank line. Textbook character cue. It is action, and your parser just gave the door a speaking part.&lt;/p&gt;

&lt;p&gt;Fountain's own answer is the &lt;code&gt;!&lt;/code&gt; prefix to force action, which works when the writer remembers. Forgiving parsers stack heuristics on top: demote a cue that never speaks again, or one that is a single word ending in a period, or one whose dialogue block is suspiciously long. Every Fountain implementation I have read has some version of this, and they disagree at the edges.&lt;/p&gt;

&lt;p&gt;Pagination is the other trap. You cannot break a page in the middle of a dialogue block and walk away. You close with &lt;code&gt;(MORE)&lt;/code&gt;, reopen with the character name plus &lt;code&gt;(CONT'D)&lt;/code&gt;, and both of those consume lines, which can push you past the limit and force another break. It is a fixed-point problem hiding inside a layout engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to poke at it
&lt;/h2&gt;

&lt;p&gt;We put a few of these on the web, free and without an account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.screenweaver.ai/tools/script-time-calculator" rel="noopener noreferrer"&gt;script time calculator&lt;/a&gt;, the one page one minute estimate&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.screenweaver.ai/tools/screenplay-format-checker" rel="noopener noreferrer"&gt;screenplay format checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.screenweaver.ai/tools/fountain-to-pdf" rel="noopener noreferrer"&gt;Fountain to PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.screenweaver.ai/tools/scene-extractor" rel="noopener noreferrer"&gt;scene extractor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They run client side, so the script never leaves the browser. If you are writing a Fountain parser yourself, the format checker is a reasonable oracle for the ambiguous cases.&lt;/p&gt;

</description>
      <category>parsing</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
