mybusinessdraftDEV / app /DocumentGeneration /table_of_content.py
Zenaight's picture
Upload 24 files
2d2c483 verified
Raw
History Blame Contribute Delete
9.11 kB
# Table of Contents Module
# Contains functions to generate TOC AFTER content is placed with real page numbers
import re
import os
import sys
from typing import List, Dict, Any
# Add the parent directory to sys.path to import from models.py and constants.py
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from models import ExportSection
from constants import SECTION_ORDER
def extract_first_heading_from_content(content: str) -> str:
"""Extract the first heading from markdown content"""
if not content:
return ""
lines = content.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
# Check for markdown headings (# ## ### etc.)
if line.startswith('#'):
# Extract heading text without the # symbols
heading_text = re.sub(r'^#+\s*', '', line).strip()
if heading_text:
return heading_text
# Check for bold headings (**text**)
elif line.startswith('**') and line.endswith('**') and len(line) > 4:
# Extract text from bold markers
heading_text = line[2:-2].strip()
if heading_text:
return heading_text
return ""
def generate_table_of_contents_after_content(sections: List[ExportSection], section_page_map: Dict[str, int], business_idea: str) -> List[Dict[str, Any]]:
"""Generate TOC AFTER all content is placed, using REAL page numbers from section_page_map
This ensures accurate page numbers based on actual content placement"""
toc_items = []
# Create a mapping from section titles to section objects for easy lookup
section_map = {section.title: section for section in sections}
# Add business plan sections in the predefined SECTION_ORDER
for i, expected_title in enumerate(SECTION_ORDER):
# Find the corresponding section - try multiple matching strategies
section = None
# Strategy 1: Exact title match
section = section_map.get(expected_title)
# Strategy 2: Case-insensitive match
if not section:
for section_title, section_obj in section_map.items():
if section_title.lower() == expected_title.lower():
section = section_obj
break
# Strategy 3: Partial match (e.g., "Company Profile" matches "Company Profile Section")
if not section:
for section_title, section_obj in section_map.items():
if (expected_title.lower() in section_title.lower() or
section_title.lower() in expected_title.lower()):
section = section_obj
break
# Strategy 4: Key-based match (remove 'prompt_' prefix and match)
if not section:
for section_obj in sections:
if section_obj.key:
key_clean = section_obj.key.replace('prompt_', '') if section_obj.key.startswith('prompt_') else section_obj.key
if key_clean.lower() == expected_title.lower().replace(' ', '').replace('&', ''):
section = section_obj
break
# Only add sections that have actual content AND real page numbers - NO DUMMY DATA
has_content = bool(section and section.content and section.content.strip())
if has_content:
# Get the REAL page number from section_page_map
real_page = section_page_map.get(section.title, 0)
if real_page > 0: # Only add if we have a real page number
# Extract the actual heading from markdown content
actual_heading = extract_first_heading_from_content(section.content)
# Use the actual heading from markdown content, fallback to expected title
toc_title = actual_heading if actual_heading else expected_title
toc_items.append({
"title": toc_title, # Use actual heading from markdown content
"level": 1,
"page": real_page, # REAL page number from actual content placement
"section_type": "section",
"has_content": True,
"section_obj": section # Store reference to actual section
})
return toc_items
def add_table_of_contents_page(pdf, toc_items: List[Dict[str, Any]], business_idea: str):
"""Add a professional table of contents page with REAL page numbers"""
# TOC items are already in the correct order and have real page numbers
sorted_toc_items = toc_items
# Professional color scheme - Refined for bank/investor presentation
primary_color = (0, 0, 0) # Black - for headings
accent_color = (75, 0, 130) # Navy purple - for accent lines and highlights
text_color = (51, 51, 51) # Dark gray - for body text
light_gray = (221, 221, 221) # Lighter gray - for subtle lines (#DDDDDD)
navy_color = (25, 25, 112) # Navy blue - for TOC and emphasis
# Page title - more compact
pdf.set_font("Arial", "B", 20) # Reduced from 24 to 20
pdf.set_text_color(*primary_color)
pdf.set_xy(25, 20) # Reduced from 25 to 20
pdf.cell(0, 15, "Table of Contents", ln=True) # Reduced from 18 to 15
# Calculate text width and make line autofit
text_width = pdf.get_string_width("Table of Contents")
line_width = min(text_width + 12, 160) # Add 12mm padding, max 160mm
# Removed purple accent line under TOC title
# TOC content - optimized for single page layout
y_position = 50 # Reduced from 60 to 50
pages_used = 1
for item in sorted_toc_items:
# Only add new page if absolutely necessary (very close to bottom)
if y_position > 290: # Very close to bottom margin
pdf.add_page()
y_position = 25
pages_used += 1
# Indent based on level - better indentation
indent = 0 if item["level"] == 1 else 20 # Increased indent for subsections
# Font and color based on level - ALL sections same color
if item["level"] == 1:
pdf.set_font("Arial", "B", 12)
pdf.set_text_color(*navy_color) # Navy blue for all main entries
else:
pdf.set_font("Arial", "", 10)
pdf.set_text_color(*text_color)
# Title with hyperlink
title_width = pdf.get_string_width(item["title"])
pdf.set_xy(25 + indent, y_position)
try:
# FPDF2 hyperlinks: test both 0-based and 1-based indexing
# First try 1-based (no subtraction)
target_page = item["page"]
# Try different FPDF2 link methods
if hasattr(pdf, 'add_link'):
link_id = pdf.add_link()
pdf.set_link(link_id, page=target_page)
pdf.link(25 + indent, y_position, title_width, 8, link_id)
else:
pdf.link(25 + indent, y_position, title_width, 8, dest=target_page)
except:
pass # If hyperlink fails, continue without it
pdf.cell(title_width, 8, item["title"], ln=False)
# Dots leading to page number
pdf.set_font("Arial", "", 8)
pdf.set_text_color(128, 128, 128) # Medium gray for dots
# Calculate dots position
title_width = pdf.get_string_width(item["title"])
dots_start = 25 + indent + title_width + 5
dots_end = 160
# Draw dots
for x in range(int(dots_start), int(dots_end), 3):
pdf.set_xy(x, y_position + 3)
pdf.cell(1, 1, ".", ln=False)
# Page number with hyperlink - NOW USING REAL PAGE NUMBERS
page_number_width = pdf.get_string_width(str(item["page"]))
pdf.set_xy(160, y_position)
try:
# FPDF2 hyperlinks: test both 0-based and 1-based indexing
# First try 1-based (no subtraction)
target_page = item["page"]
# Try different FPDF2 link methods
if hasattr(pdf, 'add_link'):
link_id = pdf.add_link()
pdf.set_link(link_id, page=target_page)
pdf.link(160, y_position, page_number_width, 8, link_id)
else:
pdf.link(160, y_position, page_number_width, 8, dest=target_page)
except:
pass # If hyperlink fails, continue without it
pdf.cell(0, 8, str(item["page"]), ln=False)
# Reduced spacing between items for better page utilization
y_position += 8 # Reduced from 10 to 8 for even tighter spacing
# Add some spacing at the end
pdf.ln(20)