From 8b85793826ceef05d94153c8ecc7eba44a8be2df Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 16 May 2021 15:14:27 -0700 Subject: [PATCH] Dumped PDF code from my jupyter notebook into source --- ptulsconv/pdf/common.py | 191 ++++++++++++++++++++++++++++++ ptulsconv/pdf/supervisor_1pg.py | 198 ++++++++++++++++++++++++++++++++ requirements.txt | 6 + setup.py | 2 +- 4 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 ptulsconv/pdf/common.py create mode 100644 ptulsconv/pdf/supervisor_1pg.py create mode 100644 requirements.txt diff --git a/ptulsconv/pdf/common.py b/ptulsconv/pdf/common.py new file mode 100644 index 0000000..6d5432d --- /dev/null +++ b/ptulsconv/pdf/common.py @@ -0,0 +1,191 @@ +from reportlab.pdfbase.pdfmetrics import (getAscent, getDescent) + +class GRect: + def __init__(self, x, y, width, height, debug_name=None): + self.x = x + self.y = y + self.width = width + self.height = height + self.debug_name = debug_name + self.normalize() + + @property + def min_x(self): + return self.x + + @property + def min_y(self): + return self.y + + @property + def max_x(self): + return self.x + self.width + + @property + def max_y(self): + return self.y + self.height + + @property + def center_x(self): + return self.x + self.width / 2 + + @property + def center_y(self): + return self.y + self.height / 2 + + def normalize(self): + if self.width < 0.: + self.width = abs(self.width) + self.x = self.x - self.width + + if self.height < 0.: + self.height = abs(self.height) + self.y = self.y - self.height + + def split_x(self, at, direction='l'): + if at >= self.width: + return None, self + elif at <= 0: + return self, None + else: + return (GRect(self.min_x, self.min_y, at, self.height), + GRect(self.min_x + at, self.y, self.width - at, self.height)) + + def split_y(self, at, direction='u'): + if at >= self.height: + return None, self + elif at <= 0: + return self, None + else: + if direction == 'u': + return (GRect(self.x, self.y, self.width, at), + GRect(self.x, self.y + at, self.width, self.height - at)) + else: + return (GRect(self.x, self.max_y - at, self.width, at), + GRect(self.x, self.y, self.width, self.height - at)) + + def inset_xy(self, dx, dy): + return GRect(self.x + dx, self.y + dy, self.width - dx * 2, self.height - dy * 2) + + def inset(self, d): + return self.inset_xy(d, d) + + def __repr__(self): + return "" % (self.x, self.y, self.width, self.height) + + def divide_x(self, x_list, reverse=False): + ret_list = list() + + rem = self + for item in x_list: + s, rem = rem.split_x(item) + ret_list.append(s) + + return ret_list, rem + + def divide_y(self, y_list, direction='u'): + ret_list = list() + + rem = self + for item in y_list: + s, rem = rem.split_y(item, direction) + ret_list.append(s) + + return ret_list, rem + + def draw_debug(self, canvas): + canvas.saveState() + canvas.setFont("Courier", 8) + canvas.rect(self.x, self.y, self.width, self.height) + canvas.drawString(self.x, self.y, self.debug_name or self.__repr__()) + canvas.restoreState() + + def draw_border(self, canvas, edge): + + def draw_border_impl(en): + if en == 'min_x': + coordinates = ((self.min_x, self.min_y), (self.min_x, self.max_y)) + elif en == 'max_x': + coordinates = ((self.max_x, self.min_y), (self.max_x, self.max_y)) + elif en == 'min_y': + coordinates = ((self.min_x, self.min_y), (self.max_x, self.min_y)) + elif en == 'max_y': + coordinates = ((self.min_x, self.max_y), (self.max_x, self.max_y)) + else: + return + + s = canvas.beginPath() + s.moveTo(*coordinates[0]) + s.lineTo(*coordinates[1]) + canvas.drawPath(s) + + if type(edge) is str: + edge = [edge] + + for e in edge: + draw_border_impl(e) + + def draw_text_cell(self, canvas, text, font_name, font_size, vertical_align='t', force_baseline=None, inset_x=0., + inset_y=0., draw_baseline=False): + canvas.saveState() + + inset_rect = self.inset_xy(inset_x, inset_y) + + if vertical_align == 'm': + y = inset_rect.center_y - getAscent(font_name, font_size) / 2. + elif vertical_align == 't': + y = inset_rect.max_y - getAscent(font_name, font_size) + else: + y = inset_rect.min_y - getDescent(font_name, font_size) + + if force_baseline is not None: + y = self.min_y + force_baseline + + cp = canvas.beginPath() + cp.rect(self.min_x, self.min_y, self.width, self.height) + canvas.clipPath(cp, stroke=0, fill=0) + + canvas.setFont(font_name, font_size) + tx = canvas.beginText() + tx.setTextOrigin(inset_rect.min_x, y) + tx.textLine(text) + canvas.drawText(tx) + + if draw_baseline: + canvas.setDash([3.0, 1.0, 2.0, 1.0]) + canvas.setLineWidth(0.5) + bl = canvas.beginPath() + bl.moveTo(inset_rect.min_x, y - 1.) + bl.lineTo(inset_rect.max_x, y - 1.) + canvas.drawPath(bl) + + canvas.restoreState() + + def draw_flowable(self, canvas, flowable, inset_x=0., inset_y=0., draw_baselines=False): + canvas.saveState() + + inset_rect = self.inset_xy(inset_x, inset_y) + + cp = canvas.beginPath() + cp.rect(self.min_x, self.min_y, self.width, self.height) + canvas.clipPath(cp, stroke=0, fill=0) + + w, h = flowable.wrap(inset_rect.width, inset_rect.height) + + flowable.drawOn(canvas, inset_rect.x, inset_rect.max_y - h) + + if draw_baselines: + canvas.setDash([3.0, 1.0, 2.0, 1.0]) + canvas.setLineWidth(0.5) + leading = flowable.style.leading + + y = inset_rect.max_y - flowable.style.fontSize - 1. + while y > inset_rect.min_x: + bl = canvas.beginPath() + bl.moveTo(inset_rect.min_x, y) + bl.lineTo(inset_rect.max_x, y) + canvas.drawPath(bl) + y = y - leading + + canvas.restoreState() + diff --git a/ptulsconv/pdf/supervisor_1pg.py b/ptulsconv/pdf/supervisor_1pg.py new file mode 100644 index 0000000..3bf5dbe --- /dev/null +++ b/ptulsconv/pdf/supervisor_1pg.py @@ -0,0 +1,198 @@ +from reportlab.pdfgen.canvas import Canvas + +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont + +from reportlab.lib.units import inch +from reportlab.lib.pagesizes import letter + +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.platypus import Paragraph + +from .common import GRect + + +def draw_header_block(canvas, rect): + rect.draw_text_cell(canvas, "CUE002", "Helvetica", 44, vertical_align='m') + + +def draw_title_block(canvas, rect): + (supervisor, client,), title = rect.divide_y([16., 16., ]) + title.draw_text_cell(canvas, "This is the title", "Futura", 18, inset_y=2.) + client.draw_text_cell(canvas, "This is the client", "Futura", 11, inset_y=2.) + supervisor.draw_text_cell(canvas, "This is the supervisor", "Futura", 11, inset_y=2.) + + +def draw_character_row(canvas, rect): + label_frame, value_frame = rect.split_x(1.25 * inch) + label_frame.draw_text_cell(canvas, "CHARACTER", "Futura", 10, force_baseline=9.) + value_frame.draw_text_cell(canvas, "1 / Luke Skywalker / Mark Hamill", "Futura", 12, force_baseline=9.) + rect.draw_border(canvas, ['min_y', 'max_y']) + + +def draw_cue_number_block(canvas, rect): + (label_frame, number_frame,), aux_frame = rect.divide_y([0.20 * inch, 0.375 * inch], direction='d') + label_frame.draw_text_cell(canvas, "CUE NUMBER", "Futura", 10, inset_y=5., vertical_align='t') + number_frame.draw_text_cell(canvas, "CUE1001", "Futura", 14, inset_x=10., inset_y=2., draw_baseline=True) + aux_frame.draw_text_cell(canvas, "TV OPT ADLIB EFF", "Futura", 10, inset_x=10., inset_y=2., vertical_align='t') + rect.draw_border(canvas, 'max_x') + + +def draw_timecode_block(canvas, rect): + (in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y( + [0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d') + + in_label_frame.draw_text_cell(canvas, "IN", "Futura", 10, vertical_align='t', inset_y=5., inset_x=5.) + in_frame.draw_text_cell(canvas, "01:00:00:00", "Futura", 14, inset_x=10., inset_y=2., draw_baseline=True) + out_label_frame.draw_text_cell(canvas, "OUT", "Futura", 10, vertical_align='t', inset_y=5., inset_x=5.) + out_frame.draw_text_cell(canvas, "01:01:00:00", "Futura", 14, inset_x=10., inset_y=2., draw_baseline=True) + + rect.draw_border(canvas, 'max_x') + + +def draw_reason_block(canvas, rect): + reason_cell, notes_cell = rect.split_y(24., direction='d') + reason_label, reason_value = reason_cell.split_x(.75 * inch) + notes_label, notes_value = notes_cell.split_x(.75 * inch) + + reason_label.draw_text_cell(canvas, "Reason:", "Futura", 12, inset_x=5., inset_y=5., vertical_align='b') + reason_value.draw_text_cell(canvas, "Low level", "Futura", 12, inset_x=5., inset_y=5., draw_baseline=True, + vertical_align='b') + notes_label.draw_text_cell(canvas, "Note:", "Futura", 12, inset_x=5., inset_y=5., vertical_align='t') + + style = getSampleStyleSheet()['BodyText'] + style.fontName = 'Futura' + style.fontSize = 12 + style.leading = 14 + + p = Paragraph("""This is a long note line, for use during spotting to elaborate on a cue's justification.""", style) + + notes_value.draw_flowable(canvas, p, draw_baselines=True, inset_x=5., inset_y=5.) + + +def draw_prompt(canvas, rect, prompt= ""): + label, block = rect.split_y(0.20 * inch, direction='d') + + label.draw_text_cell(canvas, "PROMPT", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.) + + style = getSampleStyleSheet()['BodyText'] + style.fontName = 'Futura' + style.fontSize = 14 + + style.leading = 24 + style.leftIndent = 1.5 * inch + style.rightIndent = 1.5 * inch + + block.draw_flowable(canvas, prompt, draw_baselines=True) + + rect.draw_border(canvas, 'max_y') + + +def draw_notes(canvas, rect, note=""): + label, block = rect.split_y(0.20 * inch, direction='d') + + label.draw_text_cell(canvas, "NOTES", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.) + + style = getSampleStyleSheet()['BodyText'] + style.fontName = 'Futura' + style.fontSize = 14 + style.leading = 24 + + prompt = Paragraph(note, style) + + block.draw_flowable(canvas, prompt, draw_baselines=True) + + rect.draw_border(canvas, ['max_y', 'min_y']) + + +def draw_take_grid(canvas, rect): + canvas.saveState() + + cp = canvas.beginPath() + cp.rect(rect.min_x, rect.min_y, rect.width, rect.height) + canvas.clipPath(cp, stroke=0, fill=0) + + canvas.setDash([3.0, 2.0]) + + for xi in range(1, 10): + x = xi * (rect.width / 10) + if xi % 5 == 0: + canvas.setDash(1, 0) + else: + canvas.setDash([2, 5]) + + ln = canvas.beginPath() + ln.moveTo(rect.min_x + x, rect.min_y) + ln.lineTo(rect.min_x + x, rect.max_y) + canvas.drawPath(ln) + + for yi in range(1, 10): + y = yi * (rect.height / 6) + if yi % 2 == 0: + canvas.setDash(1, 0) + else: + canvas.setDash([2, 5]) + + ln = canvas.beginPath() + ln.moveTo(rect.min_x, rect.min_y + y) + ln.lineTo(rect.max_x, rect.min_y + y) + canvas.drawPath(ln) + + rect.draw_border(canvas, 'max_x') + + canvas.restoreState() + + +def draw_aux_block(canvas, rect): + rect.draw_border(canvas, 'min_x') + + content_rect = rect.inset_xy(10., 10.) + lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.], direction='d') + + lines[0].draw_text_cell(canvas, "Time for this line: 6 mins", "Futura", 9.) + lines[1].draw_text_cell(canvas, "Running time: 6 mins", "Futura", 9.) + lines[2].draw_text_cell(canvas, "Actual Start: ______________", "Futura", 9., vertical_align='b') + lines[3].draw_text_cell(canvas, "Record Date: ______________", "Futura", 9., vertical_align='b') + lines[4].draw_text_cell(canvas, "Engineer: ______________", "Futura", 9., vertical_align='b') + lines[5].draw_text_cell(canvas, "Location: ______________", "Futura", 9., vertical_align='b') + + +def draw_footer(canvas, rect): + rect.draw_border(canvas, 'max_y') + rect.draw_text_cell(canvas, "2021-01-01 19:14:21 - Spotting: 2021-01-01 - Page 1 of 1", font_name="Futura", + font_size=10., inset_y=2.) + + +def output_report(): + page = GRect(0, 0, letter[0], letter[1]) + + page = page.inset(inch * 0.5) + + (header_row, char_row, data_row, prompt_row, notes_row, takes_row), footer = \ + page.divide_y([0.875 * inch, 0.375 * inch, inch, 3.0 * inch, 1.5 * inch, 3 * inch], direction= 'd') + + cue_header_block, title_header_block = header_row.split_x(4.0 * inch) + (cue_number_block, timecode_block), reason_block = data_row.divide_x([1.5 * inch, 1.5 * inch]) + (take_grid_block), aux_block = takes_row.split_x(5.25 * inch) + + pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc') ) + + c = Canvas("out.pdf", pagesize=letter) + + draw_header_block(c, cue_header_block) + draw_title_block(c, title_header_block) + draw_character_row(c, char_row) + draw_cue_number_block(c, cue_number_block) + draw_timecode_block(c, timecode_block) + draw_reason_block(c, reason_block) + + draw_prompt(c, prompt_row) + draw_notes(c, notes_row) + + draw_take_grid(c, take_grid_block) + draw_aux_block(c, aux_block) + + draw_footer(c, footer) + + c.showPage() + c.save() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..90b5cde --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ptulsconv~=0.6.0 +setuptools~=56.2.0 +reportlab~=3.5.67 +ffmpeg~=1.4 +parsimonious~=0.8.1 +tqdm~=4.60.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 2ea1c2b..c05a291 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup(name='ptulsconv', "Topic :: Text Processing :: Markup :: XML"], packages=['ptulsconv'], keywords='text-processing parsers film tv editing editorial', - install_requires=['parsimonious', 'tqdm'], + install_requires=['parsimonious', 'tqdm', 'reportlab'], package_data={ "ptulsconv": ["xslt/*.xsl"] },