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
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
reportlab
unittest2
PDF
nose
pep8
reportlab
unittest2
32 changes: 29 additions & 3 deletions screenplain/export/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand All @@ -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(
Expand All @@ -198,6 +222,8 @@ def convert_full(screenplay, out, css_file):
'<style type="text/css">'
)
out.write(css)
if numbered:
out.write(dialog_numbers_css)
out.write(
'</style>'
'</head>'
Expand Down
20 changes: 12 additions & 8 deletions screenplain/export/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
top_margin = 1 * inch
bottom_margin = page_height - top_margin - frame_height


default_style = ParagraphStyle(
'default',
fontName='Courier',
Expand Down Expand Up @@ -156,19 +155,23 @@ 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 \
""" <super><seq id="dialog_counter" /></super>""" \
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))
else:
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):
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 14 additions & 2 deletions screenplain/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand Down
92 changes: 92 additions & 0 deletions tests/dialog_numbering_tests.py
Original file line number Diff line number Diff line change
@@ -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)