|
| 1 | +""" |
| 2 | +Generate the RecordSponge Expungement Checklist PDF. |
| 3 | +
|
| 4 | +Requirements: pip install reportlab |
| 5 | +
|
| 6 | +Output: src/frontend/public/docs/expungement-checklist.pdf |
| 7 | +
|
| 8 | +Run from the project root: |
| 9 | + python scripts/generate-checklist-pdf.py |
| 10 | +""" |
| 11 | + |
| 12 | +import os |
| 13 | +from reportlab.lib.pagesizes import letter |
| 14 | +from reportlab.lib.units import inch |
| 15 | +from reportlab.lib.colors import HexColor, black, white |
| 16 | +from reportlab.platypus import ( |
| 17 | + SimpleDocTemplate, Paragraph, Spacer, HRFlowable, Flowable |
| 18 | +) |
| 19 | +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet |
| 20 | + |
| 21 | +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 22 | +OUTPUT_PATH = os.path.join(SCRIPT_DIR, "..", "src", "frontend", "public", "docs", "expungement-checklist.pdf") |
| 23 | + |
| 24 | + |
| 25 | +class CheckboxStep(Flowable): |
| 26 | + """A checkbox followed by step text, vertically aligned.""" |
| 27 | + |
| 28 | + def __init__(self, text, style, checkbox_size=11): |
| 29 | + super().__init__() |
| 30 | + self.text = text |
| 31 | + self.style = style |
| 32 | + self.checkbox_size = checkbox_size |
| 33 | + self._para = Paragraph(text, style) |
| 34 | + |
| 35 | + def wrap(self, availWidth, availHeight): |
| 36 | + text_width = availWidth - self.checkbox_size - 10 |
| 37 | + self._para_w, self._para_h = self._para.wrap(text_width, availHeight) |
| 38 | + self.width = availWidth |
| 39 | + self.height = self._para_h + 4 |
| 40 | + return self.width, self.height |
| 41 | + |
| 42 | + def draw(self): |
| 43 | + para_y = self.height - self._para_h |
| 44 | + self._para.drawOn(self.canv, self.checkbox_size + 10, para_y) |
| 45 | + |
| 46 | + cb_y = self.height - self._para_h + (self._para_h - self.checkbox_size) / 2 - 1 |
| 47 | + |
| 48 | + self.canv.setStrokeColor(black) |
| 49 | + self.canv.setFillColor(white) |
| 50 | + self.canv.setLineWidth(0.8) |
| 51 | + self.canv.rect(0, max(0, cb_y), self.checkbox_size, self.checkbox_size, fill=1, stroke=1) |
| 52 | + |
| 53 | + |
| 54 | +def generate(): |
| 55 | + os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True) |
| 56 | + |
| 57 | + doc = SimpleDocTemplate( |
| 58 | + OUTPUT_PATH, |
| 59 | + pagesize=letter, |
| 60 | + topMargin=0.75 * inch, |
| 61 | + bottomMargin=0.75 * inch, |
| 62 | + leftMargin=0.75 * inch, |
| 63 | + rightMargin=0.75 * inch, |
| 64 | + ) |
| 65 | + |
| 66 | + styles = getSampleStyleSheet() |
| 67 | + blue = HexColor("#357edd") |
| 68 | + dark = HexColor("#333333") |
| 69 | + gray = HexColor("#555555") |
| 70 | + |
| 71 | + title_style = ParagraphStyle("ChecklistTitle", parent=styles["Title"], fontSize=20, textColor=dark, spaceAfter=6) |
| 72 | + subtitle_style = ParagraphStyle("Subtitle", parent=styles["Normal"], fontSize=11, textColor=gray, spaceAfter=4) |
| 73 | + step_style = ParagraphStyle("StepStyle", parent=styles["Normal"], fontSize=12, textColor=dark, fontName="Helvetica-Bold", spaceBefore=0, spaceAfter=4, leading=14) |
| 74 | + sub_style = ParagraphStyle("SubStyle", parent=styles["Normal"], fontSize=11, textColor=gray, leftIndent=28, spaceBefore=2, spaceAfter=2) |
| 75 | + note_style = ParagraphStyle("NoteStyle", parent=styles["Normal"], fontSize=11, textColor=gray, spaceBefore=0, spaceAfter=4, borderWidth=1, borderColor=HexColor("#cccccc"), borderPadding=8) |
| 76 | + |
| 77 | + link_str = 'color="#357edd"' |
| 78 | + story = [] |
| 79 | + |
| 80 | + story.append(Paragraph("RecordSponge Expungement Checklist", title_style)) |
| 81 | + story.append(Paragraph("A step-by-step guide to the expungement process", subtitle_style)) |
| 82 | + story.append(HRFlowable(width="100%", thickness=1, color=blue, spaceAfter=12)) |
| 83 | + |
| 84 | + steps = [ |
| 85 | + { |
| 86 | + "title": "Log in to OECI", |
| 87 | + "subs": [ |
| 88 | + "You will need an OECI account to search for criminal records.", |
| 89 | + f'Purchase a subscription at <a href="https://www.courts.oregon.gov/services/online/Pages/ojcin-signup.aspx" {link_str}>courts.oregon.gov</a>.', |
| 90 | + ], |
| 91 | + }, |
| 92 | + { |
| 93 | + "title": "Search records", |
| 94 | + "subs": [ |
| 95 | + "Ensure that Assumptions are met", |
| 96 | + "Search by name and date of birth", |
| 97 | + ], |
| 98 | + }, |
| 99 | + { |
| 100 | + "title": "Complete paperwork for expungement", |
| 101 | + "subs": [ |
| 102 | + "This includes paperwork to modify financial obligations if applicable", |
| 103 | + ], |
| 104 | + }, |
| 105 | + { |
| 106 | + "title": "Obtain fingerprints", |
| 107 | + "subs": [ |
| 108 | + "Mail to Oregon State Police", |
| 109 | + ], |
| 110 | + }, |
| 111 | + { |
| 112 | + "title": "File paperwork in appropriate courts", |
| 113 | + "subs": [], |
| 114 | + }, |
| 115 | + ] |
| 116 | + |
| 117 | + for i, step in enumerate(steps, 1): |
| 118 | + story.append(Spacer(1, 10)) |
| 119 | + story.append(CheckboxStep(f'<b>Step {i}:</b> {step["title"]}', step_style)) |
| 120 | + for sub in step["subs"]: |
| 121 | + story.append(Paragraph(f"\u2022 {sub}", sub_style)) |
| 122 | + |
| 123 | + story.append(Spacer(1, 24)) |
| 124 | + story.append(Paragraph( |
| 125 | + f'<b>Note:</b> If new to RecordSponge, confirm results with Michael Zhang at ' |
| 126 | + f'<a href="mailto:michael@qiu-qiulaw.com" {link_str}>michael@qiu-qiulaw.com</a>.<br/><br/>' |
| 127 | + f'For further details, visit <a href="https://recordsponge.com/manual" {link_str}>recordsponge.com/manual</a>.', |
| 128 | + note_style, |
| 129 | + )) |
| 130 | + |
| 131 | + doc.build(story) |
| 132 | + print(f"PDF generated: {os.path.abspath(OUTPUT_PATH)}") |
| 133 | + |
| 134 | + |
| 135 | +if __name__ == "__main__": |
| 136 | + generate() |
0 commit comments