Skip to content

feat: print the exported PDF instead of HTML layout#318

Open
guitarbeat wants to merge 2 commits into
mainfrom
jules-13362181141554358321-cab329b6
Open

feat: print the exported PDF instead of HTML layout#318
guitarbeat wants to merge 2 commits into
mainfrom
jules-13362181141554358321-cab329b6

Conversation

@guitarbeat

@guitarbeat guitarbeat commented May 15, 2026

Copy link
Copy Markdown
Owner

What
Refactored the export and print functionality in ExportService.js to ensure that clicking "Print" natively prints the generated PDF rather than relying on an HTML equivalent.

Why
The previous implementation for printing rebuilt an HTML DOM representation of the pages for the browser to print, which could be slightly inconsistent with the exact generated jsPDF output. By using the new _generatePdf helper, the exported PDF output is fed directly into an iframe with doc.autoPrint() enabled.

Verification
Tested exporting and printing flows successfully. No regressions found in standard layout handling.


PR created automatically by Jules for task 13362181141554358321 started by @guitarbeat

Summary by Sourcery

Refactor export and print flows to generate a single jsPDF document and use it for both downloading and printing.

New Features:

  • Enable native printing of the generated PDF instead of printing an HTML-based layout.

Enhancements:

  • Extract PDF creation into a reusable _generatePdf helper shared by export and print handlers.
  • Simplify print window handling to load a PDF blob into a hidden iframe with auto-print behavior and cleanup.

Copilot AI review requested due to automatic review settings May 15, 2026 19:38
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented May 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
zine-ify Ready Ready Preview, Comment May 15, 2026 7:46pm

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@codeant-ai

codeant-ai Bot commented May 15, 2026

Copy link
Copy Markdown

User does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@sourcery-ai

sourcery-ai Bot commented May 15, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors ExportService to centralize PDF generation in a new _generatePdf helper and updates export/print flows so both use the same jsPDF output, with printing now handled by loading an auto-printing PDF blob into an iframe instead of rendering an HTML layout.

Sequence diagram for updated PDF-based print flow

sequenceDiagram
  actor User
  participant UI as PrintButton
  participant ExportService
  participant jsPDFDoc as jsPDF_Document
  participant Browser as Iframe

  User->>UI: click
  UI->>ExportService: handlePrint()
  ExportService->>ExportService: _generatePdf()
  ExportService->>jsPDFDoc: build pages
  jsPDFDoc-->>ExportService: doc
  ExportService->>jsPDFDoc: autoPrint()
  ExportService->>jsPDFDoc: output(bloburl)
  jsPDFDoc-->>ExportService: blobUrl
  ExportService->>Browser: openPrintWindow(blobUrl)
  Browser-->>Browser: create iframe with src=blobUrl
  Browser-->>User: PDF opens and triggers print dialog
  Browser-->>Browser: optionally call iframe.contentWindow.print()
Loading

File-Level Changes

Change Details Files
Centralize PDF creation logic into a reusable helper used by both export and print flows.
  • Rename handleExport implementation to a new async _generatePdf method that builds the jsPDF document based on current grid, images, flips, zooms, and templates.
  • Return the constructed jsPDF doc instance from _generatePdf so callers can either save or print it.
  • Reintroduce handleExport as a thin wrapper that awaits _generatePdf and calls doc.save('zine.pdf').
  • Minor style tweak to guard against missing page image URLs by using an explicit block in the continue condition.
src/services/ExportService.js
Change print behavior to print the actual generated PDF via an iframe using jsPDF autoPrint instead of constructing an HTML layout.
  • Replace handlePrint implementation so it calls _generatePdf, invokes doc.autoPrint(), then generates a blob URL via doc.output('bloburl') and passes it to openPrintWindow.
  • Remove the HTML-based sheet-building and print template helpers (buildSheetsHtml and buildPrintHtml), plus the old openPrintWindow that wrote HTML into a new window or iframe and called window.print().
  • Redefine openPrintWindow to accept a PDF blob URL, attach it as the src of a hidden iframe, wait for load or a timeout, then focus and call print() on the frame window with try/catch for cross-origin issues, and finally remove the iframe and revoke the blob URL after a delay.
  • Adjust iframe onload wait timeout from 300ms to 1000ms and increase cleanup delay to 5000ms to give the print dialog time to appear before tearing down the iframe.
src/services/ExportService.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@pullpal-ai pullpal-ai 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.

The refactor to generate and print the PDF directly is a good improvement for output fidelity. However, the method _generatePdf is now publically used by both export and print, but its name and leading underscore suggest it is private. Consider renaming it to generatePdf (without underscore) if it is meant for internal reuse, or at least clarify its intended usage in a comment.

}

async openPrintWindow(blobUrl) {
const printFrame = document.createElement('iframe');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In openPrintWindow, you set the iframe's src to the blob URL before appending it to the DOM. For some browsers, setting src before appending can cause loading issues. Consider appending the iframe first, then setting src to ensure reliable loading across browsers.

printFrame.onload = resolve;
setTimeout(resolve, 300);
// Also resolve after a timeout just in case
setTimeout(resolve, 1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The timeout for resolving the iframe's load event is increased to 1000ms, and the iframe is removed after 5000ms. These are arbitrary values and may not always be sufficient, especially for large PDFs or slow devices. Consider making these timeouts configurable or at least documenting why these values were chosen.

setTimeout(() => {
printFrame.remove();
}, 1000);
URL.revokeObjectURL(blobUrl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In openPrintWindow, you revoke the blob URL after removing the iframe, which is good. However, if the print dialog is still open or the PDF is not fully loaded, this could cause issues. Consider revoking the blob URL only after confirming the print dialog has closed, if possible, or after a longer timeout.

@pullpal-ai pullpal-ai 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.

The new implementation removes a large amount of HTML/CSS-based print logic. If there are any accessibility or ARIA considerations in the old approach (such as alt text for images), make sure these are not lost in the PDF output. PDFs can be less accessible than HTML for screen readers.

// which injects JS into the PDF to trigger print automatically when opened.
// However, for iframes sometimes it's necessary, but we'll try without,
// or just let the PDF handle it if supported. Some browsers require manual trigger.
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The try/catch block for calling printFrame.contentWindow?.print() is good for robustness, but the comment could be clearer about which browsers require this and under what circumstances. Consider linking to browser compatibility documentation or providing more context.

@pullpal-ai pullpal-ai 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.

The method openPrintWindow is now tightly coupled to PDF printing via blob URLs. If future requirements need to support other formats (e.g., HTML fallback), consider splitting this into more generic and specialized methods.

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • In openPrintWindow, you can drop the e parameter in the catch block instead of using // eslint-disable-line no-unused-vars; this keeps linting clean without needing a suppression.
  • The 5-second timeout before removing the iframe and revoking the blobUrl may be brittle for slower devices or large PDFs; consider tying cleanup to the afterprint event or making the delay less hardcoded to avoid cutting off the PDF while the print dialog is still in use.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `openPrintWindow`, you can drop the `e` parameter in the `catch` block instead of using `// eslint-disable-line no-unused-vars`; this keeps linting clean without needing a suppression.
- The 5-second timeout before removing the iframe and revoking the `blobUrl` may be brittle for slower devices or large PDFs; consider tying cleanup to the `afterprint` event or making the delay less hardcoded to avoid cutting off the PDF while the print dialog is still in use.

## Individual Comments

### Comment 1
<location path="src/services/ExportService.js" line_range="191-200" />
<code_context>

-    printFrame.contentWindow?.focus();
-    printFrame.contentWindow?.print();
+    // We don't need to manually call .print() because doc.autoPrint() was used,
+    // which injects JS into the PDF to trigger print automatically when opened.
+    // However, for iframes sometimes it's necessary, but we'll try without,
+    // or just let the PDF handle it if supported. Some browsers require manual trigger.
+    try {
+      printFrame.contentWindow?.focus();
+      printFrame.contentWindow?.print();
+    } catch (e) { // eslint-disable-line no-unused-vars
+      // Ignored: cross-origin frame access might throw depending on blob handling
</code_context>
<issue_to_address>
**issue (bug_risk):** Clarify the interaction between `autoPrint()` and the explicit `print()` call to avoid double dialogs.

The current comment says we "don't need" to call `.print()` because `autoPrint()` handles it, but we still always call `printFrame.contentWindow?.print()`. This could cause two print dialogs in some browsers (one from the injected script and one from the manual call). Consider either removing the manual call, or gating it to cases where `autoPrint()` isn't supported. Also update the comment so it accurately describes the behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +191 to +200
// We don't need to manually call .print() because doc.autoPrint() was used,
// which injects JS into the PDF to trigger print automatically when opened.
// However, for iframes sometimes it's necessary, but we'll try without,
// or just let the PDF handle it if supported. Some browsers require manual trigger.
try {
printFrame.contentWindow?.focus();
printFrame.contentWindow?.print();
} catch (e) { // eslint-disable-line no-unused-vars
// Ignored: cross-origin frame access might throw depending on blob handling
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Clarify the interaction between autoPrint() and the explicit print() call to avoid double dialogs.

The current comment says we "don't need" to call .print() because autoPrint() handles it, but we still always call printFrame.contentWindow?.print(). This could cause two print dialogs in some browsers (one from the injected script and one from the manual call). Consider either removing the manual call, or gating it to cases where autoPrint() isn't supported. Also update the comment so it accurately describes the behavior.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c2b10de5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 186 to +188
printFrame.onload = resolve;
setTimeout(resolve, 300);
// Also resolve after a timeout just in case
setTimeout(resolve, 1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Wait for PDF iframe to load before calling print

The print flow can fire too early because the load wait unconditionally resolves after 1 second even if the PDF viewer has not finished loading the blob URL. On slower devices or multi-page exports, contentWindow.print() may run against a not-yet-ready frame, which can produce blank/partial prints or no dialog at all. This should wait for a real ready signal (or retry) instead of using a fixed timeout as a success path.

Useful? React with 👍 / 👎.

Comment on lines +197 to +200
printFrame.contentWindow?.print();
} catch (e) { // eslint-disable-line no-unused-vars
// Ignored: cross-origin frame access might throw depending on blob handling
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate print errors instead of silently ignoring them

If printFrame.contentWindow.print() throws (which can happen when browsers expose PDF blobs through a cross-origin/internal viewer), the catch block suppresses the failure and the method still resolves. In that scenario the caller cannot show the existing "Print Failed" toast and users get a no-op Print action. Re-throwing or reporting this path is needed so failed prints are visible and recoverable.

Useful? React with 👍 / 👎.

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