File size: 7,841 Bytes
481de95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aba55b6
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
from fpdf import FPDF
import re


class PDF(FPDF):
    def footer(self):
        """Footer with custom text on every page"""
        self.set_y(-15)
        self.set_font('Arial', 'I', 8)
        self.set_text_color(128, 128, 128)
        self.cell(0, 10, 'Generated by Zouq-ul-ilm', 0, 0, 'C')


def pdf1(text):
    pdf = PDF('P', 'mm', 'A4')
    pdf.add_page()
    pdf.set_auto_page_break(auto=True, margin=15)
    
    # Improved patterns
    h1_pattern = re.compile(r'^#\s+(.+)', re.MULTILINE)
    h2_pattern = re.compile(r'^##\s+(.+)', re.MULTILINE)
    h3_pattern = re.compile(r'^###\s+(.+)', re.MULTILINE)
    bullet_pattern = re.compile(r'^\s*[-*]\s+(.+)', re.MULTILINE)
    
    # Split text into lines for better processing
    lines = text.split('\n')
    
    for line in lines:
        line = line.strip()
        
        if not line:
            pdf.ln(4)  # Add spacing for empty lines
            continue
        
        # Check for headers first
        if h1_pattern.match(line):
            pdf.ln(4)
            pdf.set_font('Arial', 'B', 18)
            pdf.set_text_color(0, 0, 0)
            clean_text = re.sub(r'^#\s+', '', line)
            pdf.multi_cell(0, 10, clean_text.encode('latin-1', 'ignore').decode('latin-1'))
            pdf.ln(3)
            
        elif h2_pattern.match(line):
            pdf.ln(3)
            pdf.set_font('Arial', 'B', 15)
            pdf.set_text_color(0, 0, 0)
            clean_text = re.sub(r'^##\s+', '', line)
            pdf.multi_cell(0, 8, clean_text.encode('latin-1', 'ignore').decode('latin-1'))
            pdf.ln(2)
            
        elif h3_pattern.match(line):
            pdf.ln(2)
            pdf.set_font('Arial', 'B', 13)
            pdf.set_text_color(0, 0, 0)
            clean_text = re.sub(r'^###\s+', '', line)
            pdf.multi_cell(0, 7, clean_text.encode('latin-1', 'ignore').decode('latin-1'))
            pdf.ln(2)
            
        elif bullet_pattern.match(line):
            # Handle bullet points
            clean_text = re.sub(r'^\s*[-*]\s+', '', line)
            process_bullet_point(pdf, clean_text)
            
        else:
            # Normal paragraph with inline formatting
            pdf.set_font('Arial', '', 11)
            pdf.set_text_color(0, 0, 0)
            process_inline_formatting(pdf, line)
            pdf.ln(5)
    
    pdf.output(f"notes.pdf")


def process_bullet_point(pdf, text):
    """Process bullet points with proper wrapping"""
    left_margin = pdf.l_margin
    bullet_indent = 5
    text_indent = 12
    
    # Set position for bullet
    pdf.set_x(left_margin + bullet_indent)
    pdf.set_font('Arial', '', 11)
    pdf.cell(5, 5, chr(149), 0, 0)  # Bullet character
    
    # Process the text with wrapping
    process_inline_formatting_wrapped(pdf, text, left_margin + text_indent)
    pdf.ln(5)


def process_inline_formatting_wrapped(pdf, text, left_indent):
    """Process inline formatting with proper text wrapping"""
    # Split by formatting markers while keeping them
    segments = re.split(r'(\*\*.*?\*\*|__.*?__|_.*?_|~~.*?~~)', text)
    
    # Set initial position
    pdf.set_x(left_indent)
    
    # Calculate available width from current position to right margin
    right_margin = pdf.w - pdf.r_margin
    
    for segment in segments:
        if not segment:
            continue
        
        # Determine formatting
        if re.match(r'\*\*.*?\*\*|__.*?__', segment):
            style = 'B'
            clean_text = re.sub(r'\*\*|__', '', segment)
        elif re.match(r'_.*?_|~~.*?~~', segment):
            style = 'U'
            clean_text = re.sub(r'_+|~+', '', segment)
        else:
            style = ''
            clean_text = segment
        
        # Handle encoding
        try:
            safe_text = clean_text.encode('latin-1', 'ignore').decode('latin-1')
        except:
            safe_text = clean_text.encode('ascii', 'ignore').decode('ascii')
        
        # Split text into words for wrapping
        words = safe_text.split(' ')
        
        for i, word in enumerate(words):
            # Add space before word (except first word of segment)
            if i > 0 or segment != segments[0]:
                test_word = ' ' + word
            else:
                test_word = word
            
            pdf.set_font('Arial', style, 11)
            word_width = pdf.get_string_width(test_word)
            
            # Get current X position
            current_x = pdf.get_x()
            
            # Check if word fits on current line (with proper margin check)
            if current_x + word_width > right_margin:
                # Move to next line
                pdf.ln(5)
                pdf.set_x(left_indent)
                # Remove leading space after line break
                test_word = word
                word_width = pdf.get_string_width(test_word)
            
            # Write the word
            pdf.cell(word_width, 5, test_word, 0, 0)


def process_inline_formatting(pdf, text):
    """Process inline formatting for normal paragraphs"""
    left_margin = pdf.l_margin
    process_inline_formatting_wrapped(pdf, text, left_margin)


# Example usage
if __name__ == "__main__":
    sample_text = """# Hashing: Exam Prep Notes

Hashing is a fundamental technique in computer science used for **efficient data storage and retrieval**. It allows us to quickly find an element in a collection (like an array or a list) without having to search through the whole thing.

## 1. Core Concepts:

* **Hash Table:** A data structure that implements an associative array, a structure that can map keys to values (like a dictionary).
* **Key:** The input value we want to store or retrieve. The key is unique and identifies the data.
* **Value:** The actual data associated with a key. The value is what we want to store in the hash table.
* **Hash Function (h(x)):** A function that takes a key as input and returns an index (usually an integer number) where the corresponding value should be stored in the hash table. This index is also called the **hash value** or **hash code**.
* **Hash Table Size (M):** The total number of slots or buckets available in the hash table that are allocated for storage.
* **Collision:** When two different keys produce the same hash value and map to the same slot (or bucket or slot). This is inevitable and needs to be handled.
* **Load Factor (λ):** A measure of how full the hash table is, calculated as λ = n/M where n is the number of elements and M is the hash table size. A high load factor increases the likelihood of collisions.

## 2. How Hashing Works:

1. **Key is provided.**
2. **Hash Function is applied to the key:** The hash function processes the key and generates an index value.
3. **The value is stored (or retrieved) at the calculated index** in the hash table. If there's a collision (the index is already occupied), a collision resolution technique is used.

## 3. Hash Functions - Key Considerations:

* **Deterministic:** The same key should always produce the same hash value. This ensures consistency.
* **Uniformity:** Ideally, the hash function should distribute keys uniformly across the hash table to minimize collisions.
* **Efficiency:** The hash function should be fast to compute, as it's called frequently during insertions, deletions, and lookups.

## 4. Common Hash Functions:

* **Division Method:** h(k) = k mod M. Simple but can lead to clustering if M is not chosen carefully.
* **Multiplication Method:** h(k) = floor(M * (kA mod 1)) where A is a constant (often 0.618034). More complex but generally better distribution.
* **Mid-Square Method:** Square the key, extract the middle digits, and use them as the hash value.

This is a comprehensive overview of hashing concepts for your exam preparation."""

    pdf1(sample_text)