diff --git a/BootstrapBlazor.Extensions.sln b/BootstrapBlazor.Extensions.sln
index 65a3f31a..f215cb11 100644
--- a/BootstrapBlazor.Extensions.sln
+++ b/BootstrapBlazor.Extensions.sln
@@ -184,6 +184,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Authenticat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Graph", "src\components\BootstrapBlazor.Graph\BootstrapBlazor.Graph.csproj", "{CED55D86-57CF-CB0D-E880-370C44C0DB1F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Html2Pdf.Playwright", "src\components\BootstrapBlazor.Html2Pdf.Playwright\BootstrapBlazor.Html2Pdf.Playwright.csproj", "{F3043A78-1942-4524-BDC4-7E88F56DF3D5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -498,6 +500,10 @@ Global
{CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -584,6 +590,7 @@ Global
{2F37FBF4-5C1C-4493-B614-0E8361432621} = {FF1089BE-C704-4374-B629-C57C08E1798F}
{1FDDF0AD-7AB6-4706-A183-26C680817BB4} = {FF1089BE-C704-4374-B629-C57C08E1798F}
{CED55D86-57CF-CB0D-E880-370C44C0DB1F} = {FF1089BE-C704-4374-B629-C57C08E1798F}
+ {F3043A78-1942-4524-BDC4-7E88F56DF3D5} = {FF1089BE-C704-4374-B629-C57C08E1798F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF}
diff --git a/src/components/BootstrapBlazor.Html2Pdf.Playwright/BootstrapBlazor.Html2Pdf.Playwright.csproj b/src/components/BootstrapBlazor.Html2Pdf.Playwright/BootstrapBlazor.Html2Pdf.Playwright.csproj
new file mode 100644
index 00000000..2376acf3
--- /dev/null
+++ b/src/components/BootstrapBlazor.Html2Pdf.Playwright/BootstrapBlazor.Html2Pdf.Playwright.csproj
@@ -0,0 +1,17 @@
+
+
+
+ 9.0.0
+
+
+
+ Bootstrap Blazor WebAssembly wasm UI Components Pdf
+ Bootstrap UI components extensions of Html2Pdf use Playwright lib
+
+
+
+
+
+
+
+
diff --git a/src/components/BootstrapBlazor.Html2Pdf.Playwright/Extensions/ServiceCollectionExtensions.cs b/src/components/BootstrapBlazor.Html2Pdf.Playwright/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..fc9e11d8
--- /dev/null
+++ b/src/components/BootstrapBlazor.Html2Pdf.Playwright/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using BootstrapBlazor.Components;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+///
+/// BootstrapBlazor 服务扩展类
+///
+public static class BootstrapBlazorHtml2PdfServiceExtensions
+{
+ ///
+ /// 添加 AzureOpenAIService 服务
+ ///
+ ///
+ public static IServiceCollection AddBootstrapBlazorHtml2PdfUsePlaywrightService(this IServiceCollection services)
+ {
+ services.AddSingleton();
+#if NET8_0_OR_GREATER
+ services.AddKeyedSingleton("BootstrapBlazor.Html2Pdf.Playwright");
+#endif
+ return services;
+ }
+}
diff --git a/src/components/BootstrapBlazor.Html2Pdf.Playwright/Services/DefaultPdfService.cs b/src/components/BootstrapBlazor.Html2Pdf.Playwright/Services/DefaultPdfService.cs
new file mode 100644
index 00000000..6f9540db
--- /dev/null
+++ b/src/components/BootstrapBlazor.Html2Pdf.Playwright/Services/DefaultPdfService.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using Microsoft.Playwright;
+
+namespace BootstrapBlazor.Components;
+
+///
+/// 默认 Html to Pdf 实现
+///
+class DefaultPdfService : IHtml2Pdf
+{
+ ///
+ ///
+ ///
+ public Task PdfDataAsync(string url)
+ {
+ return GeneratePdfFromUrlAsync(url);
+ }
+
+ ///
+ ///
+ ///
+ public async Task PdfStreamAsync(string url)
+ {
+ var data = await GeneratePdfFromUrlAsync(url);
+ return new MemoryStream(data);
+ }
+
+ ///
+ /// Export method
+ ///
+ /// html raw string
+ ///
+ ///
+ public Task PdfDataFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null)
+ {
+ return GeneratePdfFromHtmlAsync(html, links, scripts);
+ }
+
+ ///
+ /// Export method
+ ///
+ /// html raw string
+ ///
+ ///
+ public async Task PdfStreamFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null)
+ {
+ var data = await PdfDataFromHtmlAsync(html, links, scripts);
+ return new MemoryStream(data);
+ }
+
+ private static async Task GeneratePdfFromUrlAsync(string url)
+ {
+ using var playwright = await Playwright.CreateAsync();
+ await using var browser = await playwright.Chromium.LaunchAsync(CreateOptions());
+
+ await using var context = await browser.NewContextAsync();
+ var page = await context.NewPageAsync();
+
+ await page.GotoAsync(url, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
+ return await page.PdfAsync(new PagePdfOptions
+ {
+ Format = "A4",
+ Landscape = false,
+ });
+ }
+
+ private static async Task GeneratePdfFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null)
+ {
+ using var playwright = await Playwright.CreateAsync();
+ await using var browser = await playwright.Chromium.LaunchAsync(CreateOptions());
+
+ await using var context = await browser.NewContextAsync();
+ var page = await context.NewPageAsync();
+
+ await page.SetContentAsync(html);
+ await AddStyleTagAsync(page, links);
+ await AddScriptTagAsync(page, scripts);
+
+ return await page.PdfAsync(new PagePdfOptions
+ {
+ Format = "A4",
+ Landscape = false,
+ });
+ }
+
+ private static BrowserTypeLaunchOptions CreateOptions() => new()
+ {
+ Headless = true,
+ Args = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-web-security"]
+ };
+
+ private static async Task AddStyleTagAsync(IPage page, IEnumerable? links = null)
+ {
+ var styles = new List();
+
+ if (links != null)
+ {
+ styles.AddRange(links);
+ }
+
+ foreach (var link in styles)
+ {
+ await page.AddStyleTagAsync(new PageAddStyleTagOptions()
+ {
+ Url = link,
+ });
+ }
+ }
+
+ private static async Task AddScriptTagAsync(IPage page, IEnumerable? scripts = null)
+ {
+ var tags = new List();
+
+ if (scripts != null)
+ {
+ tags.AddRange(scripts);
+ }
+
+ foreach (var script in tags)
+ {
+ await page.AddScriptTagAsync(new PageAddScriptTagOptions()
+ {
+ Url = script,
+ Type = "script"
+ });
+ }
+ }
+}
diff --git a/src/components/BootstrapBlazor.Html2Pdf.Playwright/_Imports.razor b/src/components/BootstrapBlazor.Html2Pdf.Playwright/_Imports.razor
new file mode 100644
index 00000000..77285129
--- /dev/null
+++ b/src/components/BootstrapBlazor.Html2Pdf.Playwright/_Imports.razor
@@ -0,0 +1 @@
+@using Microsoft.AspNetCore.Components.Web