TheAiCollectiveART commited on
Commit
c770ec1
·
verified ·
1 Parent(s): 2accd6b

Upload missing root file: generate_wasm_performance_pdf.py

Browse files
Files changed (1) hide show
  1. generate_wasm_performance_pdf.py +454 -0
generate_wasm_performance_pdf.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import re
4
+ import shutil
5
+ from reportlab.lib.pagesizes import letter
6
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak, KeepTogether
7
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
8
+ from reportlab.lib import colors
9
+ from reportlab.pdfgen import canvas
10
+
11
+ class NumberedCanvas(canvas.Canvas):
12
+ def __init__(self, *args, **kwargs):
13
+ super().__init__(*args, **kwargs)
14
+ self._saved_page_states = []
15
+
16
+ def showPage(self):
17
+ self._saved_page_states.append(dict(self.__dict__))
18
+ self._startPage()
19
+
20
+ def save(self):
21
+ num_pages = len(self._saved_page_states)
22
+ for state in self._saved_page_states:
23
+ self.__dict__.update(state)
24
+ self.draw_page_decorations(num_pages)
25
+ super().showPage()
26
+ super().save()
27
+
28
+ def draw_page_decorations(self, page_count):
29
+ self.saveState()
30
+
31
+ # We start headers/footers on page 2 (leaving cover page clean)
32
+ if self._pageNumber > 1:
33
+ # Running Header
34
+ self.setFont("Helvetica-Bold", 8)
35
+ self.setFillColor(colors.HexColor("#0F2D59"))
36
+ self.drawString(54, 755, "LANGUAGE-U SYSTEM TELEMETRY & OPTIMIZATION")
37
+
38
+ self.setFont("Helvetica", 8)
39
+ self.setFillColor(colors.HexColor("#718096"))
40
+ self.drawRightString(558, 755, "WASM MICROSECOND PERFORMANCE WHITE PAPER")
41
+
42
+ # Header line
43
+ self.setStrokeColor(colors.HexColor("#CBD5E0"))
44
+ self.setLineWidth(0.75)
45
+ self.line(54, 747, 558, 747)
46
+
47
+ # Running Footer
48
+ self.setStrokeColor(colors.HexColor("#CBD5E0"))
49
+ self.setLineWidth(0.75)
50
+ self.line(54, 55, 558, 55)
51
+
52
+ self.setFont("Helvetica", 8)
53
+ self.setFillColor(colors.HexColor("#718096"))
54
+ self.drawString(54, 42, "zymatica.space | astronautshe.com | Devs One | We Are TheAiCollective.art")
55
+
56
+ page_text = f"Page {self._pageNumber} of {page_count}"
57
+ self.drawRightString(558, 42, page_text)
58
+
59
+ self.restoreState()
60
+
61
+ def clean_md_text(text):
62
+ # Escape HTML special chars except tags we generate
63
+ text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
64
+
65
+ # Restore specific tags
66
+ text = text.replace(r"$$\text{Latency}_{\text{WebGPU}} = t_{\text{buffer\_copy}} + t_{\text{command\_compile}} + t_{\text{queue\_dispatch}} + t_{\text{mapAsync}} \approx 0.115 \text{ ms}$$",
67
+ '<i>Latency<sub>WebGPU</sub> = t<sub>buffer_copy</sub> + t<sub>command_compile</sub> + t<sub>queue_dispatch</sub> + t<sub>mapAsync</sub> &asymp; 0.115 ms</i>')
68
+ text = text.replace(r"$$\text{Latency}_{\text{WebGPU}} = t_{\text{buffer\_copy}} + t_{\text{command\_compile}} + t_{\text{queue\_dispatch}} + t_{\text{mapAsync}} \approx 0.115 \text{ ms}$$", "")
69
+ text = text.replace(r"$$", "")
70
+ text = text.replace(r"$\text{Latency}_{\text{WebGPU}}$", "<i>Latency<sub>WebGPU</sub></i>")
71
+ text = text.replace(r"$t_{\text{buffer\_copy}}$", "<i>t<sub>buffer_copy</sub></i>")
72
+ text = text.replace(r"$t_{\text{command\_compile}}$", "<i>t<sub>command_compile</sub></i>")
73
+ text = text.replace(r"$t_{\text{queue\_dispatch}}$", "<i>t<sub>queue_dispatch</sub></i>")
74
+ text = text.replace(r"$t_{\text{mapAsync}}$", "<i>t<sub>mapAsync</sub></i>")
75
+ text = text.replace(r"$t_{\text{mapAsync}} \approx 0.115 \text{ ms}$", "<i>t<sub>mapAsync</sub> &asymp; 0.115 ms</i>")
76
+ text = text.replace(r"$\approx 0.115 \text{ ms}$", "<i>&asymp; 0.115 ms</i>")
77
+ text = text.replace(r"$\approx 0.12 \text{ ms}$", "<i>&asymp; 0.12 ms</i>")
78
+ text = text.replace(r"$\text{META:num\_concepts:payload\_hash:compressed\_len}$", "<b>META:num_concepts:payload_hash:compressed_len</b>")
79
+ text = text.replace(r"$\text{trans\_rc}$", "<i>trans_rc</i>")
80
+ text = text.replace(r"$\text{trans\_rf}$", "<i>trans_rf</i>")
81
+ text = text.replace(r"$\text{trans\_ra}$", "<i>trans_ra</i>")
82
+
83
+ # Convert bold markdown **text** to <b>text</b>
84
+ text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
85
+ text = re.sub(r'__(.*?)__', r'<b>\1</b>', text)
86
+
87
+ # Convert italic markdown *text* to <i>text</i>
88
+ text = re.sub(r'\*(.*?)\*', r'<i>\1</i>', text)
89
+
90
+ # Convert `code` to <font name="Courier"><b>code</b></font>
91
+ text = re.sub(r'`(.*?)`', r'<b><font name="Courier" color="#0F2D59">\1</font></b>', text)
92
+
93
+ # Convert links [text](url) to <a href="url">text</a>
94
+ text = re.sub(r'\[(.*?)\]\((.*?)\)', r'<a href="\2"><font color="#1D4ED8"><b>\1</b></font></a>', text)
95
+
96
+ return text
97
+
98
+ def parse_markdown(md_path):
99
+ with open(md_path, 'r', encoding='utf-8') as f:
100
+ content = f.read()
101
+
102
+ lines = content.split('\n')
103
+ elements = []
104
+
105
+ in_code_block = False
106
+ code_lines = []
107
+
108
+ in_table = False
109
+ table_rows = []
110
+
111
+ i = 0
112
+ while i < len(lines):
113
+ line = lines[i]
114
+
115
+ # Code block check
116
+ if line.strip().startswith('```'):
117
+ if in_code_block:
118
+ in_code_block = False
119
+ elements.append({
120
+ 'type': 'code',
121
+ 'content': '\n'.join(code_lines)
122
+ })
123
+ code_lines = []
124
+ else:
125
+ in_code_block = True
126
+ i += 1
127
+ continue
128
+
129
+ if in_code_block:
130
+ code_lines.append(line)
131
+ i += 1
132
+ continue
133
+
134
+ # Table check
135
+ if line.strip().startswith('|') and not in_table:
136
+ # Check if next line is table separator
137
+ if i + 1 < len(lines) and lines[i+1].strip().startswith('|') and '-' in lines[i+1]:
138
+ in_table = True
139
+ table_rows = []
140
+ headers = [cell.strip() for cell in line.split('|')[1:-1]]
141
+ table_rows.append(headers)
142
+ i += 2
143
+ continue
144
+
145
+ if in_table:
146
+ if line.strip().startswith('|'):
147
+ cells = [cell.strip() for cell in line.split('|')[1:-1]]
148
+ table_rows.append(cells)
149
+ i += 1
150
+ continue
151
+ else:
152
+ in_table = False
153
+ elements.append({
154
+ 'type': 'table',
155
+ 'content': table_rows
156
+ })
157
+ table_rows = []
158
+
159
+ # Skip empty lines but keep single Spacers
160
+ if line.strip() == '':
161
+ elements.append({'type': 'spacer', 'content': 8})
162
+ i += 1
163
+ continue
164
+
165
+ # Separator line
166
+ if line.strip() == '---':
167
+ elements.append({'type': 'pagebreak', 'content': None})
168
+ i += 1
169
+ continue
170
+
171
+ # Headers
172
+ if line.startswith('# '):
173
+ elements.append({'type': 'h1', 'content': line[2:].strip()})
174
+ elif line.startswith('## '):
175
+ elements.append({'type': 'h2', 'content': line[3:].strip()})
176
+ elif line.startswith('### '):
177
+ elements.append({'type': 'h3', 'content': line[4:].strip()})
178
+ # Bullet list items
179
+ elif line.strip().startswith('* ') or line.strip().startswith('- '):
180
+ elements.append({'type': 'bullet', 'content': line.strip()[2:].strip()})
181
+ elif re.match(r'^\d+\.\s', line.strip()):
182
+ content_start = line.find('.') + 1
183
+ elements.append({'type': 'bullet', 'content': line[content_start:].strip()})
184
+ # Image
185
+ elif line.strip().startswith('![') and '](' in line:
186
+ caption_match = re.search(r'!\[(.*?)\]', line)
187
+ path_match = re.search(r'\]\(([^)]+)\)', line)
188
+ elements.append({
189
+ 'type': 'image',
190
+ 'caption': caption_match.group(1) if caption_match else '',
191
+ 'path': path_match.group(1) if path_match else ''
192
+ })
193
+ # Plain text
194
+ else:
195
+ elements.append({'type': 'p', 'content': line.strip()})
196
+
197
+ i += 1
198
+
199
+ return elements
200
+
201
+ def build_pdf(md_path, pdf_path):
202
+ elements = parse_markdown(md_path)
203
+
204
+ doc = SimpleDocTemplate(
205
+ pdf_path,
206
+ pagesize=letter,
207
+ leftMargin=54,
208
+ rightMargin=54,
209
+ topMargin=72,
210
+ bottomMargin=72
211
+ )
212
+
213
+ styles = getSampleStyleSheet()
214
+
215
+ # Zymatica Theme Palette
216
+ primary_color = colors.HexColor("#0F2D59") # Deep Navy
217
+ secondary_color = colors.HexColor("#1D4ED8") # Slate Blue
218
+ dark_neutral = colors.HexColor("#111111") # Near Black
219
+
220
+ # Custom Styles
221
+ title_style = ParagraphStyle(
222
+ 'DocTitle',
223
+ parent=styles['Heading1'],
224
+ fontName='Helvetica-Bold',
225
+ fontSize=20,
226
+ leading=24,
227
+ textColor=primary_color,
228
+ spaceAfter=15
229
+ )
230
+
231
+ h1_style = ParagraphStyle(
232
+ 'SecHeading1',
233
+ parent=styles['Heading1'],
234
+ fontName='Helvetica-Bold',
235
+ fontSize=13,
236
+ leading=16,
237
+ textColor=primary_color,
238
+ spaceBefore=14,
239
+ spaceAfter=6,
240
+ keepWithNext=True
241
+ )
242
+
243
+ h2_style = ParagraphStyle(
244
+ 'SecHeading2',
245
+ parent=styles['Heading2'],
246
+ fontName='Helvetica-Bold',
247
+ fontSize=11,
248
+ leading=14,
249
+ textColor=secondary_color,
250
+ spaceBefore=10,
251
+ spaceAfter=4,
252
+ keepWithNext=True
253
+ )
254
+
255
+ body_style = ParagraphStyle(
256
+ 'BodyText',
257
+ parent=styles['Normal'],
258
+ fontName='Helvetica',
259
+ fontSize=9.5,
260
+ leading=13.5,
261
+ textColor=dark_neutral,
262
+ spaceAfter=5
263
+ )
264
+
265
+ bullet_style = ParagraphStyle(
266
+ 'BulletText',
267
+ parent=styles['Normal'],
268
+ fontName='Helvetica',
269
+ fontSize=9,
270
+ leading=13,
271
+ textColor=dark_neutral,
272
+ leftIndent=15,
273
+ firstLineIndent=-10,
274
+ spaceAfter=4
275
+ )
276
+
277
+ code_style = ParagraphStyle(
278
+ 'CodeSnippet',
279
+ parent=styles['Normal'],
280
+ fontName='Courier',
281
+ fontSize=8,
282
+ leading=11,
283
+ textColor=colors.HexColor("#0F2D59"),
284
+ spaceAfter=4
285
+ )
286
+
287
+ table_header_style = ParagraphStyle(
288
+ 'TableHeader',
289
+ parent=styles['Normal'],
290
+ fontName='Helvetica-Bold',
291
+ fontSize=8.5,
292
+ leading=11,
293
+ textColor=colors.white
294
+ )
295
+
296
+ table_cell_style = ParagraphStyle(
297
+ 'TableCell',
298
+ parent=styles['Normal'],
299
+ fontName='Helvetica',
300
+ fontSize=8,
301
+ leading=11,
302
+ textColor=dark_neutral
303
+ )
304
+
305
+ table_cell_bold = ParagraphStyle(
306
+ 'TableCellBold',
307
+ parent=styles['Normal'],
308
+ fontName='Helvetica-Bold',
309
+ fontSize=8,
310
+ leading=11,
311
+ textColor=dark_neutral
312
+ )
313
+
314
+ meta_style = ParagraphStyle(
315
+ 'DocMeta',
316
+ parent=styles['Normal'],
317
+ fontName='Helvetica-Bold',
318
+ fontSize=8.5,
319
+ leading=12,
320
+ textColor=colors.HexColor("#4A5568")
321
+ )
322
+
323
+ story = []
324
+
325
+ # Title Page Header logo block
326
+ logo_path = "Logo.jpg"
327
+ # Resolve correct paths
328
+ if not os.path.exists(logo_path):
329
+ logo_path = r"j:\Language-U\Logo.jpg"
330
+
331
+ if os.path.exists(logo_path):
332
+ logo_img = Image(logo_path, width=44, height=44)
333
+ header_data = [[logo_img, Paragraph("<b>THE AI COLLECTIVE</b><br/><font color='#4A5568'>Zymatica &bull; astronautshe.com &bull; DevsOne</font>", meta_style)]]
334
+ header_table = Table(header_data, colWidths=[55, 449])
335
+ header_table.setStyle(TableStyle([
336
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
337
+ ('BOTTOMPADDING', (0,0), (-1,-1), 8),
338
+ ]))
339
+ story.append(header_table)
340
+
341
+ # Header line
342
+ story.append(Table([[ "" ]], colWidths=[504], rowHeights=[1.5], style=TableStyle([
343
+ ('BACKGROUND', (0,0), (-1,-1), primary_color),
344
+ ('BOTTOMPADDING', (0,0), (-1,-1), 0),
345
+ ('TOPPADDING', (0,0), (-1,-1), 0),
346
+ ])))
347
+ story.append(Spacer(1, 15))
348
+
349
+ i = 0
350
+ while i < len(elements):
351
+ elem = elements[i]
352
+
353
+ if elem['type'] == 'pagebreak':
354
+ story.append(PageBreak())
355
+ elif elem['type'] == 'spacer':
356
+ story.append(Spacer(1, elem['content']))
357
+ elif elem['type'] == 'h1':
358
+ title_text = clean_md_text(elem['content'])
359
+ if "Breaking the Browser Compute" in title_text:
360
+ story.append(Paragraph(title_text, title_style))
361
+ else:
362
+ story.append(Paragraph(title_text, h1_style))
363
+ elif elem['type'] == 'h2':
364
+ story.append(Paragraph(clean_md_text(elem['content']), h1_style))
365
+ elif elem['type'] == 'h3':
366
+ story.append(Paragraph(clean_md_text(elem['content']), h2_style))
367
+ elif elem['type'] == 'bullet':
368
+ story.append(Paragraph(f"&bull; {clean_md_text(elem['content'])}", bullet_style))
369
+ elif elem['type'] == 'p':
370
+ p_text = elem['content']
371
+ if p_text.startswith('Published by:') or p_text.startswith('Authors:') or p_text.startswith('License:'):
372
+ story.append(Paragraph(clean_md_text(p_text), meta_style))
373
+ else:
374
+ story.append(Paragraph(clean_md_text(p_text), body_style))
375
+ elif elem['type'] == 'image':
376
+ image_filename = elem['path']
377
+ # We already have logo at the top, skip cover large image embeds in document structure
378
+ if "Logo.jpg" in image_filename:
379
+ i += 1
380
+ continue
381
+ image_dir = os.path.dirname(md_path)
382
+ full_image_path = os.path.join(image_dir, image_filename)
383
+ if os.path.exists(full_image_path):
384
+ img = Image(full_image_path, width=480, height=280)
385
+ caption = Paragraph(f"<i>Figure: {clean_md_text(elem['caption'])}</i>", body_style)
386
+ img_table = Table([[img]], colWidths=[504])
387
+ img_table.setStyle(TableStyle([
388
+ ('ALIGN', (0,0), (-1,-1), 'CENTER'),
389
+ ('BOTTOMPADDING', (0,0), (-1,-1), 5),
390
+ ]))
391
+ story.append(KeepTogether([img_table, Spacer(1, 4), caption]))
392
+ elif elem['type'] == 'code':
393
+ code_text = elem['content']
394
+ code_text_clean = code_text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
395
+ code_lines = [Paragraph(line.replace(" ", "&nbsp;"), code_style) for line in code_text_clean.split('\n')]
396
+ code_table_data = [[line] for line in code_lines]
397
+ code_table = Table(code_table_data, colWidths=[504])
398
+ code_table.setStyle(TableStyle([
399
+ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor("#EDF2F7")),
400
+ ('BORDER', (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E0")),
401
+ ('PADDING', (0,0), (-1,-1), 5),
402
+ ('TOPPADDING', (0,0), (-1,-1), 1),
403
+ ('BOTTOMPADDING', (0,0), (-1,-1), 1),
404
+ ]))
405
+ story.append(code_table)
406
+ elif elem['type'] == 'table':
407
+ table_rows = elem['content']
408
+ pdf_table_data = []
409
+
410
+ headers = [Paragraph(clean_md_text(cell), table_header_style) for cell in table_rows[0]]
411
+ pdf_table_data.append(headers)
412
+
413
+ for r in range(1, len(table_rows)):
414
+ row_cells = []
415
+ for c in range(len(table_rows[r])):
416
+ cell_text = table_rows[r][c]
417
+ if cell_text.startswith('**') and cell_text.endswith('**'):
418
+ row_cells.append(Paragraph(clean_md_text(cell_text), table_cell_bold))
419
+ else:
420
+ row_cells.append(Paragraph(clean_md_text(cell_text), table_cell_style))
421
+ pdf_table_data.append(row_cells)
422
+
423
+ num_cols = len(table_rows[0])
424
+ col_widths = [504 / num_cols] * num_cols
425
+ if num_cols == 4: # Telemetry Tables
426
+ col_widths = [140, 94, 90, 180]
427
+
428
+ pdf_table = Table(pdf_table_data, colWidths=col_widths, repeatRows=1)
429
+ pdf_table.setStyle(TableStyle([
430
+ ('BACKGROUND', (0,0), (-1,0), primary_color),
431
+ ('ALIGN', (0,0), (-1,-1), 'LEFT'),
432
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
433
+ ('BOTTOMPADDING', (0,0), (-1,-1), 4),
434
+ ('TOPPADDING', (0,0), (-1,-1), 4),
435
+ ('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.white, colors.HexColor("#F7FAFC")]),
436
+ ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#E2E8F0")),
437
+ ]))
438
+ story.append(pdf_table)
439
+
440
+ i += 1
441
+
442
+ # End document page block
443
+ doc.build(story, canvasmaker=NumberedCanvas)
444
+ print(f"[+] PDF compiled successfully at: {pdf_path}")
445
+
446
+ if __name__ == "__main__":
447
+ md_file = r"j:\Language-U\zymatica.space_repo\WASM_PERFORMANCE_RECORD.md"
448
+ pdf_file = r"j:\Language-U\zymatica.space_repo\WASM_PERFORMANCE_RECORD.pdf"
449
+ build_pdf(md_file, pdf_file)
450
+
451
+ # Copy PDF to root workspace as well for easy access
452
+ root_pdf = r"j:\Language-U\WASM_PERFORMANCE_RECORD.pdf"
453
+ shutil.copy(pdf_file, root_pdf)
454
+ print(f"[+] PDF copied to root workspace at: {root_pdf}")