make a pcod book journal

Asking for Preferences
Reading File
Reading File
Running Command

mkdir -p /home/daytona/workspace/pcod-journal && ls /home/daytona/workspace/pcod-journal/

Writing File

~/pcod-journal/build_journal.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    PageBreak, HRFlowable, KeepTogether
)
from reportlab.pdfgen import canvas
from reportlab.platypus.flowables import Flowable
import os

# ── Colour palette ──────────────────────────────────────────────────────────
ROSE       = colors.HexColor("#E8768A")
SOFT_ROSE  = colors.HexColor("#F4B8C3")
LAVENDER   = colors.HexColor("#C9B8E8")
MINT       = colors.HexColor("#A8D8C8")
PEACH      = colors.HexColor("#F9C9A0")
CREAM      = colors.HexColor("#FFF8F0")
DARK_ROSE  = colors.HexColor("#C0435A")
SLATE      = colors.HexColor("#4A4A6A")
LIGHT_GREY = colors.HexColor("#F5F5F5")
WHITE      = colors.white

OUTPUT = "/home/daytona/workspace/pcod-journal/PCOD_Diet_Lifestyle_Journal.pdf"
W, H = A4  # 595.27 x 841.89 pts

# ── Custom Flowables ─────────────────────────────────────────────────────────

class ColorBar(Flowable):
    """A full-width horizontal colour band."""
    def __init__(self, width, height, fill_color, radius=0):
        super().__init__()
        self.bar_width = width
        self.bar_height = height
        self.fill_color = fill_color
        self.radius = radius

    def wrap(self, avail_w, avail_h):
        return self.bar_width, self.bar_height

    def draw(self):
        c = self.canv
        c.setFillColor(self.fill_color)
        if self.radius:
            c.roundRect(0, 0, self.bar_width, self.bar_height,
                        self.radius, stroke=0, fill=1)
        else:
            c.rect(0, 0, self.bar_width, self.bar_height, stroke=0, fill=1)


class DottedLine(Flowable):
    """A dotted writing line."""
    def __init__(self, width, y_offset=0):
        super().__init__()
        self.line_width = width
        self.y_offset = y_offset

    def wrap(self, avail_w, avail_h):
        return self.line_width, 14

    def draw(self):
        c = self.canv
        c.setStrokeColor(colors.HexColor("#D0B8B8"))
        c.setDash([2, 4])
        c.line(0, 4, self.line_width, 4)
        c.setDash()


class CheckBox(Flowable):
    """A small checkbox with label."""
    def __init__(self, label, width=200):
        super().__init__()
        self.label = label
        self.cb_width = width

    def wrap(self, avail_w, avail_h):
        return self.cb_width, 18

    def draw(self):
        c = self.canv
        c.setStrokeColor(ROSE)
        c.setFillColor(WHITE)
        c.roundRect(0, 2, 12, 12, 2, stroke=1, fill=1)
        c.setFont("Helvetica", 9)
        c.setFillColor(SLATE)
        c.drawString(18, 3, self.label)


class MoodBox(Flowable):
    """5-emoji mood row."""
    def __init__(self, width):
        super().__init__()
        self.mb_width = width

    def wrap(self, avail_w, avail_h):
        return self.mb_width, 40

    def draw(self):
        c = self.canv
        emojis = [":(", ":/", ":|", ":)", ":D"]
        labels = ["Awful", "Bad", "Okay", "Good", "Great"]
        slot = self.mb_width / 5
        for i, (em, lb) in enumerate(zip(emojis, labels)):
            x = i * slot + slot / 2
            c.setFillColor(SOFT_ROSE)
            c.circle(x, 22, 14, stroke=0, fill=1)
            c.setFillColor(SLATE)
            c.setFont("Helvetica-Bold", 10)
            c.drawCentredString(x, 18, em)
            c.setFont("Helvetica", 7)
            c.drawCentredString(x, 5, lb)
            # circle to tick
            c.setStrokeColor(ROSE)
            c.setFillColor(WHITE)
            c.circle(x, 22, 14, stroke=1, fill=0)


# ── Page templates ───────────────────────────────────────────────────────────

def cover_page_callback(canvas_obj, doc):
    """Decorative cover background."""
    canvas_obj.saveState()
    # Background
    canvas_obj.setFillColor(CREAM)
    canvas_obj.rect(0, 0, W, H, stroke=0, fill=1)
    # Top banner
    canvas_obj.setFillColor(ROSE)
    canvas_obj.rect(0, H - 140, W, 140, stroke=0, fill=1)
    # Bottom strip
    canvas_obj.setFillColor(SOFT_ROSE)
    canvas_obj.rect(0, 0, W, 50, stroke=0, fill=1)
    # Decorative circles
    canvas_obj.setFillColor(colors.HexColor("#F4B8C360"))
    canvas_obj.circle(W - 60, H - 80, 60, stroke=0, fill=1)
    canvas_obj.setFillColor(colors.HexColor("#C9B8E860"))
    canvas_obj.circle(50, 80, 45, stroke=0, fill=1)
    canvas_obj.setFillColor(colors.HexColor("#A8D8C860"))
    canvas_obj.circle(W - 30, 200, 35, stroke=0, fill=1)
    canvas_obj.restoreState()


def standard_page(canvas_obj, doc):
    """Header/footer for inner pages."""
    canvas_obj.saveState()
    # Background
    canvas_obj.setFillColor(CREAM)
    canvas_obj.rect(0, 0, W, H, stroke=0, fill=1)
    # Top accent
    canvas_obj.setFillColor(SOFT_ROSE)
    canvas_obj.rect(0, H - 20, W, 20, stroke=0, fill=1)
    # Bottom accent
    canvas_obj.setFillColor(LAVENDER)
    canvas_obj.rect(0, 0, W, 18, stroke=0, fill=1)
    # Page number
    canvas_obj.setFont("Helvetica", 8)
    canvas_obj.setFillColor(SLATE)
    canvas_obj.drawCentredString(W / 2, 5, f"PCOD Diet & Lifestyle Journal  •  {doc.page}")
    canvas_obj.restoreState()


# ── Style helpers ─────────────────────────────────────────────────────────────

def styles():
    base = getSampleStyleSheet()
    s = {}

    s["cover_title"] = ParagraphStyle(
        "cover_title", fontName="Helvetica-Bold", fontSize=34,
        textColor=WHITE, alignment=TA_CENTER, leading=42, spaceAfter=6
    )
    s["cover_sub"] = ParagraphStyle(
        "cover_sub", fontName="Helvetica", fontSize=14,
        textColor=colors.HexColor("#FFE0E8"), alignment=TA_CENTER, leading=20
    )
    s["cover_name"] = ParagraphStyle(
        "cover_name", fontName="Helvetica", fontSize=11,
        textColor=SLATE, alignment=TA_CENTER, leading=16
    )
    s["chapter"] = ParagraphStyle(
        "chapter", fontName="Helvetica-Bold", fontSize=20,
        textColor=DARK_ROSE, alignment=TA_CENTER, leading=26,
        spaceBefore=10, spaceAfter=6
    )
    s["section"] = ParagraphStyle(
        "section", fontName="Helvetica-Bold", fontSize=13,
        textColor=DARK_ROSE, alignment=TA_LEFT, leading=18,
        spaceBefore=10, spaceAfter=4
    )
    s["body"] = ParagraphStyle(
        "body", fontName="Helvetica", fontSize=10,
        textColor=SLATE, alignment=TA_JUSTIFY, leading=16,
        spaceAfter=6
    )
    s["small"] = ParagraphStyle(
        "small", fontName="Helvetica", fontSize=8,
        textColor=colors.HexColor("#888899"), alignment=TA_CENTER,
        leading=12, spaceAfter=4
    )
    s["label"] = ParagraphStyle(
        "label", fontName="Helvetica-Bold", fontSize=9,
        textColor=SLATE, alignment=TA_LEFT, leading=14
    )
    s["bullet"] = ParagraphStyle(
        "bullet", fontName="Helvetica", fontSize=10,
        textColor=SLATE, leading=16, leftIndent=14,
        spaceAfter=3, bulletIndent=4
    )
    s["tip_box"] = ParagraphStyle(
        "tip_box", fontName="Helvetica", fontSize=9,
        textColor=SLATE, leading=14, leftIndent=10, rightIndent=10
    )
    return s


# ── Section builders ──────────────────────────────────────────────────────────

def section_header(title, bg=ROSE, fg=WHITE, content_width=None):
    cw = content_width or (W - 4 * cm)
    data = [[Paragraph(title, ParagraphStyle(
        "sh", fontName="Helvetica-Bold", fontSize=14,
        textColor=fg, alignment=TA_CENTER, leading=18
    ))]]
    t = Table(data, colWidths=[cw])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), bg),
        ("ROUNDEDCORNERS", [8]),
        ("TOPPADDING", (0, 0), (-1, -1), 8),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
        ("LEFTPADDING", (0, 0), (-1, -1), 12),
        ("RIGHTPADDING", (0, 0), (-1, -1), 12),
    ]))
    return t


def info_card(title, body_text, bg_color, st, content_width=None):
    cw = content_width or (W - 4 * cm)
    items = [
        Paragraph(f"<b>{title}</b>", ParagraphStyle(
            "ct", fontName="Helvetica-Bold", fontSize=11,
            textColor=DARK_ROSE, leading=16
        )),
        Spacer(1, 4),
        Paragraph(body_text, st["body"]),
    ]
    data = [[items]]
    t = Table(data, colWidths=[cw - 16])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), bg_color),
        ("ROUNDEDCORNERS", [6]),
        ("TOPPADDING", (0, 0), (-1, -1), 10),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 10),
        ("LEFTPADDING", (0, 0), (-1, -1), 12),
        ("RIGHTPADDING", (0, 0), (-1, -1), 12),
    ]))
    return t


def writing_lines(n, st, content_width=None):
    cw = content_width or (W - 4 * cm)
    elems = []
    for _ in range(n):
        elems.append(DottedLine(cw))
        elems.append(Spacer(1, 6))
    return elems


def two_col_table(left_items, right_items, st, content_width=None):
    """Two-column layout using a table."""
    cw = content_width or (W - 4 * cm)
    col = (cw - 10) / 2

    def col_elems(items):
        elems = []
        for item in items:
            elems.append(Paragraph(f"• {item}", st["bullet"]))
        return elems

    data = [[col_elems(left_items), col_elems(right_items)]]
    t = Table(data, colWidths=[col, col])
    t.setStyle(TableStyle([
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("LEFTPADDING", (0, 0), (-1, -1), 0),
        ("RIGHTPADDING", (0, 0), (-1, -1), 0),
    ]))
    return t


def habit_tracker_table(st, content_width=None):
    cw = content_width or (W - 4 * cm)
    days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    habits = [
        "Drink 8 glasses of water",
        "Eat 3 balanced meals",
        "No refined sugar",
        "30-min exercise",
        "8 hrs of sleep",
        "Take supplements",
        "Mindfulness/meditation",
        "Limit caffeine",
    ]
    header_row = ["Habit"] + days
    col_w = [cw - 7 * 28] + [28] * 7
    data = [header_row]
    for h in habits:
        row = [h] + ["○"] * 7
        data.append(row)

    style = TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), ROSE),
        ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, 0), 9),
        ("ALIGN", (1, 0), (-1, -1), "CENTER"),
        ("ALIGN", (0, 0), (0, -1), "LEFT"),
        ("FONTNAME", (0, 1), (0, -1), "Helvetica"),
        ("FONTSIZE", (0, 1), (-1, -1), 9),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#FFF0F3")]),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#DDBBC8")),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING", (0, 0), (0, -1), 6),
        ("TEXTCOLOR", (1, 1), (-1, -1), colors.HexColor("#CCAAAA")),
        ("FONTNAME", (1, 1), (-1, -1), "Helvetica-Bold"),
        ("FONTSIZE", (1, 1), (-1, -1), 12),
    ])
    t = Table(data, colWidths=col_w)
    t.setStyle(style)
    return t


def meal_log_table(meal, st, content_width=None):
    cw = content_width or (W - 4 * cm)
    col_w = [cw * 0.45, cw * 0.20, cw * 0.20, cw * 0.15]
    header = ["Food Item", "Portion", "Calories (approx)", "PCOD-Friendly?"]
    rows = [header] + [["", "", "", ""] for _ in range(5)]
    style = TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LAVENDER),
        ("TEXTCOLOR", (0, 0), (-1, 0), SLATE),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, 0), 9),
        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CCBBDD")),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#F5F0FF")]),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
        ("FONTSIZE", (0, 1), (-1, -1), 9),
    ])
    data = [header] + [["", "", "", "  ○ Yes  ○ No"] for _ in range(5)]
    t = Table(data, colWidths=col_w)
    t.setStyle(style)
    return t


def symptom_slider(label, st, content_width=None):
    cw = content_width or (W - 4 * cm)
    nums = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
    col_w = [(cw - 80) / 10] * 10
    row = [Paragraph(n, ParagraphStyle(
        "sn", fontName="Helvetica-Bold", fontSize=9,
        textColor=SLATE, alignment=TA_CENTER, leading=12
    )) for n in nums]
    t = Table([row], colWidths=col_w)
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#FFF0F3")),
        ("GRID", (0, 0), (-1, -1), 0.5, SOFT_ROSE),
        ("TOPPADDING", (0, 0), (-1, -1), 4),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
    ]))
    outer = Table(
        [[Paragraph(f"<b>{label}</b>", ParagraphStyle(
            "lbl", fontName="Helvetica-Bold", fontSize=9,
            textColor=SLATE, leading=14
        )), t]],
        colWidths=[80, cw - 80]
    )
    outer.setStyle(TableStyle([
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("LEFTPADDING", (0, 0), (-1, -1), 0),
        ("RIGHTPADDING", (0, 0), (-1, -1), 0),
    ]))
    return outer


# ── Main build ────────────────────────────────────────────────────────────────

def build():
    doc = SimpleDocTemplate(
        OUTPUT, pagesize=A4,
        rightMargin=2 * cm, leftMargin=2 * cm,
        topMargin=2.2 * cm, bottomMargin=1.8 * cm
    )

    st = styles()
    cw = W - 4 * cm  # usable content width
    story = []

    # ══════════════════════════════════════════════
    # COVER PAGE
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 3.5 * cm))
    story.append(Paragraph("PCOD", st["cover_title"]))
    story.append(Paragraph("Diet &amp; Lifestyle Journal", st["cover_title"]))
    story.append(Spacer(1, 0.6 * cm))
    story.append(Paragraph(
        "Your personal companion for managing Polycystic Ovary Disease<br/>"
        "through nourishment, movement and self-care",
        st["cover_sub"]
    ))
    story.append(Spacer(1, 2.5 * cm))
    story.append(Paragraph("Name: ____________________________________", st["cover_name"]))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph("Start Date: ____________  End Date: ____________", st["cover_name"]))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph("Doctor / Nutritionist: ____________________________________", st["cover_name"]))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 2 – ABOUT THIS JOURNAL
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("About This Journal", bg=ROSE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph(
        "This journal is your daily guide on the path to hormonal balance. "
        "PCOD (Polycystic Ovary Disease) is a hormonal condition that affects "
        "millions of women worldwide. While medical treatment is important, "
        "diet and lifestyle changes are among the most powerful tools available "
        "to manage symptoms, regulate cycles and improve quality of life.",
        st["body"]
    ))
    story.append(Spacer(1, 0.3 * cm))

    cards = [
        ("Track your meals", CREAM,
         "Log every meal with portions and food quality. Identify patterns "
         "that worsen or improve your symptoms."),
        ("Move your body", colors.HexColor("#EDF9F5"),
         "Record your daily movement — even a 20-minute walk counts! Consistent "
         "exercise improves insulin sensitivity in PCOD."),
        ("Monitor symptoms", colors.HexColor("#F0EEF9"),
         "Track bloating, fatigue, acne, mood and cycle irregularities to share "
         "with your healthcare team."),
        ("Celebrate wins", colors.HexColor("#FFF4EC"),
         "Note small victories — drinking enough water, choosing a salad, "
         "sleeping 8 hours. Progress is progress."),
    ]
    for title, bg, body in cards:
        story.append(info_card(title, body, bg, st, cw))
        story.append(Spacer(1, 0.25 * cm))

    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph(
        "Always consult your doctor, gynaecologist or registered dietitian "
        "before making significant dietary changes.",
        st["small"]
    ))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 3 – PCOD AT A GLANCE
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("PCOD at a Glance", bg=LAVENDER, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))

    story.append(Paragraph("What is PCOD?", st["section"]))
    story.append(Paragraph(
        "PCOD is a condition in which the ovaries produce multiple immature or "
        "partially mature eggs that develop into cysts. This disrupts hormonal "
        "balance — especially androgens (male hormones) — leading to irregular "
        "periods, weight changes, acne, excess hair growth and fertility challenges.",
        st["body"]
    ))

    story.append(Paragraph("Common Symptoms", st["section"]))
    syms_left = ["Irregular periods", "Weight gain", "Acne & oily skin", "Hair thinning"]
    syms_right = ["Excess facial/body hair", "Fatigue", "Mood swings", "Bloating"]
    story.append(two_col_table(syms_left, syms_right, st, cw))

    story.append(Paragraph("Key Diet Principles for PCOD", st["section"]))
    principles = [
        ("<b>Low Glycaemic Index (GI) foods</b> — choose whole grains, legumes, vegetables and fruits "
         "that release sugar slowly, preventing insulin spikes."),
        ("<b>Anti-inflammatory eating</b> — include turmeric, ginger, berries, leafy greens, "
         "fatty fish, nuts and seeds."),
        ("<b>Adequate protein</b> — eggs, pulses, paneer, tofu, chicken and fish help balance "
         "hormones and keep you full."),
        ("<b>Healthy fats</b> — avocado, olive oil, flaxseeds and walnuts support hormone production."),
        ("<b>Limit refined carbs and sugar</b> — white bread, sugary drinks, pastries and processed "
         "snacks worsen insulin resistance."),
        ("<b>Eat mindfully</b> — chew slowly, avoid screen time while eating, stop at 80% full."),
    ]
    for p in principles:
        story.append(Paragraph(f"• {p}", st["bullet"]))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 4 – FOODS: EAT MORE / EAT LESS
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("PCOD-Friendly Food Guide", bg=MINT, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.5 * cm))

    col = (cw - 10) / 2
    eat_more = [
        "Brown rice, quinoa, oats",
        "Lentils, chickpeas, kidney beans",
        "Leafy greens (spinach, kale, fenugreek)",
        "Colourful vegetables (carrot, beet, broccoli)",
        "Berries, apples, pears, guava",
        "Eggs, fish, tofu, paneer",
        "Almonds, walnuts, flaxseeds, chia",
        "Olive oil, coconut oil (moderate)",
        "Turmeric, cinnamon, ginger",
        "Green tea, herbal teas",
        "Plain yoghurt / curd",
        "Plenty of water (2.5–3 L/day)",
    ]
    eat_less = [
        "White bread, maida, white rice",
        "Sugary drinks, packaged juices",
        "Sweets, pastries, cakes, biscuits",
        "Deep-fried & processed snacks",
        "Margarine, trans fats",
        "Excess red meat",
        "Alcohol",
        "High-sodium packaged foods",
        "Artificial sweeteners (excess)",
        "Skipping meals",
        "Very low-calorie crash diets",
        "Excess caffeine",
    ]

    more_header = Paragraph(
        "✓  Eat More", ParagraphStyle(
            "mh", fontName="Helvetica-Bold", fontSize=12,
            textColor=WHITE, alignment=TA_CENTER, leading=16
        )
    )
    less_header = Paragraph(
        "✗  Eat Less / Avoid", ParagraphStyle(
            "lh", fontName="Helvetica-Bold", fontSize=12,
            textColor=WHITE, alignment=TA_CENTER, leading=16
        )
    )

    more_items = [Paragraph(f"• {i}", st["bullet"]) for i in eat_more]
    less_items = [Paragraph(f"• {i}", st["bullet"]) for i in eat_less]

    t = Table(
        [[more_header, less_header],
         [more_items, less_items]],
        colWidths=[col, col]
    )
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (0, 0), MINT),
        ("BACKGROUND", (1, 0), (1, 0), ROSE),
        ("BACKGROUND", (0, 1), (0, 1), colors.HexColor("#EEF9F5")),
        ("BACKGROUND", (1, 1), (1, 1), colors.HexColor("#FFF0F3")),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CCDDCC")),
        ("TOPPADDING", (0, 0), (-1, -1), 8),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
        ("ROUNDEDCORNERS", [4]),
    ]))
    story.append(t)
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 5 – SAMPLE MEAL PLAN
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("7-Day Sample PCOD Meal Plan", bg=PEACH, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph(
        "This plan is a general guide. Adjust portions to your calorie needs and preferences. "
        "Always consult your dietitian for a personalised plan.",
        st["small"]
    ))
    story.append(Spacer(1, 0.3 * cm))

    days_plan = [
        ("Mon", "Oats with berries &amp; flaxseeds", "Brown rice + dal + salad", "Handful almonds", "Grilled fish / tofu + sauteed veggies"),
        ("Tue", "Scrambled eggs + whole-wheat toast", "Quinoa khichdi + curd", "Apple slices + peanut butter", "Chicken soup + multigrain roti"),
        ("Wed", "Smoothie (spinach, banana, chia)", "Rajma curry + brown rice", "Roasted chana", "Stir-fried veggies + paneer"),
        ("Thu", "Overnight oats with nuts", "Lentil soup + salad + roti", "Walnuts + green tea", "Baked salmon + steamed broccoli"),
        ("Fri", "Moong dal chilla + mint chutney", "Chickpea salad + multigrain bread", "Yoghurt parfait", "Egg curry + cauliflower rice"),
        ("Sat", "Poha with veggies &amp; peanuts", "Mixed veg curry + brown rice", "Fruit chaat", "Tofu stir-fry + soba noodles"),
        ("Sun", "Idli (ragi) + sambar", "Palak paneer + quinoa", "Cucumber hummus sticks", "Grilled chicken / mushrooms + salad"),
    ]
    header = ["Day", "Breakfast", "Lunch", "Snack", "Dinner"]
    col_ws = [30, (cw - 30) * 0.27, (cw - 30) * 0.27, (cw - 30) * 0.19, (cw - 30) * 0.27]

    table_data = [header]
    for row in days_plan:
        table_data.append([Paragraph(c, ParagraphStyle(
            "mp", fontName="Helvetica", fontSize=8,
            textColor=SLATE, leading=11, alignment=TA_LEFT
        )) for c in row])

    meal_style = TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), PEACH),
        ("TEXTCOLOR", (0, 0), (-1, 0), SLATE),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, 0), 9),
        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#FFFAF0")]),
        ("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#E0C8A0")),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING", (0, 0), (-1, -1), 4),
        ("RIGHTPADDING", (0, 0), (-1, -1), 4),
    ])
    t = Table(table_data, colWidths=col_ws)
    t.setStyle(meal_style)
    story.append(t)
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 6 – EXERCISE & LIFESTYLE
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Exercise &amp; Lifestyle Guide", bg=LAVENDER, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))

    story.append(Paragraph("Recommended Exercise Types", st["section"]))
    ex_data = [
        ["Exercise", "Frequency", "Duration", "Benefit"],
        ["Brisk Walking", "5-7x / week", "30-45 min", "Insulin sensitivity"],
        ["Yoga", "3-5x / week", "45-60 min", "Hormonal balance &amp; stress"],
        ["Strength Training", "2-3x / week", "30-40 min", "Metabolism &amp; lean mass"],
        ["Swimming / Cycling", "3x / week", "30-45 min", "Cardio &amp; weight control"],
        ["Pilates", "2-3x / week", "30 min", "Core strength &amp; flexibility"],
        ["Dance / Aerobics", "3x / week", "30-40 min", "Mood &amp; calorie burn"],
    ]
    ex_col_w = [cw * 0.28, cw * 0.20, cw * 0.18, cw * 0.34]
    ex_t = Table(
        [[Paragraph(c, ParagraphStyle("eh", fontName="Helvetica-Bold" if r == 0 else "Helvetica",
                    fontSize=9, textColor=WHITE if r == 0 else SLATE,
                    alignment=TA_CENTER, leading=12)) for c in row]
         for r, row in enumerate(ex_data)],
        colWidths=ex_col_w
    )
    ex_t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LAVENDER),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#F5F0FF")]),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CCBBDD")),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
    ]))
    story.append(ex_t)

    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph("Lifestyle Tips", st["section"]))
    tips = [
        ("Sleep 7-9 hours nightly.", "Poor sleep elevates cortisol and worsens insulin resistance."),
        ("Manage stress actively.", "Try deep breathing, journalling, art or spending time in nature."),
        ("Limit endocrine disruptors.", "Reduce plastic containers, non-stick pans and synthetic fragrances."),
        ("Stay hydrated.", "Aim for 2.5-3 litres of water daily; herbal teas count."),
        ("Maintain a healthy weight.", "Even a 5-10% weight reduction can restore menstrual regularity."),
        ("Minimise screen time before bed.", "Blue light affects melatonin and hormonal rhythms."),
    ]
    for tip_title, tip_body in tips:
        tip_text = f"<b>{tip_title}</b> {tip_body}"
        data = [[Paragraph(tip_text, st["tip_box"])]]
        tip_t = Table(data, colWidths=[cw])
        tip_t.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#F5F0FF")),
            ("ROUNDEDCORNERS", [5]),
            ("TOPPADDING", (0, 0), (-1, -1), 6),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
            ("LEFTPADDING", (0, 0), (-1, -1), 10),
            ("RIGHTPADDING", (0, 0), (-1, -1), 10),
            ("BOX", (0, 0), (-1, -1), 0.5, LAVENDER),
        ]))
        story.append(tip_t)
        story.append(Spacer(1, 4))

    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 7 – WEEKLY HABIT TRACKER
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Weekly Habit Tracker", bg=ROSE, cw=cw))
    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph(
        "Fill in the circle (○ → ●) each day you complete the habit. "
        "Use one copy per week. Print as many times as needed!",
        st["small"]
    ))
    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph(
        "Week of: ________________________    Weight: ________ kg    Energy Level (1-10): ________",
        st["label"]
    ))
    story.append(Spacer(1, 0.4 * cm))
    story.append(habit_tracker_table(st, cw))
    story.append(Spacer(1, 0.5 * cm))
    story.append(Paragraph("Notes / Observations this week:", st["label"]))
    story.append(Spacer(1, 0.15 * cm))
    story.extend(writing_lines(4, st, cw))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 8 – DAILY JOURNAL TEMPLATE (2 per page)
    # ══════════════════════════════════════════════
    for day_n in range(1, 3):
        story.append(Spacer(1, 0.2 * cm))
        story.append(section_header(
            f"Daily Journal  —  Day {day_n}", bg=SOFT_ROSE, fg=SLATE, cw=cw
        ))
        story.append(Spacer(1, 0.2 * cm))

        meta_row = Table(
            [[Paragraph("Date: _______________", st["label"]),
              Paragraph("Cycle Day: _______", st["label"]),
              Paragraph("Mood: ○ Great  ○ Good  ○ Okay  ○ Low", st["label"])]],
            colWidths=[cw * 0.35, cw * 0.20, cw * 0.45]
        )
        meta_row.setStyle(TableStyle([
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ("LEFTPADDING", (0, 0), (-1, -1), 0),
            ("RIGHTPADDING", (0, 0), (-1, -1), 0),
        ]))
        story.append(meta_row)
        story.append(Spacer(1, 0.2 * cm))

        # Meals
        for meal in ["Breakfast", "Lunch", "Dinner", "Snacks"]:
            story.append(Paragraph(f"<b>{meal}</b>", st["label"]))
            story.append(meal_log_table(meal, st, cw))
            story.append(Spacer(1, 0.15 * cm))

        # Water tracker
        water_cols = [18] * 10
        water_cells = [Paragraph(
            "○", ParagraphStyle("wt", fontName="Helvetica-Bold", fontSize=14,
                                textColor=MINT, alignment=TA_CENTER, leading=18)
        ) for _ in range(10)]
        water_t = Table([water_cells], colWidths=water_cols)
        water_t.setStyle(TableStyle([
            ("LEFTPADDING", (0, 0), (-1, -1), 0),
            ("RIGHTPADDING", (0, 0), (-1, -1), 0),
            ("ALIGN", (0, 0), (-1, -1), "CENTER"),
        ]))
        water_row = Table(
            [[Paragraph("<b>Water (each ○ = 1 glass):</b>", st["label"]), water_t]],
            colWidths=[160, cw - 160]
        )
        water_row.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                                        ("LEFTPADDING", (0, 0), (-1, -1), 0),
                                        ("RIGHTPADDING", (0, 0), (-1, -1), 0)]))
        story.append(water_row)
        story.append(Spacer(1, 0.15 * cm))

        # Exercise
        ex_row = Table(
            [[Paragraph("<b>Exercise Today:</b>", st["label"]),
              Paragraph("Type: _________________  Duration: ________ min  Intensity: ○ Low  ○ Med  ○ High", st["body"])]],
            colWidths=[100, cw - 100]
        )
        ex_row.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                                     ("LEFTPADDING", (0, 0), (-1, -1), 0),
                                     ("RIGHTPADDING", (0, 0), (-1, -1), 0)]))
        story.append(ex_row)
        story.append(Spacer(1, 0.15 * cm))

        # Symptoms
        story.append(Paragraph("<b>Symptom Check  (circle your level 1–10):</b>", st["label"]))
        for sym in ["Bloating", "Fatigue", "Cramps", "Acne"]:
            story.append(symptom_slider(sym, st, cw))
            story.append(Spacer(1, 3))

        # Notes
        story.append(Spacer(1, 0.1 * cm))
        story.append(Paragraph("<b>Notes / Gratitude:</b>", st["label"]))
        story.extend(writing_lines(2, st, cw))

        if day_n == 1:
            story.append(HRFlowable(width=cw, thickness=1, color=SOFT_ROSE,
                                    spaceAfter=8, spaceBefore=4))

    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 9 – MONTHLY CYCLE & SYMPTOM TRACKER
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Monthly Cycle &amp; Symptom Tracker", bg=ROSE, cw=cw))
    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph(
        "Month: _________________________    Year: ___________    Last Period Start: ___________",
        st["label"]
    ))
    story.append(Spacer(1, 0.3 * cm))

    days_row = [str(i) for i in range(1, 32)]
    sym_rows = [
        "Period (P=flow, S=spotting)",
        "Bloating (1-5)",
        "Cramps (1-5)",
        "Acne (1-5)",
        "Mood (H/N/L)",
        "Energy (H/N/L)",
        "Exercise done?",
        "Weight (kg)",
    ]
    col_days_w = [(cw - 80) / 31] * 31
    header_r = [""] + days_row
    data_rows = [header_r]
    for sr in sym_rows:
        data_rows.append([Paragraph(sr, ParagraphStyle(
            "sr", fontName="Helvetica", fontSize=6.5,
            textColor=SLATE, leading=9, alignment=TA_LEFT
        ))] + [""] * 31)

    cycle_t = Table(data_rows, colWidths=[80] + col_days_w)
    cycle_t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), ROSE),
        ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, 0), 7),
        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#FFF0F3")]),
        ("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#DDBBC8")),
        ("TOPPADDING", (0, 0), (-1, -1), 4),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("LEFTPADDING", (0, 0), (0, -1), 3),
    ]))
    story.append(cycle_t)
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph("<b>Cycle Notes / Observations:</b>", st["label"]))
    story.extend(writing_lines(3, st, cw))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 10 – SUPPLEMENTS & MEDICATION LOG
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Supplements &amp; Medication Log", bg=PEACH, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))
    story.append(Paragraph(
        "Commonly prescribed supplements for PCOD include Inositol, Vitamin D, "
        "Omega-3, Magnesium, NAC and Zinc. Always take supplements under medical supervision.",
        st["body"]
    ))
    story.append(Spacer(1, 0.4 * cm))

    sup_header = ["Supplement / Medication", "Dose", "Frequency", "Prescribed By", "Start Date", "Notes"]
    sup_data = [sup_header] + [["", "", "", "", "", ""] for _ in range(10)]
    sup_col_w = [cw * 0.27, cw * 0.10, cw * 0.13, cw * 0.16, cw * 0.14, cw * 0.20]
    sup_t = Table(
        [[Paragraph(c, ParagraphStyle("sh", fontName="Helvetica-Bold" if r == 0 else "Helvetica",
                    fontSize=8.5, textColor=WHITE if r == 0 else SLATE,
                    alignment=TA_CENTER, leading=11)) for c in row]
         for r, row in enumerate(sup_data)],
        colWidths=sup_col_w
    )
    sup_t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), PEACH),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, colors.HexColor("#FFFAF0")]),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#E0C8A0")),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
    ]))
    story.append(sup_t)
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 11 – DOCTOR VISIT NOTES
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Doctor Visit Notes", bg=LAVENDER, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))

    for visit_n in range(1, 4):
        story.append(KeepTogether([
            Paragraph(f"Visit {visit_n}", ParagraphStyle(
                "vn", fontName="Helvetica-Bold", fontSize=11,
                textColor=DARK_ROSE, leading=16
            )),
            Spacer(1, 0.1 * cm),
            Table(
                [[Paragraph("Date: ___________________", st["label"]),
                  Paragraph("Doctor: ___________________________", st["label"]),
                  Paragraph("Clinic: __________________________", st["label"])]],
                colWidths=[cw * 0.25, cw * 0.38, cw * 0.37]
            ),
            Spacer(1, 0.1 * cm),
            Paragraph("<b>Reason for visit:</b>", st["label"]),
            *writing_lines(2, st, cw),
            Paragraph("<b>Tests ordered / results:</b>", st["label"]),
            *writing_lines(2, st, cw),
            Paragraph("<b>Doctor's advice / treatment changes:</b>", st["label"]),
            *writing_lines(2, st, cw),
            Paragraph("<b>Next appointment:</b>  ___________________", st["label"]),
            Spacer(1, 0.3 * cm),
            HRFlowable(width=cw, thickness=0.8, color=LAVENDER, spaceAfter=6),
        ]))

    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 12 – GOAL SETTING & AFFIRMATIONS
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("My Goals &amp; Affirmations", bg=ROSE, cw=cw))
    story.append(Spacer(1, 0.4 * cm))

    story.append(Paragraph("My 4-Week Goals", st["section"]))
    goal_areas = [
        ("Diet Goal", "e.g., Eat 5 servings of vegetables daily"),
        ("Exercise Goal", "e.g., Walk 30 minutes 5 days a week"),
        ("Sleep Goal", "e.g., Be in bed by 10:30 PM"),
        ("Stress Goal", "e.g., Practice 10-min meditation daily"),
        ("Overall Health Goal", "e.g., Lose 2 kg in 4 weeks"),
    ]
    for area, placeholder in goal_areas:
        story.append(Paragraph(f"<b>{area}:</b>  <i>{placeholder}</i>", st["label"]))
        story.extend(writing_lines(2, st, cw))

    story.append(Spacer(1, 0.3 * cm))
    story.append(Paragraph("Daily Affirmations", st["section"]))
    affirmations = [
        "I am healing and getting stronger every day.",
        "I nourish my body with love and intention.",
        "My hormones are finding balance.",
        "I choose foods that support my wellbeing.",
        "I am patient and kind with myself.",
        "Small steps every day lead to lasting change.",
    ]
    for aff in affirmations:
        data = [[Paragraph(f"♡  {aff}", ParagraphStyle(
            "af", fontName="Helvetica-Oblique", fontSize=10,
            textColor=DARK_ROSE, alignment=TA_CENTER, leading=16
        ))]]
        aff_t = Table(data, colWidths=[cw])
        aff_t.setStyle(TableStyle([
            ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#FFF0F5")),
            ("TOPPADDING", (0, 0), (-1, -1), 5),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
            ("BOX", (0, 0), (-1, -1), 0.5, SOFT_ROSE),
            ("ROUNDEDCORNERS", [4]),
        ]))
        story.append(aff_t)
        story.append(Spacer(1, 4))

    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # PAGE 13 – NOTES & REFLECTIONS
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 0.3 * cm))
    story.append(section_header("Personal Notes &amp; Reflections", bg=MINT, fg=SLATE, cw=cw))
    story.append(Spacer(1, 0.5 * cm))
    story.extend(writing_lines(28, st, cw))
    story.append(PageBreak())

    # ══════════════════════════════════════════════
    # BACK COVER / FINAL PAGE
    # ══════════════════════════════════════════════
    story.append(Spacer(1, 4 * cm))
    story.append(section_header("Remember", bg=ROSE, cw=cw))
    story.append(Spacer(1, 0.6 * cm))

    final_msgs = [
        "PCOD is manageable. Thousands of women have found their balance through "
        "consistent diet, movement and self-care.",
        "You are not defined by your diagnosis. You are defined by your courage "
        "and commitment to your own wellbeing.",
        "Be gentle with yourself on the hard days. Every small choice matters.",
        "Share this journal with your doctor or nutritionist to get the most "
        "personalised guidance.",
    ]
    for msg in final_msgs:
        story.append(Paragraph(f"♡  {msg}", ParagraphStyle(
            "fm", fontName="Helvetica-Oblique", fontSize=11,
            textColor=DARK_ROSE, alignment=TA_CENTER, leading=18,
            spaceAfter=12
        )))

    story.append(Spacer(1, 1.5 * cm))
    story.append(Paragraph(
        "This journal is for informational purposes only and does not constitute "
        "medical advice. Always consult a qualified healthcare professional.",
        st["small"]
    ))

    # ── BUILD ──────────────────────────────────────
    def page_callback(c, d):
        if d.page == 1:
            cover_page_callback(c, d)
        else:
            standard_page(c, d)

    doc.build(story, onFirstPage=page_callback, onLaterPages=page_callback)
    print(f"PDF saved to: {OUTPUT}")


if __name__ == "__main__":
    build()
Running Command

cd /home/daytona/workspace/pcod-journal && python build_journal.py 2>&1

Running Command

cd /home/daytona/workspace/pcod-journal && sed -i 's/section_header(\(.*\), cw=cw)/section_header(\1, content_width=cw)/g' build_journal.py && python build_journal.py 2>&1

Running Command

cd /home/daytona/workspace/pcod-journal && grep -n "cw=cw" build_journal.py

Reading File
Editing File

~/pcod-journal/build_journal.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/pcod-journal/build_journal.py'
Running Command

cd /home/daytona/workspace/pcod-journal && python build_journal.py 2>&1

Your PCOD Diet & Lifestyle Journal is ready!

PCOD Diet Lifestyle Journal

PDF Document · PDF


Here's what's inside the 13-page full-colour journal:
PageContent
1Cover — personalised name, start/end date & doctor fields
2About This Journal — purpose & how to use it
3PCOD at a Glance — what it is, common symptoms, key diet principles
4PCOD-Friendly Food Guide — Eat More vs Eat Less side-by-side
57-Day Sample Meal Plan — breakfast, lunch, snack & dinner
6Exercise & Lifestyle Guide — table of workouts + lifestyle tips
7Weekly Habit Tracker — 8 habits × 7 days, printable weekly
8Daily Journal Template — meal log tables, water tracker, symptom sliders (1–10), exercise log, notes
9Monthly Cycle & Symptom Tracker — 31-day grid for period, bloating, cramps, mood, energy, weight
10Supplements & Medication Log — 10-row table with dose, frequency & prescriber
11Doctor Visit Notes — 3 visit slots with structured fields
12Goal Setting & Affirmations — 5 goal areas + 6 daily affirmations
13Personal Notes & Reflections — free writing page
This is a shared conversation. Sign in to Orris to start your own chat.