Skip to content

feat(Html2Pdf): add PaperFormat parameter#634

Merged
ArgoZhang merged 2 commits intomasterfrom
refactor-pdf
Oct 30, 2025
Merged

feat(Html2Pdf): add PaperFormat parameter#634
ArgoZhang merged 2 commits intomasterfrom
refactor-pdf

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Oct 30, 2025

Link issues

fixes #633

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Extend DefaultPdfService to support additional PDF settings including paper format, margins, background printing, scale, and header/footer for HTML-to-PDF conversion

New Features:

  • Add support for specifying paper format in PdfOptions for HTML-to-PDF conversion
  • Expose PrintBackground, DisplayHeaderFooter, Scale, and custom margin settings in the PDF generation options

Enhancements:

  • Implement helper methods to map custom PaperFormat and MarginOptions to PuppeteerSharp.Media types

Copilot AI review requested due to automatic review settings October 30, 2025 11:26
@bb-auto bb-auto Bot added the enhancement New feature or request label Oct 30, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Oct 30, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Oct 30, 2025

Reviewer's Guide

This PR enhances the HTML-to-PDF service by introducing a customizable PaperFormat parameter and expanding PDF output options, implemented via new helper methods to map user settings to PuppeteerSharp’s PDF configuration.

Class diagram for updated PdfOptions and PaperFormat handling

classDiagram
    class PdfOptions {
        +bool Landscape
        +bool PrintBackground
        +PaperFormat Format
        +MarginOptions MarginOptions
        +bool DisplayHeaderFooter
        +double Scale
    }
    class PaperFormat {
        +double Width
        +double Height
        +A0
        +A1
        +A2
        +A3
        +A4
        +A5
        +A6
        +Letter
        +Legal
        +Tabloid
        +Ledger
    }
    class MarginOptions {
        +double Top
        +double Bottom
        +double Left
        +double Right
    }
    PdfOptions --> PaperFormat : uses
    PdfOptions --> MarginOptions : uses
Loading

Class diagram for DefaultPdfService helper methods

classDiagram
    class DefaultPdfService {
        +static PuppeteerSharp.PdfOptions GetOptions(PdfOptions? options)
        +static PuppeteerSharp.Media.MarginOptions? GetMarginOptions(MarginOptions options)
        +static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
    }
    DefaultPdfService --> PdfOptions : uses
    DefaultPdfService --> MarginOptions : uses
    DefaultPdfService --> PaperFormat : uses
Loading

File-Level Changes

Change Details Files
Extended PDF options mapping
  • Added PrintBackground, DisplayHeaderFooter, and Scale properties
  • Integrated Format property using a helper mapper
  • Linked MarginOptions mapping
DefaultPdfService.cs
Introduced GetMarginOptions helper
  • Created method to convert custom MarginOptions to PuppeteerSharp.Media.MarginOptions
DefaultPdfService.cs
Implemented PaperFormat-to-PuppeteerPaperFormat conversion
  • Mapped each predefined PaperFormat enum value to PuppeteerSharp.Media.PaperFormat
  • Supported custom dimensions fallback
DefaultPdfService.cs
Updated project file for new feature
  • Adjusted package references to include PaperFormat support
BootstrapBlazor.Html2Pdf.csproj

Assessment against linked issues

Issue Objective Addressed Explanation
#633 Add a PaperFormat parameter to the Html2Pdf functionality, allowing users to specify the paper format for PDF generation.

Possibly linked issues


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

@ArgoZhang ArgoZhang merged commit 53a9929 into master Oct 30, 2025
5 of 6 checks passed
@ArgoZhang ArgoZhang deleted the refactor-pdf branch October 30, 2025 11:27
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider replacing the long if/else chain in GetFormat with a C# switch expression or a dictionary lookup to make the mapping more concise and maintainable.
  • Null‐check options.MarginOptions (and any other optional sub‐properties) before calling GetMarginOptions to prevent potential NullReferenceExceptions when those properties are not provided.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider replacing the long if/else chain in GetFormat with a C# switch expression or a dictionary lookup to make the mapping more concise and maintainable.
- Null‐check options.MarginOptions (and any other optional sub‐properties) before calling GetMarginOptions to prevent potential NullReferenceExceptions when those properties are not provided.

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.Html2Pdf/Services/DefaultPdfService.cs:163-166` </location>
<code_context>
+        }
+        else
+        {
+            return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
+        }
+    }
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Validate custom PaperFormat dimensions to prevent invalid PDF output.

Ensure Width and Height are positive and within acceptable limits to prevent runtime errors or malformed PDFs.

```suggestion
        else
        {
            // Validate custom PaperFormat dimensions
            const double MinDimension = 1.0; // Minimum allowed dimension in inches
            const double MaxDimension = 100.0; // Maximum allowed dimension in inches

            if (format.Width < MinDimension || format.Height < MinDimension ||
                format.Width > MaxDimension || format.Height > MaxDimension)
            {
                throw new ArgumentOutOfRangeException(
                    $"Custom PaperFormat dimensions are invalid. Width and Height must be between {MinDimension} and {MaxDimension} inches. " +
                    $"Received Width: {format.Width}, Height: {format.Height}");
            }

            return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
        }
```
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.Html2Pdf/Services/DefaultPdfService.cs:117` </location>
<code_context>
+        Right = options.Right
+    };
+
+    private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
+    {
+        if (format == PaperFormat.A0)
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the long if/else chain in GetFormat with a switch-expression or static lookup for improved clarity and maintainability.

Consider using a switch-expression (or a static lookup) to collapse the long if/else chain:

```csharp
private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
    => format switch
    {
        PaperFormat.A0     => PuppeteerSharp.Media.PaperFormat.A0,
        PaperFormat.A1     => PuppeteerSharp.Media.PaperFormat.A1,
        PaperFormat.A2     => PuppeteerSharp.Media.PaperFormat.A2,
        PaperFormat.A3     => PuppeteerSharp.Media.PaperFormat.A3,
        PaperFormat.A4     => PuppeteerSharp.Media.PaperFormat.A4,
        PaperFormat.A5     => PuppeteerSharp.Media.PaperFormat.A5,
        PaperFormat.A6     => PuppeteerSharp.Media.PaperFormat.A6,
        PaperFormat.Letter => PuppeteerSharp.Media.PaperFormat.Letter,
        PaperFormat.Legal  => PuppeteerSharp.Media.PaperFormat.Legal,
        PaperFormat.Tabloid=> PuppeteerSharp.Media.PaperFormat.Tabloid,
        PaperFormat.Ledger => PuppeteerSharp.Media.PaperFormat.Ledger,
        _                  => new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height)
    };
```

Or prebuild a readonly dictionary:

```csharp
private static readonly IReadOnlyDictionary<PaperFormat, PuppeteerSharp.Media.PaperFormat>
    _formatMap = new Dictionary<PaperFormat, PuppeteerSharp.Media.PaperFormat>
{
    [PaperFormat.A0]     = PuppeteerSharp.Media.PaperFormat.A0,
    [PaperFormat.A1]     = PuppeteerSharp.Media.PaperFormat.A1,
    // ...
};

private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
    => _formatMap.TryGetValue(format, out var pf)
       ? pf
       : new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
```

Either approach keeps the same behavior but is far more concise and easier to maintain.
</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 +163 to +166
else
{
return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Validate custom PaperFormat dimensions to prevent invalid PDF output.

Ensure Width and Height are positive and within acceptable limits to prevent runtime errors or malformed PDFs.

Suggested change
else
{
return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
}
else
{
// Validate custom PaperFormat dimensions
const double MinDimension = 1.0; // Minimum allowed dimension in inches
const double MaxDimension = 100.0; // Maximum allowed dimension in inches
if (format.Width < MinDimension || format.Height < MinDimension ||
format.Width > MaxDimension || format.Height > MaxDimension)
{
throw new ArgumentOutOfRangeException(
$"Custom PaperFormat dimensions are invalid. Width and Height must be between {MinDimension} and {MaxDimension} inches. " +
$"Received Width: {format.Width}, Height: {format.Height}");
}
return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
}

Right = options.Right
};

private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
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 (complexity): Consider replacing the long if/else chain in GetFormat with a switch-expression or static lookup for improved clarity and maintainability.

Consider using a switch-expression (or a static lookup) to collapse the long if/else chain:

private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
    => format switch
    {
        PaperFormat.A0     => PuppeteerSharp.Media.PaperFormat.A0,
        PaperFormat.A1     => PuppeteerSharp.Media.PaperFormat.A1,
        PaperFormat.A2     => PuppeteerSharp.Media.PaperFormat.A2,
        PaperFormat.A3     => PuppeteerSharp.Media.PaperFormat.A3,
        PaperFormat.A4     => PuppeteerSharp.Media.PaperFormat.A4,
        PaperFormat.A5     => PuppeteerSharp.Media.PaperFormat.A5,
        PaperFormat.A6     => PuppeteerSharp.Media.PaperFormat.A6,
        PaperFormat.Letter => PuppeteerSharp.Media.PaperFormat.Letter,
        PaperFormat.Legal  => PuppeteerSharp.Media.PaperFormat.Legal,
        PaperFormat.Tabloid=> PuppeteerSharp.Media.PaperFormat.Tabloid,
        PaperFormat.Ledger => PuppeteerSharp.Media.PaperFormat.Ledger,
        _                  => new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height)
    };

Or prebuild a readonly dictionary:

private static readonly IReadOnlyDictionary<PaperFormat, PuppeteerSharp.Media.PaperFormat>
    _formatMap = new Dictionary<PaperFormat, PuppeteerSharp.Media.PaperFormat>
{
    [PaperFormat.A0]     = PuppeteerSharp.Media.PaperFormat.A0,
    [PaperFormat.A1]     = PuppeteerSharp.Media.PaperFormat.A1,
    // ...
};

private static PuppeteerSharp.Media.PaperFormat GetFormat(PaperFormat format)
    => _formatMap.TryGetValue(format, out var pf)
       ? pf
       : new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);

Either approach keeps the same behavior but is far more concise and easier to maintain.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR extends the PDF generation functionality by adding support for additional PDF options including paper format, margins, background printing, headers/footers, and scaling. It also introduces conditional versioning based on Visual Studio version to support both v17.0 and v18.0.

  • Added support for paper format conversion between custom and PuppeteerSharp enums
  • Added support for margin options, print background, display header/footer, and scale settings
  • Implemented conditional package versioning based on Visual Studio version

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/components/BootstrapBlazor.Html2Pdf/Services/DefaultPdfService.cs Extended GetOptions method to map additional PDF properties and added helper methods for format and margin conversion
src/components/BootstrapBlazor.Html2Pdf/BootstrapBlazor.Html2Pdf.csproj Introduced conditional versioning for VS 17.0 and 18.0 with corresponding package version updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +109 to +115
private static PuppeteerSharp.Media.MarginOptions? GetMarginOptions(MarginOptions options) => new PuppeteerSharp.Media.MarginOptions
{
Top = options.Top,
Bottom = options.Bottom,
Left = options.Left,
Right = options.Right
};
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

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

The method does not handle null input. If options.MarginOptions is null when called from GetOptions (line 103), this will throw a NullReferenceException when accessing options.Top, options.Bottom, etc. Consider adding a null check or making the parameter nullable and returning null if the input is null.

Suggested change
private static PuppeteerSharp.Media.MarginOptions? GetMarginOptions(MarginOptions options) => new PuppeteerSharp.Media.MarginOptions
{
Top = options.Top,
Bottom = options.Bottom,
Left = options.Left,
Right = options.Right
};
private static PuppeteerSharp.Media.MarginOptions? GetMarginOptions(MarginOptions? options)
{
if (options == null) return null;
return new PuppeteerSharp.Media.MarginOptions
{
Top = options.Top,
Bottom = options.Bottom,
Left = options.Left,
Right = options.Right
};
}

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +166
if (format == PaperFormat.A0)
{
return PuppeteerSharp.Media.PaperFormat.A0;
}
else if (format == PaperFormat.A1)
{
return PuppeteerSharp.Media.PaperFormat.A1;
}
else if (format == PaperFormat.A2)
{
return PuppeteerSharp.Media.PaperFormat.A2;
}
else if (format == PaperFormat.A3)
{
return PuppeteerSharp.Media.PaperFormat.A3;
}
else if (format == PaperFormat.A4)
{
return PuppeteerSharp.Media.PaperFormat.A4;
}
else if (format == PaperFormat.A5)
{
return PuppeteerSharp.Media.PaperFormat.A5;
}
else if (format == PaperFormat.A6)
{
return PuppeteerSharp.Media.PaperFormat.A6;
}
else if (format == PaperFormat.Letter)
{
return PuppeteerSharp.Media.PaperFormat.Letter;
}
else if (format == PaperFormat.Legal)
{
return PuppeteerSharp.Media.PaperFormat.Legal;
}
else if (format == PaperFormat.Tabloid)
{
return PuppeteerSharp.Media.PaperFormat.Tabloid;
}
else if (format == PaperFormat.Ledger)
{
return PuppeteerSharp.Media.PaperFormat.Ledger;
}
else
{
return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
}
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

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

The long if-else chain can be refactored using a switch expression for better readability and maintainability. This would make the code more concise and easier to extend in the future.

Suggested change
if (format == PaperFormat.A0)
{
return PuppeteerSharp.Media.PaperFormat.A0;
}
else if (format == PaperFormat.A1)
{
return PuppeteerSharp.Media.PaperFormat.A1;
}
else if (format == PaperFormat.A2)
{
return PuppeteerSharp.Media.PaperFormat.A2;
}
else if (format == PaperFormat.A3)
{
return PuppeteerSharp.Media.PaperFormat.A3;
}
else if (format == PaperFormat.A4)
{
return PuppeteerSharp.Media.PaperFormat.A4;
}
else if (format == PaperFormat.A5)
{
return PuppeteerSharp.Media.PaperFormat.A5;
}
else if (format == PaperFormat.A6)
{
return PuppeteerSharp.Media.PaperFormat.A6;
}
else if (format == PaperFormat.Letter)
{
return PuppeteerSharp.Media.PaperFormat.Letter;
}
else if (format == PaperFormat.Legal)
{
return PuppeteerSharp.Media.PaperFormat.Legal;
}
else if (format == PaperFormat.Tabloid)
{
return PuppeteerSharp.Media.PaperFormat.Tabloid;
}
else if (format == PaperFormat.Ledger)
{
return PuppeteerSharp.Media.PaperFormat.Ledger;
}
else
{
return new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height);
}
return format switch
{
PaperFormat.A0 => PuppeteerSharp.Media.PaperFormat.A0,
PaperFormat.A1 => PuppeteerSharp.Media.PaperFormat.A1,
PaperFormat.A2 => PuppeteerSharp.Media.PaperFormat.A2,
PaperFormat.A3 => PuppeteerSharp.Media.PaperFormat.A3,
PaperFormat.A4 => PuppeteerSharp.Media.PaperFormat.A4,
PaperFormat.A5 => PuppeteerSharp.Media.PaperFormat.A5,
PaperFormat.A6 => PuppeteerSharp.Media.PaperFormat.A6,
PaperFormat.Letter => PuppeteerSharp.Media.PaperFormat.Letter,
PaperFormat.Legal => PuppeteerSharp.Media.PaperFormat.Legal,
PaperFormat.Tabloid => PuppeteerSharp.Media.PaperFormat.Tabloid,
PaperFormat.Ledger => PuppeteerSharp.Media.PaperFormat.Ledger,
_ => new PuppeteerSharp.Media.PaperFormat(format.Width, format.Height)
};

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Html2Pdf): add PaperFormat parameter

2 participants