# Makes the ready-made Office templates in public/templates. Needs: pip install python-docx openpyxl python-pptx. Run: python3 tools/make-office-templates.py public/templates import sys, os from docx import Document from docx.shared import Pt, RGBColor, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter from pptx import Presentation from pptx.util import Inches as PI, Pt as PP from pptx.dml.color import RGBColor as PC OUT = sys.argv[1]; os.makedirs(OUT, exist_ok=True) BLUE = '3D6BF0' # ---------------- Word def base(doc, size=11): st = doc.styles['Normal']; st.font.name = 'Calibri'; st.font.size = Pt(size) for s in doc.sections: s.left_margin = s.right_margin = Inches(1); s.top_margin = s.bottom_margin = Inches(0.9) def para(doc, text='', bold=False, size=None, color=None, align=None, after=6, italic=False): p = doc.add_paragraph(); r = p.add_run(text); r.bold = bold; r.italic = italic if size: r.font.size = Pt(size) if color: r.font.color.rgb = RGBColor.from_string(color) if align: p.alignment = align p.paragraph_format.space_after = Pt(after); return p def heading(doc, text, size=13): p = para(doc, text.upper(), bold=True, size=size - 2, color=BLUE, after=3); p.paragraph_format.space_before = Pt(10) return p d = Document(); base(d, 11) para(d, '[Your name]', bold=True, size=14, after=0); para(d, '[Street address]', after=0); para(d, '[City, State ZIP]', after=0); para(d, '[Phone] · [Email]', after=14) para(d, '[Date]', after=14); para(d, '[Recipient name]', after=0); para(d, '[Title or company]', after=0); para(d, '[Street address]', after=0); para(d, '[City, State ZIP]', after=14) para(d, 'Dear [Name],', after=10) para(d, 'Start with the reason you are writing, in a sentence or two. Keep it friendly and direct.', after=10) para(d, 'Use the middle paragraph for the details: what you need, what happened, or what you are offering. Short paragraphs are easier to read.', after=10) para(d, 'Close by saying what happens next, and thank them for their time.', after=14) para(d, 'Sincerely,', after=30); para(d, '[Your name]', after=0) d.save(f'{OUT}/letter.docx') d = Document(); base(d, 10.5) para(d, '[Your Name]', bold=True, size=24, after=0); para(d, '[City, State] · [Phone] · [Email]', color='666666', after=8) heading(d, 'Summary'); para(d, 'Two or three sentences about who you are, what you do well, and what you are looking for.') heading(d, 'Experience') for i in range(2): para(d, '[Job title], [Employer]', bold=True, after=0); para(d, '[Month Year] – [Month Year]', italic=True, color='666666', after=3) for t in ['What you did and the result it had (use numbers when you can)', 'Something you improved, built or led', 'A skill you used every day']: p = d.add_paragraph(t, style='List Bullet'); p.paragraph_format.space_after = Pt(1) para(d, '', after=4) heading(d, 'Education'); para(d, '[School], [Degree or program]', bold=True, after=0); para(d, '[Year]', italic=True, color='666666') heading(d, 'Skills'); para(d, '[Skill] · [Skill] · [Skill] · [Skill] · [Skill]') d.save(f'{OUT}/resume.docx') d = Document(); base(d, 11) para(d, 'Meeting notes', bold=True, size=22, color=BLUE, after=2); para(d, '[Meeting name]', size=13, after=10) t = d.add_table(rows=4, cols=2); t.style = 'Light Grid Accent 1' for i, (a, b) in enumerate([('Date', '[Date and time]'), ('Where', '[Place or link]'), ('Who was there', '[Names]'), ('Taking notes', '[Name]')]): t.cell(i, 0).text = a; t.cell(i, 1).text = b para(d, '', after=4); heading(d, 'Agenda') for x in ['[First topic]', '[Second topic]', '[Third topic]']: d.add_paragraph(x, style='List Number') heading(d, 'Notes'); para(d, 'What was said and what was decided.', italic=True, color='666666'); para(d, '', after=30) heading(d, 'Action items') t = d.add_table(rows=4, cols=3); t.style = 'Light Grid Accent 1' for i, h in enumerate(['What needs doing', 'Who', 'By when']): t.cell(0, i).text = h d.save(f'{OUT}/meeting-notes.docx') d = Document(); base(d, 11) para(d, '[Family Name] News', bold=True, size=28, color=BLUE, align=WD_ALIGN_PARAGRAPH.CENTER, after=0); para(d, '[Month Year]', align=WD_ALIGN_PARAGRAPH.CENTER, color='666666', after=14) heading(d, 'The big news'); para(d, 'Share the headline: a trip, a birthday, a new job, a new pet. A few friendly sentences is plenty.') heading(d, 'What everyone has been up to') for n in ['[Name]', '[Name]', '[Name]']: para(d, n, bold=True, after=0); para(d, 'A line or two about school, work, hobbies or something funny.', after=8) heading(d, 'Coming up'); t = d.add_table(rows=4, cols=2); t.style = 'Light List Accent 1' t.cell(0, 0).text = 'Date'; t.cell(0, 1).text = 'What' heading(d, 'A favorite moment'); para(d, 'Add a photo here (Insert → Image) and a sentence about it.', italic=True, color='666666') d.save(f'{OUT}/family-newsletter.docx') d = Document(); base(d, 11) para(d, '[Recipe name]', bold=True, size=24, color=BLUE, after=2); para(d, 'Serves [4] · Prep [15 min] · Cook [30 min]', color='666666', after=10) heading(d, 'Ingredients') for x in ['[2 cups flour]', '[1 tsp salt]', '[1 cup milk]', '[2 eggs]']: d.add_paragraph(x, style='List Bullet') heading(d, 'Steps') for x in ['[First step]', '[Second step]', '[Third step]']: d.add_paragraph(x, style='List Number') heading(d, 'Notes'); para(d, 'Where it came from, what to change next time, what goes well with it.', italic=True, color='666666') d.save(f'{OUT}/recipe-card.docx') # ---------------- Excel thin = Side(style='thin', color='D0D5DD'); BOX = Border(left=thin, right=thin, top=thin, bottom=thin) HEAD = PatternFill('solid', fgColor=BLUE); SOFT = PatternFill('solid', fgColor='EEF2FF') def hdr(ws, row, values, widths=None): for i, v in enumerate(values, 1): c = ws.cell(row=row, column=i, value=v); c.font = Font(bold=True, color='FFFFFF'); c.fill = HEAD; c.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True); c.border = BOX if widths: for i, w in enumerate(widths, 1): ws.column_dimensions[get_column_letter(i)].width = w def title(ws, text, sub=None): ws['A1'] = text; ws['A1'].font = Font(bold=True, size=18, color=BLUE) if sub: ws['A2'] = sub; ws['A2'].font = Font(italic=True, color='666666') wb = Workbook(); ws = wb.active; ws.title = 'Budget'; title(ws, 'Monthly budget', 'Fill in the planned and actual amounts. The last column does the math.') ws['A4'] = 'Income'; ws['A4'].font = Font(bold=True, size=12) hdr(ws, 5, ['Item', 'Planned', 'Actual', 'Difference'], [30, 14, 14, 14]) inc = ['Paycheck', 'Second income', 'Other'] for i, n in enumerate(inc): r = 6 + i; ws.cell(r, 1, n); ws.cell(r, 2, 0); ws.cell(r, 3, 0); ws.cell(r, 4, f'=C{r}-B{r}') r = 6 + len(inc); ws.cell(r, 1, 'Total income').font = Font(bold=True); ws.cell(r, 2, f'=SUM(B6:B{r-1})'); ws.cell(r, 3, f'=SUM(C6:C{r-1})'); ws.cell(r, 4, f'=C{r}-B{r}'); inc_total = r for c in range(1, 5): ws.cell(r, c).fill = SOFT; ws.cell(r, c).font = Font(bold=True) start = r + 2; ws.cell(start, 1, 'Expenses').font = Font(bold=True, size=12); hdr(ws, start + 1, ['Item', 'Planned', 'Actual', 'Difference']) exp = ['Rent or mortgage', 'Utilities', 'Groceries', 'Transportation', 'Insurance', 'Phone and internet', 'Eating out', 'Kids and school', 'Health', 'Giving', 'Savings', 'Fun', 'Other'] first = start + 2 for i, n in enumerate(exp): r = first + i; ws.cell(r, 1, n); ws.cell(r, 2, 0); ws.cell(r, 3, 0); ws.cell(r, 4, f'=B{r}-C{r}') last = first + len(exp) - 1; r = last + 1; ws.cell(r, 1, 'Total expenses'); ws.cell(r, 2, f'=SUM(B{first}:B{last})'); ws.cell(r, 3, f'=SUM(C{first}:C{last})'); ws.cell(r, 4, f'=B{r}-C{r}'); exp_total = r for c in range(1, 5): ws.cell(r, c).fill = SOFT; ws.cell(r, c).font = Font(bold=True) r += 2; ws.cell(r, 1, 'Left over (income − expenses)').font = Font(bold=True, size=12); ws.cell(r, 2, f'=B{inc_total}-B{exp_total}'); ws.cell(r, 3, f'=C{inc_total}-C{exp_total}') for row in ws.iter_rows(min_row=6, max_row=r): for c in row[1:4]: c.number_format = '$#,##0.00;[Red]-$#,##0.00' for c in row[:4]: if c.row not in (start, start + 1, r) and c.value is not None: c.border = BOX ws.freeze_panes = 'A6'; ws.sheet_view.showGridLines = False; wb.save(f'{OUT}/monthly-budget.xlsx') wb = Workbook(); ws = wb.active; ws.title = 'Chores'; title(ws, 'Weekly chore chart', 'Put a name in each box, or a check mark when it is done.') days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; hdr(ws, 4, ['Chore'] + days + ['Points'], [26] + [10] * 7 + [10]) chores = ['Make bed', 'Set the table', 'Clear the table', 'Dishes / dishwasher', 'Feed the pets', 'Take out trash', 'Tidy bedroom', 'Laundry', 'Vacuum', 'Water plants'] for i, n in enumerate(chores): r = 5 + i; ws.cell(r, 1, n) for c in range(1, 10): ws.cell(r, c).border = BOX; ws.cell(r, c).alignment = Alignment(horizontal='center' if c > 1 else 'left') ws.cell(r, 9, f'=COUNTA(B{r}:H{r})') r = 5 + len(chores); ws.cell(r, 1, 'Total').font = Font(bold=True); ws.cell(r, 9, f'=SUM(I5:I{r-1})'); ws.cell(r, 9).font = Font(bold=True) for c in range(1, 10): ws.cell(r, c).fill = SOFT; ws.cell(r, c).border = BOX ws.freeze_panes = 'B5'; ws.sheet_view.showGridLines = False; wb.save(f'{OUT}/chore-chart.xlsx') wb = Workbook(); ws = wb.active; ws.title = 'Meals'; title(ws, 'Weekly meal planner', 'Plan the week, then build the shopping list from it.') hdr(ws, 4, ['', 'Breakfast', 'Lunch', 'Dinner', 'Snacks'], [14, 24, 24, 28, 22]) for i, d_ in enumerate(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']): r = 5 + i; ws.cell(r, 1, d_).font = Font(bold=True); ws.cell(r, 1).fill = SOFT for c in range(1, 6): ws.cell(r, c).border = BOX; ws.cell(r, c).alignment = Alignment(vertical='top', wrap_text=True) ws.row_dimensions[r].height = 34 ws.cell(13, 1, 'Shopping list').font = Font(bold=True, size=12); ws.cell(14, 1, 'Write what you need to buy under here.').font = Font(italic=True, color='666666') ws.sheet_view.showGridLines = False; wb.save(f'{OUT}/meal-planner.xlsx') wb = Workbook(); ws = wb.active; ws.title = 'Expenses'; title(ws, 'Expense tracker', 'One line per purchase. The totals below add themselves up.') hdr(ws, 4, ['Date', 'What', 'Category', 'Paid with', 'Amount'], [14, 32, 18, 16, 14]) for r in range(5, 45): for c in range(1, 6): ws.cell(r, c).border = BOX ws.cell(r, 1).number_format = 'mmm d, yyyy'; ws.cell(r, 5).number_format = '$#,##0.00' ws['D46'] = 'Total'; ws['D46'].font = Font(bold=True); ws['E46'] = '=SUM(E5:E44)'; ws['E46'].number_format = '$#,##0.00'; ws['E46'].font = Font(bold=True) ws['G4'] = 'By category'; ws['G4'].font = Font(bold=True, color='FFFFFF'); ws['G4'].fill = HEAD; ws['H4'] = 'Total'; ws['H4'].font = Font(bold=True, color='FFFFFF'); ws['H4'].fill = HEAD; ws.column_dimensions['G'].width = 18; ws.column_dimensions['H'].width = 14 for i, n in enumerate(['Groceries', 'Gas', 'Eating out', 'Home', 'Kids', 'Health', 'Fun', 'Other']): r = 5 + i; ws.cell(r, 7, n); ws.cell(r, 8, f'=SUMIF($C$5:$C$44,G{r},$E$5:$E$44)'); ws.cell(r, 8).number_format = '$#,##0.00' ws.freeze_panes = 'A5'; ws.sheet_view.showGridLines = False; wb.save(f'{OUT}/expense-tracker.xlsx') # ---------------- PowerPoint prs = Presentation(); prs.slide_width = PI(13.333); prs.slide_height = PI(7.5) def slide(title_text, lines=None, layout=1, sub=None): s = prs.slides.add_slide(prs.slide_layouts[layout]); s.shapes.title.text = title_text for p in s.shapes.title.text_frame.paragraphs: for r in p.runs: r.font.color.rgb = PC.from_string(BLUE); r.font.bold = True; r.font.name = 'Calibri' if layout == 0 and sub is not None: s.placeholders[1].text = sub elif lines is not None: tf = s.placeholders[1].text_frame; tf.clear() for i, t in enumerate(lines): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph(); p.text = t; p.font.size = PP(26); p.font.name = 'Calibri' return s slide('[Presentation title]', layout=0, sub='[Your name] · [Date]') slide('Agenda', ['[First topic]', '[Second topic]', '[Third topic]', 'Questions']) slide('[A key point]', ['Say it in one clear sentence', 'Back it up with two or three short points', 'Add a picture or a chart on the right (Insert → Image)']) slide('[Another key point]', ['Keep to a few words per line', 'Let the pictures do the talking', 'Practice saying the rest out loud']) slide('Summary', ['What we covered', 'What happens next', 'Who does what']) slide('Thank you', layout=0, sub='[Contact or question prompt]') prs.save(f'{OUT}/simple-presentation.pptx') print(sorted(os.listdir(OUT)))