#!/usr/bin/env python3 """Writes the demo DOCX, XLSX, PPTX and Markdown into argv[1].""" import sys import zipfile from pathlib import Path from xml.sax.saxutils import escape from pptx import Presentation from pptx.dml.color import RGBColor from pptx.enum.shapes import MSO_SHAPE from pptx.util import Emu, Inches, Pt OUT = Path(sys.argv[1]) OUT.mkdir(parents=True, exist_ok=True) RELS = "http://schemas.openxmlformats.org/package/2006/relationships" DOC_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" S = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" def package(path, parts): with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as archive: for name, text in parts.items(): archive.writestr(name, text) def types(overrides): rows = "".join(f'' for p, t in overrides) return ('' '' f'' f'{rows}') def rels(rows): body = "".join(f'' for i, t, target in rows) return f'{body}' # DOCX: a one-page project brief. def run(text, bold=False, color=None): props = ("" if bold else "") + (f'' if color else "") return f'{props}{escape(text)}' def para(text, style=None, runs=None): pstyle = f'' if style else "" return f"{pstyle}{runs or run(text)}" def cell(text, width, header=False): shade = '' if header else "" return (f'{shade}' f'{run(text, bold=header)}') def table(rows, widths): border = "".join(f'' for side in ("top", "left", "bottom", "right", "insideH", "insideV")) grid = "".join(f'' for w in widths) body = "".join("" + "".join(cell(t, w, i == 0) for t, w in zip(row, widths)) + "" for i, row in enumerate(rows)) return (f'{border}' f'' f'{grid}{body}') def style(sid, name, font, size, color="1F2A26", bold=False, before=0, after=160, based=None, level=None): b = "" if bold else "" outline = f'' if level is not None else "" base = f'' if based else "" return (f'{base}' f'{outline}' f'{b}' f'') def docx(path): styles = (f'' '' '' + style("Normal", "Normal", "Georgia", 22, "2B3330") + style("Title", "Title", "Helvetica Neue", 52, "1F3D35", True, 0, 80) + style("Subtitle", "Subtitle", "Helvetica Neue", 24, "5E6E68", False, 0, 360) + style("Heading1", "heading 1", "Helvetica Neue", 30, "2F6B5B", True, 320, 120, level=0) + "") body = "".join([ para("Coastal Trails Field Guide", "Title"), para("Project brief · Spring edition · Draft 3", "Subtitle"), para("Purpose", "Heading1"), para("The field guide maps forty walks along the northern coast, from short harbor loops to " "full-day cliff routes. Each walk gets a hand-drawn map, honest timings, tide notes and " "a short story about the place. The spring edition adds twelve new routes and a fold-out " "overview map."), para("Goals for this edition", "Heading1"), para("", runs=run("Accurate. ", bold=True) + run("Every route is walked twice before it goes to print.")), para("", runs=run("Readable. ", bold=True) + run("One walk per spread, with the map and the notes side by side.")), para("", runs=run("Durable. ", bold=True) + run("Water-resistant paper and a lay-flat binding for the trail.")), para("Schedule", "Heading1"), table([["Phase", "Owner", "Due"], ["Route surveys", "Maya Lind", "12 March"], ["Maps and illustrations", "Theo Brandt", "2 April"], ["Copy edit and proofs", "Ines Okafor", "23 April"], ["Print and distribution", "Sam Reyes", "14 May"]], [3600, 2800, 2600]), para(""), para("Notes: ", runs=run("Next review: ", bold=True) + run("Thursday, with the first six route maps.")), ]) sect = ('' '') document = (f'' f'{body}{sect}') main = "application/vnd.openxmlformats-officedocument.wordprocessingml" package(path, { "[Content_Types].xml": types([("/word/document.xml", f"{main}.document.main+xml"), ("/word/styles.xml", f"{main}.styles+xml")]), "_rels/.rels": rels([("r1", "officeDocument", "word/document.xml")]), "word/_rels/document.xml.rels": rels([("r1", "styles", "styles.xml")]), "word/document.xml": document, "word/styles.xml": styles, }) # XLSX: half-year sales by product line. MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"] SALES = { "Trail Maps": [4820, 5140, 6380, 7120, 7950, 8610], "Guidebooks": [6150, 5980, 7210, 8040, 8820, 9470], "Prints": [2310, 2475, 2890, 3120, 3460, 3780], "Workshops": [1800, 2100, 2650, 3400, 3900, 4350], "Postcards": [940, 870, 1120, 1360, 1580, 1710], "Gift Cards": [1250, 980, 760, 820, 1430, 2050], } def col(i): return "ABCDEFGH"[i] def xlsx(path): def text(ref, value, s): return f'{escape(value)}' def num(ref, value, s): return f'{value}' rows = ['' + text("A1", "Product", 1) + "".join(text(f"{col(i + 1)}1", m, 2) for i, m in enumerate(MONTHS)) + text("H1", "Total", 2) + ""] for r, (line, values) in enumerate(SALES.items(), start=2): cells = text(f"A{r}", line, 3) + "".join(num(f"{col(i + 1)}{r}", v, 4) for i, v in enumerate(values)) cells += f'SUM(B{r}:G{r}){sum(values)}' rows.append(f'{cells}') rows.append('' + text("A9", "All lines", 1) + "".join(f'' for i in range(7)) + "") sheet = (f'' '' '' '' f'{"".join(rows)}') styles = (f'' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '') workbook = (f'' '') main = "application/vnd.openxmlformats-officedocument.spreadsheetml" package(path, { "[Content_Types].xml": types([("/xl/workbook.xml", f"{main}.sheet.main+xml"), ("/xl/worksheets/sheet1.xml", f"{main}.worksheet+xml"), ("/xl/styles.xml", f"{main}.styles+xml")]), "_rels/.rels": rels([("r1", "officeDocument", "xl/workbook.xml")]), "xl/_rels/workbook.xml.rels": rels([("r1", "worksheet", "worksheets/sheet1.xml"), ("r2", "styles", "styles.xml")]), "xl/workbook.xml": workbook, "xl/worksheets/sheet1.xml": sheet, "xl/styles.xml": styles, }) # PPTX: four simple slides. def pptx(path): deck = Presentation() deck.slide_width, deck.slide_height = Emu(12192000), Emu(6858000) ink, accent = RGBColor(0x1F, 0x3D, 0x35), RGBColor(0x2F, 0x6B, 0x5B) def paint(frame, size, color, bold=False): for p in frame.paragraphs: for r in p.runs: r.font.size, r.font.color.rgb, r.font.bold, r.font.name = Pt(size), color, bold, "Helvetica Neue" def place(shape, left, top, width, height): shape.left, shape.top, shape.width, shape.height = Inches(left), Inches(top), Inches(width), Inches(height) def bar(slide, left, top, width, height): shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(left), Inches(top), Inches(width), Inches(height)) shape.fill.solid() shape.fill.fore_color.rgb = accent shape.line.fill.background() title = deck.slides.add_slide(deck.slide_layouts[0]) title.shapes.title.text = "Coastal Trails 2027" title.placeholders[1].text = "Spring edition · Planning review" place(title.shapes.title, 1.0, 2.2, 11.33, 1.4) place(title.placeholders[1], 1.0, 3.7, 11.33, 0.8) paint(title.shapes.title.text_frame, 54, ink, True) paint(title.placeholders[1].text_frame, 24, accent) bar(title, 0, 6.9, 13.333, 0.6) slides = [ ("What is new", ["Twelve new routes on the northern cliffs", "A fold-out overview map", "Tide tables for every harbor walk", "Water-resistant, lay-flat binding"]), ("Half-year sales", ["Guidebooks lead at $45,670", "Workshops more than doubled since January", "Trail Maps up 79% since January", "Prints steady at about $3,000 a month"]), ("Next steps", ["Finish route surveys by 12 March", "Maps and illustrations by 2 April", "Proofs to the printer on 23 April", "Launch walk on 14 May"]), ] for heading, bullets in slides: slide = deck.slides.add_slide(deck.slide_layouts[1]) slide.shapes.title.text = heading place(slide.shapes.title, 1.3, 0.6, 10.73, 1.1) paint(slide.shapes.title.text_frame, 40, ink, True) bar(slide, 1.0, 0.75, 0.12, 0.8) body = slide.placeholders[1] place(body, 1.3, 2.0, 10.73, 4.6) body.text_frame.text = bullets[0] for line in bullets[1:]: body.text_frame.add_paragraph().text = line paint(body.text_frame, 28, RGBColor(0x2B, 0x33, 0x30)) deck.core_properties.author = "Coastal Trails" deck.save(path) MARKDOWN = r"""# Trail notes: Harbor Loop A short walk around the old harbor, best at low tide. The path follows the sea wall, climbs to the lighthouse and comes back through the dunes. ## Before you go - [x] Check the tide table - [x] Pack water and a windproof layer - [ ] Download the offline map - [ ] Tell someone your route ## Route at a glance | Section | Distance | Time | | ---------------- | -------- | ------ | | Sea wall | 1.2 km | 15 min | | Lighthouse climb | 0.8 km | 20 min | | Dune path | 2.1 km | 30 min | ## Pace Walking time is distance over pace, plus a minute for every ten meters of climb: $$ t = \frac{d}{v} + \frac{h}{10} $$ ## Plan ```mermaid flowchart LR A[Harbor] --> B[Sea wall] B --> C[Lighthouse] C --> D[Dunes] D --> A ``` """ docx(OUT / "Field Guide Brief.docx") xlsx(OUT / "Sales 2027.xlsx") pptx(OUT / "Coastal Trails 2027.pptx") (OUT / "harbor-loop.md").write_text(MARKDOWN)