Certificate of Completion
Click Here to go to the generator
Here is the code used if needed in the future. This is also on Joseph's paid Google account
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Medart University - Certificate Generator (Canvas)</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" xintegrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Load Google Fonts for a professional look -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
<style>
/* Base font for the body */
body {
font-family: 'Inter', sans-serif;
}
/* Styling for the canvas element, making it visually distinct for the preview */
canvas {
border: 10px solid #1e3a8a; /* A deep blue color for the outer border */
background-color: #f0f4f8; /* A light, textured background */
width: 1000px; /* Actual drawing width for the canvas internal rendering */
height: 750px; /* Adjusted height to prevent cutting off */
/* The transform is for the on-screen preview only, scaling it down to fit */
transform: scale(0.8);
transform-origin: top center;
margin-top: 2rem;
margin-bottom: 2rem;
box-sizing: content-box; /* Ensures padding and border are included in total width/height */
}
/* Hidden element to ensure 'Playfair Display' font is loaded by the browser before drawing on canvas */
.font-preload {
font-family: 'Playfair Display', serif;
position: absolute;
left: -9999px; /* Move off-screen */
visibility: hidden;
pointer-events: none;
}
</style>
</head>
<body class="bg-gray-100 flex flex-col items-center justify-center min-h-screen p-4">
<!-- Form for generating the certificate details -->
<div class="w-full max-w-lg bg-white p-8 rounded-lg shadow-lg" id="form-container">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6">Certificate Generator (Canvas)</h1>
<p class="text-center text-gray-600 mb-8">Fill in the details below to generate the certificate.</p>
<div class="space-y-6">
<div>
<label for="studentName" class="block text-sm font-medium text-gray-700">Student's Full Name</label>
<input type="text" id="studentName" name="studentName" placeholder="e.g., Jane Doe" class="mt-1 block w-full px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="courseName" class="block text-sm font-medium text-gray-700">Education Level / Course</label>
<input type="text" id="courseName" name="courseName" placeholder="e.g., Bachelor of Science in Computer Science" class="mt-1 block w-full px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="imageUrl" class="block text-sm font-medium text-gray-700">Certificate Image URL (Optional)</label>
<input type="text" id="imageUrl" name="imageUrl" value="https://static.wixstatic.com/media/7d136f_4cd5ec277c45402a98373beccabbbd7a~mv2.png/v1/fill/w_390,h_78,al_c,q_95,usm_0.66_1.00_0.01,enc_avif,quality_auto/7d136f_4cd5ec277c45402a98373beccabbbd7a~mv2.png" class="mt-1 block w-full px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="completionDate" class="block text-sm font-medium text-gray-700">Date of Completion</label>
<input type="date" id="completionDate" name="completionDate" class="mt-1 block w-full px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
<div class="mt-8">
<button id="printButton" class="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Generate and Save PDF
</button>
</div>
</div>
<!-- Certificate Preview Area - now a canvas element -->
<canvas id="certificateCanvas" width="1000" height="750"></canvas> <!-- Height adjusted -->
<!-- Hidden element to ensure 'Playfair Display' font is loaded by the browser -->
<div class="font-preload">.</div>
<script>
// Get references to DOM elements
const studentNameInput = document.getElementById('studentName');
const courseNameInput = document.getElementById('courseName');
const completionDateInput = document.getElementById('completionDate');
const imageUrlInput = document.getElementById('imageUrl');
const printButton = document.getElementById('printButton');
const certificateCanvas = document.getElementById('certificateCanvas');
const ctx = certificateCanvas.getContext('2d'); // Get the 2D rendering context
// Define the fixed dimensions for the canvas, matching the desired certificate size
const CERT_WIDTH = 1000;
const CERT_HEIGHT = 750; // Adjusted height
/**
* Draws the entire certificate onto the canvas.
* This function is asynchronous because it needs to wait for images to load.
* @returns {Promise<void>} A promise that resolves when all drawing is complete.
*/
async function drawCertificate() {
return new Promise(resolve => {
// Clear the entire canvas before redrawing
ctx.clearRect(0, 0, CERT_WIDTH, CERT_HEIGHT);
// Draw the background color for the certificate
ctx.fillStyle = '#f0f4f8'; /* Light, textured background */
ctx.fillRect(0, 0, CERT_WIDTH, CERT_HEIGHT);
// Draw the inner border of the certificate
ctx.strokeStyle = '#a0aec0'; /* Lighter silver/gray border */
ctx.lineWidth = 2;
// Border extends to the new height
ctx.strokeRect(30, 30, CERT_WIDTH - 60, CERT_HEIGHT - 60);
// Retrieve values from the input fields
const studentName = studentNameInput.value.trim() || 'Student Name';
const courseName = courseNameInput.value.trim() || 'Education Level / Course';
const imageUrl = imageUrlInput.value.trim();
const completionDate = completionDateInput.value;
let formattedDate = 'Completion Date';
if (completionDate) {
const date = new Date(completionDate + 'T00:00:00');
const options = { year: 'numeric', month: 'long', day: 'numeric' };
formattedDate = date.toLocaleDateString('en-US', options);
}
// --- Header Section ---
// Draw a simple geometric university icon
ctx.fillStyle = '#1e3a8a'; /* Deep blue */
// Left triangle
ctx.beginPath();
ctx.moveTo(CERT_WIDTH / 2 - 80, 100);
ctx.lineTo(CERT_WIDTH / 2 - 60, 140);
ctx.lineTo(CERT_WIDTH / 2 - 100, 140);
ctx.closePath();
ctx.fill();
// Right triangle
ctx.beginPath();
ctx.moveTo(CERT_WIDTH / 2 + 80, 100);
ctx.lineTo(CERT_WIDTH / 2 + 60, 140);
ctx.lineTo(CERT_WIDTH / 2 + 100, 140);
ctx.closePath();
ctx.fill();
// Center circle
ctx.beginPath();
ctx.arc(CERT_WIDTH / 2, 110, 20, 0, Math.PI * 2);
ctx.fill();
// University Name Text
ctx.fillStyle = '#1e3a8a'; /* Deep blue */
ctx.font = '700 48px "Playfair Display", serif'; /* Bold, larger font for main title */
ctx.textAlign = 'center'; /* Center align text horizontally */
ctx.fillText('MEDART UNIVERSITY', CERT_WIDTH / 2, 180);
// Certificate of Completion subheading
ctx.fillStyle = '#374151'; /* Gray-800 */
ctx.font = '400 24px "Inter", sans-serif';
ctx.fillText('Certificate of Completion', CERT_WIDTH / 2, 220);
// --- Certificate Image Section ---
let imageFinishedLoading = true; // Flag to track image loading state
if (imageUrl) {
imageFinishedLoading = false; // Set to false if an image URL is provided
const img = new Image();
img.crossOrigin = 'anonymous'; // Essential for loading images from other domains to avoid CORS issues and allow canvas to read them
img.onload = () => {
const imgWidth = Math.min(img.width, 250); // Constrain image width
const imgHeight = (img.height / img.width) * imgWidth; // Maintain aspect ratio
const imgX = (CERT_WIDTH - imgWidth) / 2; // Center image horizontally
const imgY = 250; // Vertical position below the title
// Draw the image onto the canvas
ctx.drawImage(img, imgX, imgY, imgWidth, imgHeight);
imageFinishedLoading = true;
resolve(); // Resolve the promise once the image is successfully drawn
};
img.onerror = () => {
console.error('Image failed to load:', imageUrl);
// Draw a placeholder rectangle and text if the image fails to load
ctx.fillStyle = '#cccccc';
ctx.fillRect((CERT_WIDTH - 250) / 2, 250, 250, 100); /* Placeholder box */
ctx.fillStyle = '#6b7280';
ctx.font = '400 16px "Inter", sans-serif';
ctx.fillText('Image Not Found', CERT_WIDTH / 2, 300);
imageFinishedLoading = true;
resolve(); // Resolve the promise even if the image fails
};
img.src = imageUrl; // Set the image source to start loading
}
// --- Main Certificate Content ---
// "This is to certify that" text
ctx.fillStyle = '#374151'; /* Gray-800 */
ctx.font = '400 20px "Inter", sans-serif';
ctx.fillText('This is to certify that', CERT_WIDTH / 2, 400);
// Student's Name
ctx.fillStyle = '#1f2937'; /* Gray-900 */
ctx.font = '700 40px "Playfair Display", serif';
ctx.fillText(studentName, CERT_WIDTH / 2, 450);
// Draw an underline for the student's name
const textMetrics = ctx.measureText(studentName);
const underlineY = 455;
const underlineWidth = textMetrics.width + 40; /* Add some padding around the text */
ctx.strokeStyle = '#6b7280'; /* Gray-500 */
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo((CERT_WIDTH - underlineWidth) / 2, underlineY);
ctx.lineTo((CERT_WIDTH + underlineWidth) / 2, underlineY);
ctx.stroke();
// "has successfully completed the requirements for" text
ctx.fillStyle = '#374151'; /* Gray-800 */
ctx.font = '400 20px "Inter", sans-serif';
ctx.fillText('has successfully completed the requirements for', CERT_WIDTH / 2, 500);
// Course Name
ctx.fillStyle = '#1f2937'; /* Gray-900 */
ctx.font = '700 32px "Playfair Display", serif';
ctx.fillText(courseName, CERT_WIDTH / 2, 540);
// --- Footer Section (Date and Signature) ---
// Adjust footer position relative to new canvas height
const footerTextY = CERT_HEIGHT - 120; // Position higher from the new bottom edge
const underlineOffset = 10; // Offset from text baseline for the underline
const labelOffset = 30; // Offset from text baseline for the labels ("Date", "University President")
ctx.fillStyle = '#374151'; /* Gray-800 */
ctx.font = '400 16px "Inter", sans-serif';
ctx.textAlign = 'center';
// Date information
ctx.fillText(formattedDate, CERT_WIDTH / 2 - 200, footerTextY);
ctx.strokeStyle = '#6b7280';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(CERT_WIDTH / 2 - 300, footerTextY + underlineOffset); // Underline below text
ctx.lineTo(CERT_WIDTH / 2 - 100, footerTextY + underlineOffset);
ctx.stroke();
ctx.font = '600 14px "Inter", sans-serif';
ctx.fillText('Date', CERT_WIDTH / 2 - 200, footerTextY + labelOffset); // Label below underline
// Signature line and title
ctx.font = 'italic 400 28px "Playfair Display", serif'; /* More cursive/signature-like font */
ctx.fillText('Brian Jones', CERT_WIDTH / 2 + 200, footerTextY);
ctx.strokeStyle = '#6b7280';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(CERT_WIDTH / 2 + 100, footerTextY + underlineOffset); // Underline below text
ctx.lineTo(CERT_WIDTH / 2 + 300, footerTextY + underlineOffset);
ctx.stroke();
ctx.font = '600 14px "Inter", sans-serif';
ctx.fillText('University President', CERT_WIDTH / 2 + 200, footerTextY + labelOffset); // Label below underline
// If no image URL was provided, resolve the promise immediately after drawing static content
if (imageFinishedLoading) {
resolve();
}
});
}
/**
* Sets the default completion date to today's date.
*/
function setDefaultDate() {
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed
const dd = String(today.getDate()).padStart(2, '0');
completionDateInput.value = `${yyyy}-${mm}-${dd}`;
}
// --- Event Listeners and Initial Setup ---
window.onload = () => {
setDefaultDate(); // Set today's date as default
drawCertificate(); // Perform initial drawing of the certificate
// Add event listeners to input fields to redraw the certificate on changes
studentNameInput.addEventListener('input', drawCertificate);
courseNameInput.addEventListener('input', drawCertificate);
completionDateInput.addEventListener('change', drawCertificate);
imageUrlInput.addEventListener('input', drawCertificate); // Image loading is handled within drawCertificate
};
// Event listener for the "Generate and Save PDF" button
printButton.addEventListener('click', async () => {
const studentName = studentNameInput.value.trim() || 'student';
const fileName = `Certificate-${studentName.replace(/\s+/g, '_')}.pdf`;
// Disable the button and update its text to provide feedback
printButton.disabled = true;
printButton.textContent = 'Generating PDF...';
// Ensure the canvas is fully drawn (especially if an image is still loading) before generating PDF
await drawCertificate();
// Options for html2pdf.js
const opt = {
margin: 0, /* No margins */
filename: fileName,
image: { type: 'jpeg', quality: 1.0 }, /* Output image as JPEG with highest quality */
html2canvas: { scale: 4, useCORS: true, letterRendering: true, width: CERT_WIDTH, height: CERT_HEIGHT }, /* Render canvas at 4x scale for sharpness */
jsPDF: { unit: 'px', format: [CERT_WIDTH, CERT_HEIGHT], orientation: 'landscape' } /* PDF unit and format matching canvas dimensions */
};
// Generate and save the PDF from the canvas
html2pdf().from(certificateCanvas).set(opt).save().finally(() => {
// Re-enable the button and reset its text after PDF generation is complete
printButton.disabled = false;
printButton.textContent = 'Generate and Save PDF';
});
});
</script>
</body>
</html>