diff --git a/requirements.txt b/requirements.txt
index 321e747..279c030 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
-reportlab
-unittest2
+PDF
nose
pep8
+reportlab
+unittest2
diff --git a/screenplain/export/html.py b/screenplain/export/html.py
index 1f88e64..3fed4e2 100644
--- a/screenplain/export/html.py
+++ b/screenplain/export/html.py
@@ -10,6 +10,28 @@
from screenplain.richstring import plain
+dialog_numbers_css = """
+
+/* begin dialog numbering css */
+
+body {
+ counter-reset: dialog_counter;
+}
+
+
+div.dialog:before, div.dual:before {
+ content: counter(dialog_counter);
+ counter-increment: dialog_counter;
+ display: inline;
+ float: left;
+ padding-right: 1em;
+}
+
+/* end dialog numbering css */
+
+"""
+
+
class tag(object):
"""Handler for automatically opening and closing a tag.
@@ -168,7 +190,7 @@ def _read_file(filename):
return stream.read()
-def convert(screenplay, out, css_file=None, bare=False):
+def convert(screenplay, out, css_file=None, bare=False, numbered=False):
"""Convert the screenplay into HTML, written to the file-like object `out`.
The output will be a complete HTML document unless `bare` is true.
@@ -179,15 +201,17 @@ def convert(screenplay, out, css_file=None, bare=False):
else:
convert_full(
screenplay, out,
- css_file or os.path.join(os.path.dirname(__file__), 'default.css')
+ css_file or os.path.join(os.path.dirname(__file__), 'default.css'),
+ numbered
)
-def convert_full(screenplay, out, css_file):
+def convert_full(screenplay, out, css_file, numbered=False):
"""Convert the screenplay into a complete HTML document,
written to the file-like object `out`.
"""
+
with open(css_file, 'r') as stream:
css = stream.read()
out.write(
@@ -198,6 +222,8 @@ def convert_full(screenplay, out, css_file):
''
''
diff --git a/screenplain/export/pdf.py b/screenplain/export/pdf.py
index 6955616..2d29364 100644
--- a/screenplain/export/pdf.py
+++ b/screenplain/export/pdf.py
@@ -43,7 +43,6 @@
top_margin = 1 * inch
bottom_margin = page_height - top_margin - frame_height
-
default_style = ParagraphStyle(
'default',
fontName='Courier',
@@ -156,8 +155,12 @@ def add_slug(story, para, style, is_strong):
story.append(Paragraph(html, style))
-def add_dialog(story, dialog):
- story.append(Paragraph(dialog.character.to_html(), character_style))
+def add_dialog(story, dialog, numbered=False):
+ dialog_counter = numbered and \
+ """ """ \
+ or ''
+ story.append(Paragraph(dialog.character.to_html() + dialog_counter,
+ character_style))
for parenthetical, line in dialog.blocks:
if parenthetical:
story.append(Paragraph(line.to_html(), parenthentical_style))
@@ -165,10 +168,10 @@ def add_dialog(story, dialog):
story.append(Paragraph(line.to_html(), dialog_style))
-def add_dual_dialog(story, dual):
+def add_dual_dialog(story, dual, numbered=False):
# TODO: format dual dialog
- add_dialog(story, dual.left)
- add_dialog(story, dual.right)
+ add_dialog(story, dual.left, numbered=numbered)
+ add_dialog(story, dual.right, numbered=numbered)
def get_title_page_story(screenplay):
@@ -245,15 +248,16 @@ def to_pdf(
screenplay, output_filename,
template_constructor=DocTemplate,
is_strong=False,
+ numbered=False,
):
story = get_title_page_story(screenplay)
has_title_page = bool(story)
for para in screenplay:
if isinstance(para, Dialog):
- add_dialog(story, para)
+ add_dialog(story, para, numbered=numbered)
elif isinstance(para, DualDialog):
- add_dual_dialog(story, para)
+ add_dual_dialog(story, para, numbered=numbered)
elif isinstance(para, Action):
add_paragraph(
story, para,
diff --git a/screenplain/main.py b/screenplain/main.py
index 770427c..4c8d9d6 100644
--- a/screenplain/main.py
+++ b/screenplain/main.py
@@ -66,6 +66,14 @@ def main(args):
'Bold and Underlined.'
)
)
+ parser.add_option(
+ '--numbered',
+ action='store_true',
+ dest='numbered',
+ help=(
+ 'For Full HTML or PDF output, number the dialog.'
+ )
+ )
options, args = parser.parse_args(args)
if len(args) >= 3:
parser.error('Too many arguments')
@@ -124,11 +132,15 @@ def main(args):
from screenplain.export.html import convert
convert(
screenplay, output,
- css_file=options.css, bare=options.bare
+ css_file=options.css, bare=options.bare,
+ numbered=options.numbered
)
elif format == 'pdf':
from screenplain.export.pdf import to_pdf
- to_pdf(screenplay, output, is_strong=options.strong)
+ to_pdf(
+ screenplay, output, is_strong=options.strong,
+ numbered=options.numbered
+ )
finally:
if output_file:
output.close()
diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py
new file mode 100644
index 0000000..f4107d1
--- /dev/null
+++ b/tests/dialog_numbering_tests.py
@@ -0,0 +1,92 @@
+from PDF import PdfFileReader
+from io import BytesIO
+
+from screenplain.export import html
+from screenplain.export.html import dialog_numbers_css
+from screenplain.export import pdf
+from screenplain.parsers import fountain
+
+from testcompat import TestCase
+
+
+script = """
+
+EXT. DESSERT ISLAND - DAY
+
+ROBERT
+I've told you a thousand times, I do not care for sea urchins.
+
+WALTER
+Given our current situation, I hardly think we can afford to be picky!
+
+ROBERT
+Listen Walt, I'm a kelp man - through and through!
+(staring determinedly into the mid-foredistance)
+My father was a kelp man...
+
+ROBERT
+...and his father before him.
+
+WALTER ^
+(sarcastically)
+"...and his father before him." ...
+
+WALTER
+I've heard it all before.
+(pause)
+Hey. This island is made of pie.
+
+"""
+
+
+class DialogNumberingTests(TestCase):
+
+ def setUp(self):
+ self.screenplay = fountain.parse(BytesIO(script))
+
+ def _extract_character_lines_from_pdf(self, pdf_file):
+ pdf_reader = PdfFileReader(pdf_file)
+ page_1 = pdf_reader.getPage(0)
+ text = page_1.extractText()
+ lines = text.split('\n')
+ character_lines = [line for line in lines
+ if line.startswith('WALTER') or
+ line.startswith('ROBERT')]
+ return character_lines
+
+ def test_pdf_without_numbering(self):
+ pdf_file = BytesIO()
+ pdf.to_pdf(self.screenplay, output_filename=pdf_file, numbered=False)
+ character_lines = self._extract_character_lines_from_pdf(pdf_file)
+ assert character_lines == ['ROBERT',
+ 'WALTER',
+ 'ROBERT',
+ 'ROBERT',
+ 'WALTER',
+ 'WALTER',
+ ]
+
+ def test_pdf_with_numbering(self):
+ pdf_file = BytesIO()
+ pdf.to_pdf(self.screenplay, output_filename=pdf_file, numbered=True)
+ character_lines = self._extract_character_lines_from_pdf(pdf_file)
+ assert character_lines == ['ROBERT 1',
+ 'WALTER 2',
+ 'ROBERT 3',
+ 'ROBERT 4',
+ 'WALTER 5',
+ 'WALTER 6',
+ ]
+
+ def _html_contains_numbering(self, bare, numbered):
+ html_file = BytesIO()
+ html.convert(self.screenplay, out=html_file, bare=bare,
+ numbered=numbered)
+ generated_html = html_file.getvalue()
+ return dialog_numbers_css in generated_html
+
+ def test_html_numbering(self):
+ assert self._html_contains_numbering(bare=False, numbered=True)
+ assert not self._html_contains_numbering(bare=False, numbered=False)
+ assert not self._html_contains_numbering(bare=True, numbered=False)
+ assert not self._html_contains_numbering(bare=True, numbered=True)