make a pcod book journal
mkdir -p /home/daytona/workspace/pcod-journal && ls /home/daytona/workspace/pcod-journal/
~/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 & 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 & 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 & 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 & 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 & stress"],
["Strength Training", "2-3x / week", "30-40 min", "Metabolism & lean mass"],
["Swimming / Cycling", "3x / week", "30-45 min", "Cardio & weight control"],
["Pilates", "2-3x / week", "30 min", "Core strength & flexibility"],
["Dance / Aerobics", "3x / week", "30-40 min", "Mood & 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 & 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 & 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 & 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 & 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()
cd /home/daytona/workspace/pcod-journal && python build_journal.py 2>&1
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
cd /home/daytona/workspace/pcod-journal && grep -n "cw=cw" build_journal.py
~/pcod-journal/build_journal.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/pcod-journal/build_journal.py'
cd /home/daytona/workspace/pcod-journal && python build_journal.py 2>&1
PCOD Diet Lifestyle Journal
PDF Document · PDF
| Page | Content |
|---|---|
| 1 | Cover — personalised name, start/end date & doctor fields |
| 2 | About This Journal — purpose & how to use it |
| 3 | PCOD at a Glance — what it is, common symptoms, key diet principles |
| 4 | PCOD-Friendly Food Guide — Eat More vs Eat Less side-by-side |
| 5 | 7-Day Sample Meal Plan — breakfast, lunch, snack & dinner |
| 6 | Exercise & Lifestyle Guide — table of workouts + lifestyle tips |
| 7 | Weekly Habit Tracker — 8 habits × 7 days, printable weekly |
| 8 | Daily Journal Template — meal log tables, water tracker, symptom sliders (1–10), exercise log, notes |
| 9 | Monthly Cycle & Symptom Tracker — 31-day grid for period, bloating, cramps, mood, energy, weight |
| 10 | Supplements & Medication Log — 10-row table with dose, frequency & prescriber |
| 11 | Doctor Visit Notes — 3 visit slots with structured fields |
| 12 | Goal Setting & Affirmations — 5 goal areas + 6 daily affirmations |
| 13 | Personal Notes & Reflections — free writing page |