diff --git a/screenplain/export/pdf.py b/screenplain/export/pdf.py index 5f4380f..ec0ebf2 100644 --- a/screenplain/export/pdf.py +++ b/screenplain/export/pdf.py @@ -39,6 +39,10 @@ top_margin = 1 * inch bottom_margin = page_height - top_margin - frame_height +# If the current page has below this many pixels remaining, +# break before starting a new scene / dialogue +pixels_threshold_page_break = 100 + character_width = 1.0 / 10 * inch default_style = ParagraphStyle( @@ -142,7 +146,14 @@ def add_paragraph(story, para, style): story.append(Paragraph(line.to_html(), style)) +def add_slug(story, para, style): + protect_page_break(story) + for line in para.lines: + story.append(Paragraph(line.to_html(), style)) + + def add_dialog(story, dialog): + protect_page_break(story) story.append(Paragraph(dialog.character.to_html(), character_style)) for parenthetical, line in dialog.blocks: if parenthetical: @@ -157,6 +168,10 @@ def add_dual_dialog(story, dual): add_dialog(story, dual.right) +def protect_page_break(story): + story.append(platypus.CondPageBreak(pixels_threshold_page_break)) + + def get_title_page_story(screenplay): """Get Platypus flowables for the title page @@ -236,7 +251,7 @@ def to_pdf(screenplay, output_filename, template_constructor=DocTemplate): centered_action_style if para.centered else action_style ) elif isinstance(para, Slug): - add_paragraph(story, para, slug_style) + add_slug(story, para, slug_style) elif isinstance(para, Transition): add_paragraph(story, para, transition_style) elif isinstance(para, types.PageBreak): diff --git a/tests/files/awkward-page-breaks.fountain b/tests/files/awkward-page-breaks.fountain new file mode 100644 index 0000000..0fba54f --- /dev/null +++ b/tests/files/awkward-page-breaks.fountain @@ -0,0 +1,58 @@ +INT. SOMEWHERE - DAY + +Stuff happens. + +So much stuff happens, that this scene takes up exactly one page. + +And when we export this as a PDF with 55 "lines per page" and 61 "characters per line" (unless we protect against it) the next scene heading will end up being orphaned at the bottom of this page, with nothing below it. The contents of the next scene will appear on the following page, without any heading. + +ALPHA +Bravo. + +CHARLIE +Delta. + +ECHO +Foxtrot. + +GOLF +Hotel. + +INDIA +Juliet. + +KILO +Lima. + +MIKE +November. + +OSCAR +Papa. + +QUEBEC +Romeo. + +SIERRA +Tango. + +UMBRELLA +Victor. + +Whiskey X-ray. Yankee Zulu. Whiskey X-ray. Yankee Zulu.Whiskey X-ray. Yankee Zulu.Whiskey X-ray. Yankee Zulu.Whiskey X-ray. Yankee Zulu.Whiskey X-ray. + +INT. SOMEWHERE ELSE, TO BE CERTAIN - NIGHT + +Exported as a PDF with 55 "lines per page" and 61 "characters per line", this scene will appear at the top of a page, with no heading above it. + +ALPHA +Bravo. + +CHARLIE +Delta. + +ECHO +Foxtrot. + +GOLF +Hotel. \ No newline at end of file diff --git a/tests/pdf_test.py b/tests/pdf_test.py new file mode 100644 index 0000000..256c84b --- /dev/null +++ b/tests/pdf_test.py @@ -0,0 +1,24 @@ +# Copyright (c) 2011 Martin Vilcans +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php + +from testcompat import TestCase +from StringIO import StringIO + +from screenplain.export.pdf import to_pdf +from screenplain.richstring import plain, bold, italic + + +class OutputTests(TestCase): + + def setUp(self): + self.out = StringIO() + # TODO: figure out how to test PDF export + + def test_scene_heading_page_break_threshold(self): + self.assertEqual(1, 1) + # TODO: test PDF export should not break heading from scene + + def test_scene_heading_page_break_threshold(self): + self.assertEqual(1, 1) + # TODO: test PDF export should not break character name from dialog