Skip to content

Web Worker for PDF gen#274

Merged
Aditya062003 merged 2 commits into
AOSSIE-Org:mainfrom
yatikakain:main
Jul 6, 2025
Merged

Web Worker for PDF gen#274
Aditya062003 merged 2 commits into
AOSSIE-Org:mainfrom
yatikakain:main

Conversation

@yatikakain

@yatikakain yatikakain commented Jun 23, 2025

Copy link
Copy Markdown
Contributor

Description

This PR introduces the use of a Web Worker to offload the PDF generation process, enhancing performance and preventing UI blocking during heavy computation.


✅ Changes Made

  • Implemented worker.onmessage to:
    • Receive PDF data from the worker thread.
    • Construct a Blob from the received PDF bytes.
    • Generate a temporary download link to trigger file download (generated_questions.pdf).
    • Append and remove the link from the DOM for clean download initiation.
  • Added logic to hide the dropdown menu (#pdfDropdown) post-download.
  • Added worker.onerror to gracefully handle and log any errors from the worker.

💡 Motivation

Generating PDFs can be computationally expensive. Moving this logic to a Web Worker:

  • Prevents blocking the main UI thread.
  • Improves responsiveness of the web application.
  • Provides a more seamless user experience.

Screenshot_thread

Screenshot_web_worker

Summary by CodeRabbit

  • New Features

    • Introduced web worker-based PDF generation for quizzes, enhancing app responsiveness during PDF export.
    • PDF exports now include interactive form fields for Boolean and multiple-choice questions, plus text fields for short answers.
    • Option to embed a logo and header in exported PDFs.
  • Bug Fixes

    • Eliminated UI freezing during PDF creation by moving processing to a background worker.
  • Chores

    • Added new PDF worker scripts for both web and extension platforms to handle PDF generation asynchronously.

@coderabbitai

coderabbitai Bot commented Jun 23, 2025

Copy link
Copy Markdown

"""

Walkthrough

PDF generation for quizzes was refactored in both the web and extension projects to use a Web Worker for off-main-thread processing. The main thread now delegates PDF creation to a new pdfWorker.js script, which handles all logic for rendering questions, answers, and interactive form fields using pdf-lib, then returns the PDF data for download.

Changes

File(s) Change Summary
eduaid_web/src/pages/Output.jsx,
extension/src/pages/question/Question.jsx
Refactored generatePDF to delegate PDF creation to a Web Worker; removed inline PDF logic and added worker communication and error handling.
eduaid_web/src/workers/pdfWorker.js,
extension/src/workers/pdfWorker.js
Added new Web Worker scripts implementing PDF generation with pdf-lib, handling quiz rendering, pagination, and posting PDF bytes back.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Main Thread (UI)
    participant Worker as PDF Worker

    UI->>Worker: postMessage({ qaPairs, mode, logoBytes })
    Worker-->>UI: message (PDF bytes)
    UI->>UI: Create Blob and trigger PDF download
Loading

Poem

🐇
Off to the worker, the PDFs go,
No more blocking the main thread’s flow!
Questions and answers, fields to fill,
All crafted in silence, with rabbit skill.
Download appears—so quick, so neat—
A bunny’s job, now complete!

"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 899c976 and 4d8ea15.

📒 Files selected for processing (2)
  • eduaid_web/src/pages/Output.jsx (1 hunks)
  • extension/src/pages/question/Question.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extension/src/pages/question/Question.jsx
  • eduaid_web/src/pages/Output.jsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
extension/src/workers/pdfWorker.js (1)

1-163: Eliminate code duplication between web and extension workers

This file is identical to eduaid_web/src/workers/pdfWorker.js. Consider extracting the PDF generation logic to a shared module that both workers can import.

Create a shared PDF generation module:

  1. Extract the common logic to a shared location (e.g., shared/pdfGenerator.js)
  2. Import and use it in both worker files
  3. This will ensure consistency and make maintenance easier
🧹 Nitpick comments (2)
eduaid_web/src/workers/pdfWorker.js (1)

133-133: Fix inconsistent vertical spacing calculation

The spacing calculation mixes two different approaches, which could lead to layout issues.

-y -= Math.max(25, optionLines.length * 20);
+y -= optionLines.length * 20 + 5; // Consistent line height plus margin
eduaid_web/src/pages/Output.jsx (1)

2-2: Remove unused imports

The PDFDocument and rgb imports from pdf-lib are no longer used after delegating PDF generation to the worker.

-import { PDFDocument, rgb } from "pdf-lib";
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4ad36 and 899c976.

📒 Files selected for processing (4)
  • eduaid_web/src/pages/Output.jsx (1 hunks)
  • eduaid_web/src/workers/pdfWorker.js (1 hunks)
  • extension/src/pages/question/Question.jsx (1 hunks)
  • extension/src/workers/pdfWorker.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)

Comment on lines +11 to +29
const wrapText = (text, maxWidth) => {
const words = text.split(' ');
const lines = [];
let currentLine = '';

words.forEach(word => {
const testLine = currentLine ? `${currentLine} ${word}` : word;
const testWidth = testLine.length * 6;
if (testWidth > maxWidth) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
});

if (currentLine) lines.push(currentLine);
return lines;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve text width calculation accuracy

The current implementation uses a fixed multiplier (6) to estimate text width, which won't work accurately for variable-width fonts or special characters. Consider using pdf-lib's font metrics for accurate measurements.

-const wrapText = (text, maxWidth) => {
+const wrapText = (text, maxWidth, font, fontSize) => {
   const words = text.split(' ');
   const lines = [];
   let currentLine = '';
 
   words.forEach(word => {
     const testLine = currentLine ? `${currentLine} ${word}` : word;
-    const testWidth = testLine.length * 6;
+    const testWidth = font.widthOfTextAtSize(testLine, fontSize);
     if (testWidth > maxWidth) {
       lines.push(currentLine);
       currentLine = word;
     } else {
       currentLine = testLine;
     }
   });
 
   if (currentLine) lines.push(currentLine);
   return lines;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const wrapText = (text, maxWidth) => {
const words = text.split(' ');
const lines = [];
let currentLine = '';
words.forEach(word => {
const testLine = currentLine ? `${currentLine} ${word}` : word;
const testWidth = testLine.length * 6;
if (testWidth > maxWidth) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
});
if (currentLine) lines.push(currentLine);
return lines;
};
const wrapText = (text, maxWidth, font, fontSize) => {
const words = text.split(' ');
const lines = [];
let currentLine = '';
words.forEach(word => {
const testLine = currentLine ? `${currentLine} ${word}` : word;
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
if (testWidth > maxWidth) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
});
if (currentLine) lines.push(currentLine);
return lines;
};
🤖 Prompt for AI Agents
In eduaid_web/src/workers/pdfWorker.js between lines 11 and 29, the wrapText
function estimates text width by multiplying character count by a fixed value
(6), which is inaccurate for variable-width fonts. To fix this, use pdf-lib's
font width measurement methods to calculate the actual width of the testLine
string with the current font and size, replacing the fixed multiplier
calculation. This will ensure text wrapping respects the true rendered width.

Comment on lines +31 to +162
self.onmessage = async (e) => {
const { qaPairs, mode, logoBytes } = e.data;

const pageWidth = 595.28;
const pageHeight = 841.89;
const margin = 50;
const maxContentWidth = pageWidth - 2 * margin;

const pdfDoc = await PDFDocument.create();
let page = pdfDoc.addPage([pageWidth, pageHeight]);
const d = new Date(Date.now());

const form = pdfDoc.getForm();
let y = pageHeight - margin - 70;
let questionIndex = 1;

const createNewPageIfNeeded = (requiredHeight) => {
if (y - requiredHeight < margin) {
page = pdfDoc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin;
}
};

try {
if (logoBytes) {
const logoImage = await pdfDoc.embedPng(logoBytes);
const logoDims = logoImage.scale(0.2);
page.drawImage(logoImage, {
x: margin,
y: pageHeight - margin - 30,
width: logoDims.width,
height: logoDims.height,
});
page.drawText("EduAid generated Quiz", {
x: margin + logoDims.width + 10,
y: pageHeight - margin,
size: 20
});
page.drawText("Created On: " + d.toString(), {
x: margin + logoDims.width + 10,
y: pageHeight - margin - 30,
size: 10
});
}
} catch (err) {
page.drawText("EduAid generated Quiz", { x: margin, y: pageHeight - margin, size: 20 });
}

for (const qaPair of qaPairs) {
let requiredHeight = 60;
const questionLines = wrapText(qaPair.question, maxContentWidth);
requiredHeight += questionLines.length * 20;

if (mode !== 'answers') {
if (qaPair.question_type === "Boolean") {
requiredHeight += 60;
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const optionsCount = qaPair.options ? qaPair.options.length + 1 : 1;
requiredHeight += optionsCount * 25;
} else {
requiredHeight += 40;
}
}

if (mode === 'answers' || mode === 'questions_answers') {
requiredHeight += 40;
}

createNewPageIfNeeded(requiredHeight);

if (mode !== 'answers') {
questionLines.forEach((line, i) => {
const prefix = i === 0 ? `Q${questionIndex}) ` : ' ';
page.drawText(`${prefix}${line}`, { x: margin, y: y - i * 20, size: 12 });
});
y -= questionLines.length * 20 + 20;

if (mode === 'questions') {
if (qaPair.question_type === "Boolean") {
const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
['True', 'False'].forEach(option => {
radioGroup.addOptionToPage(option, page, {
x: margin + 20, y, width: 15, height: 15
});
page.drawText(option, { x: margin + 40, y: y + 2, size: 12 });
y -= 20;
});
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const allOptions = [...(qaPair.options || [])];
if (qaPair.answer && !allOptions.includes(qaPair.answer)) allOptions.push(qaPair.answer);
const shuffled = shuffleArray([...allOptions]);

const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
shuffled.forEach((option, idx) => {
radioGroup.addOptionToPage(`option${idx}`, page, {
x: margin + 20, y, width: 15, height: 15
});

const optionLines = wrapText(option, maxContentWidth - 60);
optionLines.forEach((line, i) => {
page.drawText(line, { x: margin + 40, y: y + 2 - i * 15, size: 12 });
});
y -= Math.max(25, optionLines.length * 20);
});
} else if (qaPair.question_type === "Short") {
const field = form.createTextField(`question${questionIndex}_answer`);
field.setText('');
field.addToPage(page, {
x: margin, y: y - 20, width: maxContentWidth, height: 20
});
y -= 40;
}
}
}

if (mode === 'answers' || mode === 'questions_answers') {
const answerLines = wrapText(`Answer ${questionIndex}: ${qaPair.answer}`, maxContentWidth);
answerLines.forEach((line, i) => {
page.drawText(line, {
x: margin, y: y - i * 15, size: 12, color: rgb(0, 0.5, 0)
});
});
y -= answerLines.length * 20;
}

y -= 20;
questionIndex++;
}

const pdfBytes = await pdfDoc.save();
self.postMessage(pdfBytes);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Add input validation and error handling

The worker should validate inputs and handle errors gracefully. Also, ensure proper cleanup by terminating the worker after completion.

Add input validation at the beginning:

 self.onmessage = async (e) => {
+  try {
   const { qaPairs, mode, logoBytes } = e.data;
+  
+  // Validate inputs
+  if (!qaPairs || !Array.isArray(qaPairs)) {
+    throw new Error('Invalid qaPairs: expected an array');
+  }
+  if (!mode || !['questions', 'answers', 'questions_answers'].includes(mode)) {
+    throw new Error('Invalid mode: expected questions, answers, or questions_answers');
+  }

Add error handling and worker termination:

   const pdfBytes = await pdfDoc.save();
   self.postMessage(pdfBytes);
+  self.close(); // Terminate the worker
+  } catch (error) {
+    self.postMessage({ error: error.message });
+    self.close();
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.onmessage = async (e) => {
const { qaPairs, mode, logoBytes } = e.data;
const pageWidth = 595.28;
const pageHeight = 841.89;
const margin = 50;
const maxContentWidth = pageWidth - 2 * margin;
const pdfDoc = await PDFDocument.create();
let page = pdfDoc.addPage([pageWidth, pageHeight]);
const d = new Date(Date.now());
const form = pdfDoc.getForm();
let y = pageHeight - margin - 70;
let questionIndex = 1;
const createNewPageIfNeeded = (requiredHeight) => {
if (y - requiredHeight < margin) {
page = pdfDoc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin;
}
};
try {
if (logoBytes) {
const logoImage = await pdfDoc.embedPng(logoBytes);
const logoDims = logoImage.scale(0.2);
page.drawImage(logoImage, {
x: margin,
y: pageHeight - margin - 30,
width: logoDims.width,
height: logoDims.height,
});
page.drawText("EduAid generated Quiz", {
x: margin + logoDims.width + 10,
y: pageHeight - margin,
size: 20
});
page.drawText("Created On: " + d.toString(), {
x: margin + logoDims.width + 10,
y: pageHeight - margin - 30,
size: 10
});
}
} catch (err) {
page.drawText("EduAid generated Quiz", { x: margin, y: pageHeight - margin, size: 20 });
}
for (const qaPair of qaPairs) {
let requiredHeight = 60;
const questionLines = wrapText(qaPair.question, maxContentWidth);
requiredHeight += questionLines.length * 20;
if (mode !== 'answers') {
if (qaPair.question_type === "Boolean") {
requiredHeight += 60;
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const optionsCount = qaPair.options ? qaPair.options.length + 1 : 1;
requiredHeight += optionsCount * 25;
} else {
requiredHeight += 40;
}
}
if (mode === 'answers' || mode === 'questions_answers') {
requiredHeight += 40;
}
createNewPageIfNeeded(requiredHeight);
if (mode !== 'answers') {
questionLines.forEach((line, i) => {
const prefix = i === 0 ? `Q${questionIndex}) ` : ' ';
page.drawText(`${prefix}${line}`, { x: margin, y: y - i * 20, size: 12 });
});
y -= questionLines.length * 20 + 20;
if (mode === 'questions') {
if (qaPair.question_type === "Boolean") {
const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
['True', 'False'].forEach(option => {
radioGroup.addOptionToPage(option, page, {
x: margin + 20, y, width: 15, height: 15
});
page.drawText(option, { x: margin + 40, y: y + 2, size: 12 });
y -= 20;
});
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const allOptions = [...(qaPair.options || [])];
if (qaPair.answer && !allOptions.includes(qaPair.answer)) allOptions.push(qaPair.answer);
const shuffled = shuffleArray([...allOptions]);
const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
shuffled.forEach((option, idx) => {
radioGroup.addOptionToPage(`option${idx}`, page, {
x: margin + 20, y, width: 15, height: 15
});
const optionLines = wrapText(option, maxContentWidth - 60);
optionLines.forEach((line, i) => {
page.drawText(line, { x: margin + 40, y: y + 2 - i * 15, size: 12 });
});
y -= Math.max(25, optionLines.length * 20);
});
} else if (qaPair.question_type === "Short") {
const field = form.createTextField(`question${questionIndex}_answer`);
field.setText('');
field.addToPage(page, {
x: margin, y: y - 20, width: maxContentWidth, height: 20
});
y -= 40;
}
}
}
if (mode === 'answers' || mode === 'questions_answers') {
const answerLines = wrapText(`Answer ${questionIndex}: ${qaPair.answer}`, maxContentWidth);
answerLines.forEach((line, i) => {
page.drawText(line, {
x: margin, y: y - i * 15, size: 12, color: rgb(0, 0.5, 0)
});
});
y -= answerLines.length * 20;
}
y -= 20;
questionIndex++;
}
const pdfBytes = await pdfDoc.save();
self.postMessage(pdfBytes);
};
self.onmessage = async (e) => {
try {
const { qaPairs, mode, logoBytes } = e.data;
// Validate inputs
if (!qaPairs || !Array.isArray(qaPairs)) {
throw new Error('Invalid qaPairs: expected an array');
}
if (!mode || !['questions', 'answers', 'questions_answers'].includes(mode)) {
throw new Error('Invalid mode: expected questions, answers, or questions_answers');
}
const pageWidth = 595.28;
const pageHeight = 841.89;
const margin = 50;
const maxContentWidth = pageWidth - 2 * margin;
const pdfDoc = await PDFDocument.create();
let page = pdfDoc.addPage([pageWidth, pageHeight]);
const d = new Date(Date.now());
const form = pdfDoc.getForm();
let y = pageHeight - margin - 70;
let questionIndex = 1;
const createNewPageIfNeeded = (requiredHeight) => {
if (y - requiredHeight < margin) {
page = pdfDoc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin;
}
};
try {
if (logoBytes) {
const logoImage = await pdfDoc.embedPng(logoBytes);
const logoDims = logoImage.scale(0.2);
page.drawImage(logoImage, {
x: margin,
y: pageHeight - margin - 30,
width: logoDims.width,
height: logoDims.height,
});
page.drawText("EduAid generated Quiz", {
x: margin + logoDims.width + 10,
y: pageHeight - margin,
size: 20,
});
page.drawText("Created On: " + d.toString(), {
x: margin + logoDims.width + 10,
y: pageHeight - margin - 30,
size: 10,
});
}
} catch (err) {
page.drawText("EduAid generated Quiz", {
x: margin,
y: pageHeight - margin,
size: 20,
});
}
for (const qaPair of qaPairs) {
let requiredHeight = 60;
const questionLines = wrapText(qaPair.question, maxContentWidth);
requiredHeight += questionLines.length * 20;
if (mode !== 'answers') {
if (qaPair.question_type === "Boolean") {
requiredHeight += 60;
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const optionsCount = qaPair.options ? qaPair.options.length + 1 : 1;
requiredHeight += optionsCount * 25;
} else {
requiredHeight += 40;
}
}
if (mode === 'answers' || mode === 'questions_answers') {
requiredHeight += 40;
}
createNewPageIfNeeded(requiredHeight);
if (mode !== 'answers') {
questionLines.forEach((line, i) => {
const prefix = i === 0 ? `Q${questionIndex}) ` : ' ';
page.drawText(`${prefix}${line}`, { x: margin, y: y - i * 20, size: 12 });
});
y -= questionLines.length * 20 + 20;
if (mode === 'questions') {
if (qaPair.question_type === "Boolean") {
const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
['True', 'False'].forEach(option => {
radioGroup.addOptionToPage(option, page, {
x: margin + 20, y, width: 15, height: 15
});
page.drawText(option, { x: margin + 40, y: y + 2, size: 12 });
y -= 20;
});
} else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") {
const allOptions = [...(qaPair.options || [])];
if (qaPair.answer && !allOptions.includes(qaPair.answer)) allOptions.push(qaPair.answer);
const shuffled = shuffleArray([...allOptions]);
const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`);
shuffled.forEach((option, idx) => {
radioGroup.addOptionToPage(`option${idx}`, page, {
x: margin + 20, y, width: 15, height: 15
});
const optionLines = wrapText(option, maxContentWidth - 60);
optionLines.forEach((line, i) => {
page.drawText(line, { x: margin + 40, y: y + 2 - i * 15, size: 12 });
});
y -= Math.max(25, optionLines.length * 20);
});
} else if (qaPair.question_type === "Short") {
const field = form.createTextField(`question${questionIndex}_answer`);
field.setText('');
field.addToPage(page, {
x: margin, y: y - 20, width: maxContentWidth, height: 20
});
y -= 40;
}
}
}
if (mode === 'answers' || mode === 'questions_answers') {
const answerLines = wrapText(`Answer ${questionIndex}: ${qaPair.answer}`, maxContentWidth);
answerLines.forEach((line, i) => {
page.drawText(line, {
x: margin, y: y - i * 15, size: 12, color: rgb(0, 0.5, 0)
});
});
y -= answerLines.length * 20;
}
y -= 20;
questionIndex++;
}
const pdfBytes = await pdfDoc.save();
self.postMessage(pdfBytes);
self.close();
} catch (error) {
self.postMessage({ error: error.message });
self.close();
}
};
🤖 Prompt for AI Agents
In eduaid_web/src/workers/pdfWorker.js between lines 31 and 162, add input
validation at the start of the onmessage handler to check that qaPairs is an
array, mode is a valid string, and logoBytes is either undefined or a
Uint8Array; if validation fails, post an error message and terminate the worker.
Wrap the entire processing logic in a try-catch block to catch any runtime
errors, post an error message on failure, and ensure the worker terminates after
completing processing or on error by calling self.close().

Comment thread extension/src/pages/question/Question.jsx
Comment thread eduaid_web/src/pages/Output.jsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants