Interactive Geometry Proof Explainer
Proof Title
Statement:
Given:
Diagram
Proof Steps
| Step | Statement | Reason |
|---|
Diagram not available.
'; // Display SVG or fallback currentStep = -1; // Reset steps view renderStepsTable(); updateStepButtons(); proofDisplay.style.display = 'block'; } function renderStepsTable() { if (!currentProof || !proofStepsTableBody) return; proofStepsTableBody.innerHTML = ''; // Clear previous steps const stepsToShow = (currentStep === -2) ? currentProof.steps.length : currentStep + 1; for(let i = 0; i < stepsToShow; i++) { if (i < currentProof.steps.length) { const step = currentProof.steps[i]; const row = proofStepsTableBody.insertRow(); // Use textContent for security unless HTML is explicitly needed and sanitized const cell1 = row.insertCell(); cell1.textContent = i + 1; const cell2 = row.insertCell(); cell2.textContent = step.statement; const cell3 = row.insertCell(); cell3.textContent = step.reason; } } } function updateStepButtons() { if (!currentProof || !prevStepButton || !nextStepButton || !showAllButton) return; const totalSteps = currentProof.steps.length; // Previous button enabled if not on first step (or if showing all) prevStepButton.disabled = (currentStep <= -1); // Next button enabled if not on last step and not showing all nextStepButton.disabled = (currentStep >= totalSteps - 1 || currentStep === -2); // Show all button always enabled (could be disabled if already showing all) showAllButton.disabled = (currentStep === -2); } async function generateProofPDF() { // Check required objects exist if (!currentProof || typeof jsPDF !== 'function' || typeof autoTable !== 'function' || typeof window.html2canvas !== 'function') { alert("Please select a proof first or wait for required libraries (jsPDF, AutoTable, html2canvas) to load."); return; } const originalButtonText = downloadPdfButton.textContent; downloadPdfButton.textContent = 'Generating PDF...'; downloadPdfButton.disabled = true; try { const doc = new jsPDF(); // Use the constructor // Get colors from CSS variables const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--gp-primary-color').trim(); const textColor = getComputedStyle(document.documentElement).getPropertyValue('--gp-text-color').trim(); const tableHeaderBg = getComputedStyle(document.documentElement).getPropertyValue('--gp-table-header-bg').trim(); const tableHeaderText = getComputedStyle(document.documentElement).getPropertyValue('--gp-table-header-text').trim(); const bgColor = getComputedStyle(document.documentElement).getPropertyValue('--gp-bg-color').trim() || '#ffffff'; let currentY = 15; const pageMargin = 15; const pageWidth = doc.internal.pageSize.getWidth(); const contentWidth = pageWidth - (pageMargin * 2); // --- Title --- doc.setFontSize(16); doc.setTextColor(primaryColor); doc.text(currentProof.title, pageWidth / 2, currentY, { align: 'center' }); currentY += 10; // --- Statement & Given --- doc.setFontSize(12); doc.setTextColor(textColor); doc.setFont(undefined, 'bold'); doc.text("Statement:", pageMargin, currentY); doc.setFont(undefined, 'normal'); let statementLines = doc.splitTextToSize(currentProof.statement, contentWidth - 20); doc.text(statementLines, pageMargin + 25, currentY); currentY += (statementLines.length * 5) + 5; doc.setFont(undefined, 'bold'); doc.text("Given:", pageMargin, currentY); doc.setFont(undefined, 'normal'); let givenLines = doc.splitTextToSize(currentProof.given, contentWidth - 15); doc.text(givenLines, pageMargin + 18, currentY); currentY += (givenLines.length * 5) + 10; // --- Diagram --- doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(primaryColor); doc.text("Diagram:", pageMargin, currentY); currentY += 6; const diagramElement = document.getElementById('gp-diagram'); if (diagramElement && diagramElement.querySelector('svg')) { try { // Ensure diagram is visible for html2canvas const originalDisplay = diagramElement.style.display; diagramElement.style.display = 'block'; // Temporarily ensure it's block const canvas = await window.html2canvas(diagramElement, { scale: 2, backgroundColor: bgColor, logging: false // Reduce console noise }); diagramElement.style.display = originalDisplay; // Restore original display style const imgData = canvas.toDataURL('image/png', 1.0); const imgProps = doc.getImageProperties(imgData); const imgWidth = contentWidth * 0.6; const imgHeight = (imgProps.height * imgWidth) / imgProps.width; const imgX = pageMargin + (contentWidth - imgWidth) / 2; if (currentY + imgHeight > doc.internal.pageSize.getHeight() - pageMargin) { doc.addPage(); currentY = pageMargin; } doc.addImage(imgData, 'PNG', imgX, currentY, imgWidth, imgHeight); currentY += imgHeight + 10; } catch(err) { console.error("Error rendering diagram with html2canvas:", err); doc.setFontSize(10); doc.setTextColor("#FF0000"); doc.text("Error rendering diagram for PDF.", pageMargin, currentY); currentY += 6; } } else { doc.setFontSize(10); doc.setTextColor(textColor); doc.text("Diagram not available.", pageMargin, currentY); currentY += 6; } currentY += 5; // --- Proof Steps Table --- doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(primaryColor); doc.text("Proof Steps:", pageMargin, currentY); const tableHeaders = [['Step', 'Statement', 'Reason']]; const tableBody = currentProof.steps.map((step, index) => [ index + 1, step.statement, step.reason ]); autoTable(doc, { head: tableHeaders, body: tableBody, startY: currentY + 2, theme: 'grid', headStyles: { fillColor: tableHeaderBg, textColor: tableHeaderText, fontStyle: 'bold' }, styles: { fontSize: 10, cellPadding: 3, textColor: textColor, lineColor: [200, 200, 200], lineWidth: 0.1 }, columnStyles: { 0: { halign: 'center', cellWidth: 15 }, 1: { cellWidth: 'auto' }, 2: { cellWidth: 'auto' } }, margin: { left: pageMargin, right: pageMargin } }); // --- Save PDF --- let filename = currentProof.id.replace(/[^a-z0-9]/gi, '_') + "_proof.pdf"; doc.save(filename); } catch (error) { console.error("Error generating PDF:", error); alert("An error occurred while generating the PDF."); } finally { // Restore button state if element exists if(downloadPdfButton) { downloadPdfButton.textContent = originalButtonText; downloadPdfButton.disabled = false; } } } // --- Initial Setup Function --- function initializeTool() { // Assign elements AFTER DOM is loaded selectProof = document.getElementById('gp-select-proof'); proofDisplay = document.getElementById('gp-proof-display'); proofTitle = document.getElementById('gp-title'); proofStatement = document.getElementById('gp-statement'); proofGiven = document.getElementById('gp-given'); proofDiagram = document.getElementById('gp-diagram'); proofStepsTableBody = document.querySelector('#gp-steps-table tbody'); prevStepButton = document.getElementById('gp-prev-step'); nextStepButton = document.getElementById('gp-next-step'); showAllButton = document.getElementById('gp-show-all'); downloadPdfButton = document.getElementById('gp-download-pdf'); // Check if elements exist before adding listeners/populating if(selectProof) { populateDropdown(); // Populate now that element exists selectProof.addEventListener('change', (e) => { displayProof(e.target.value); }); } else { console.error("Could not find #gp-select-proof element for initialization."); } if (nextStepButton) { nextStepButton.addEventListener('click', () => { if (!currentProof || currentStep >= currentProof.steps.length - 1) return; if (currentStep === -2) currentStep = -1; currentStep++; renderStepsTable(); updateStepButtons(); }); } else { console.error("Could not find #gp-next-step button."); } if (prevStepButton) { prevStepButton.addEventListener('click', () => { if (!currentProof || currentStep < 0) return; if (currentStep === -2) currentStep = currentProof.steps.length - 1; currentStep--; renderStepsTable(); updateStepButtons(); }); } else { console.error("Could not find #gp-prev-step button."); } if (showAllButton) { showAllButton.addEventListener('click', () => { if (!currentProof) return; currentStep = -2; renderStepsTable(); updateStepButtons(); }); } else { console.error("Could not find #gp-show-all button."); } if (downloadPdfButton) { downloadPdfButton.addEventListener('click', generateProofPDF); } else { console.error("Could not find #gp-download-pdf button."); } } // --- Wait for DOM ready before running setup --- if (document.readyState === 'loading') { // Loading hasn't finished yet document.addEventListener('DOMContentLoaded', initializeTool); } else { // `DOMContentLoaded` has already fired initializeTool(); }