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)