File size: 9,107 Bytes
2d2c483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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)