public PdfOptions() { Scale = 1; MarginOptions = new MarginOptions(); HeaderTemplate = string.Empty; FooterTemplate = string.Empty; PageRanges = string.Empty; Format = string.Empty; }
} /* * private void SpireToPDF(string html, Stream stream) { * var doc = new PdfDocument(); * var format = new PdfHtmlLayoutFormat(); * format.IsWaiting = false; * var settings = new PdfPageSettings(); * settings.Size = PdfPageSize.A4; * doc.LoadFromHTML(html, false, settings, format); * doc.SaveToStream(stream, FileFormat.PDF); * * } * private void GemBoxToPDF(string html, Stream stream) { * var doc = new DocumentModel(); * //doc.Content.LoadText(html, new HtmlLoadOptions()); * doc.Save(stream, new PdfSaveOptions()); * }*/ private async void PuppeteerToPDF(string html, string fileName) { await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision); var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); using (var page = await browser.NewPageAsync()) { await page.SetContentAsync(html); var result = await page.GetContentAsync(); Debug.WriteLine("browser result: " + result); var options = new PdfOptions(); var margins = new MarginOptions(); options.MarginOptions.Left = "25"; options.MarginOptions.Right = "25"; await page.PdfAsync(fileName, options); } }
/// <summary> /// Converts the HTML files to a PDF. /// </summary> /// <param name="options">The options.</param> /// <param name="logger">The logger.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task <ConcurrentBag <HtmlToPdfFile> > ProcessAsync(Options options, ILogger logger) { DateTime dtNow = DateTime.Now; // local time Encoding encoding = Encoding.Default; if (!string.IsNullOrEmpty(options.Encoding)) { encoding = Encoding.GetEncoding(options.Encoding); } if (!options.Inputs.Any() && !options.DumpDefaultTocXsl) { throw new ApplicationException($"At least one input must be specified."); } if (string.IsNullOrEmpty(options.OutputFilePath) && !options.DumpDefaultTocXsl) { throw new ApplicationException($"An output must be specified."); } if (options.DumpDefaultTocXsl) { string defaultTocXsl = options.DefaultTableOfContentsStyleSheetBuilder(options.OutputDottedLinesInTableOfContents); logger.LogOutput(defaultTocXsl); } ConcurrentBag <HtmlToPdfFile> htmlToPdfFiles = new ConcurrentBag <HtmlToPdfFile>(); foreach (string input in options.Inputs) { logger.LogDebug(input); } string outputFilePath = options.OutputFilePath; options.UserStyleSheet = options.UserStyleSheet?.Trim('"'); string title = options.Title; BrowserDownloader.DownloadBrowser(logger); using (TempDirectory tempDirectory = new TempDirectory()) { var launchOptions = new LaunchOptions { SlowMo = 0, Headless = true, Timeout = 0, LogProcess = false, EnqueueTransportMessages = true, Devtools = false, WebSocketFactory = WebSocketFactory, UserDataDir = tempDirectory.DirectoryPath, Args = options.AdditionalArguments?.ToArray() ?? new string[] { }, }; MarginOptions marginOptions = new MarginOptions { Bottom = options.BottomMargin, Left = options.LeftMargin, Right = options.RightMargin, Top = options.TopMargin, }; await Policy .Handle <ProcessException>() .RetryForeverAsync(onRetry: ex => { // executed before each retry // https://github.com/hardkoded/puppeteer-sharp/issues/1509 // ex. PuppeteerSharp.ProcessException: Failed to launch Chromium! [0909/142354.872:FATAL:feature_list.cc(282)] Check failed: !g_initialized_from_accessor. // Error: Backtrace: // Error: ovly_debug_event [0x00007FFE262A1252+16183762] // Error: ovly_debug_event [0x00007FFE262A0832+16181170] // Error: ovly_debug_event [0x00007FFE262B3383+16257795] // Error: ovly_debug_event [0x00007FFE262A3386+16192262] // Error: ovly_debug_event [0x00007FFE25DF4B2E+11283118] // Error: ovly_debug_event [0x00007FFE2621DB58+15645400] // Error: ovly_debug_event [0x00007FFE2621DACD+15645261] // Error: ovly_debug_event [0x00007FFE26248F28+15822504] // Error: ovly_debug_event [0x00007FFE2621D35E+15643358] // Error: ovly_debug_event [0x00007FFE262483E3+15819619] // Error: ovly_debug_event [0x00007FFE262482BB+15819323] // Error: ovly_debug_event [0x00007FFE262480F2+15818866] // Error: ChromeMain [0x00007FFE253311B6+286] // Error: Ordinal0 [0x00007FF65A33275F+10079] // Error: Ordinal0 [0x00007FF65A33182D+6189] // Error: GetHandleVerifier [0x00007FF65A43B7C2+697538] // Error: BaseThreadInitThunk [0x00007FFE5B2D84D4+20] // Error: RtlUserThreadStart [0x00007FFE5B95E871+33] // ex. PuppeteerSharp.ProcessException: Failed to create connection ---> System.TimeoutException: Timeout of 30000 ms exceeded logger.LogWarning(ex.ToString()); Thread.Sleep(1000); }) .ExecuteAsync(async() => { bool coverAdded = false; using (Browser browser = await Puppeteer.LaunchAsync(launchOptions)) { try { PdfPrinter pdfPrinter = new PdfPrinter(browser, logger); // cover options HtmlToPdfOptions htmlToPdfOptions = new HtmlToPdfOptions { StyleSheet = options.UserStyleSheet, JavascriptDelayInMilliseconds = options.JavascriptDelayInMilliseconds, Landscape = options.Landscape, PaperFormat = options.PaperFormat, Height = options.PageHeight, Width = options.PageWidth, PrintBackground = options.PrintBackground, }; if (!string.IsNullOrEmpty(options.Cover) && (!coverAdded)) { // print cover string pdfFile = await pdfPrinter.PrintAsPdfAsync( options.Cover, htmlToPdfOptions, null); int numberOfPages = PdfDocument.CountNumberOfPages(pdfFile); logger.LogDebug($"Cover file \"{options.Cover}\" contains number of PDF pages: {numberOfPages}."); HtmlToPdfFile htmlToPdfFile = new HtmlToPdfFile { Input = options.Cover, Index = 0, PdfFilePath = pdfFile, PrintHeaderAndFooter = false, NumberOfPages = numberOfPages, }; htmlToPdfFiles.Add(htmlToPdfFile); coverAdded = true; } // page options htmlToPdfOptions.MarginOptions = marginOptions; // header htmlToPdfOptions.HeaderTemplateBuilder.Left = options.HeaderLeft; htmlToPdfOptions.HeaderTemplateBuilder.Center = options.HeaderCenter; htmlToPdfOptions.HeaderTemplateBuilder.Right = options.HeaderRight; string headerFontSize = options.HeaderFontSize.AppendUnits("px"); htmlToPdfOptions.HeaderTemplateBuilder.FontSize = headerFontSize; htmlToPdfOptions.HeaderTemplateBuilder.FontName = options.HeaderFontName; htmlToPdfOptions.HeaderTemplateBuilder.Html = options.HeaderHtml; // footer htmlToPdfOptions.FooterTemplateBuilder.Left = options.FooterLeft; htmlToPdfOptions.FooterTemplateBuilder.Center = options.FooterCenter; htmlToPdfOptions.FooterTemplateBuilder.Right = options.FooterRight; string footerFontSize = options.FooterFontSize.AppendUnits("px"); htmlToPdfOptions.FooterTemplateBuilder.FontSize = footerFontSize; htmlToPdfOptions.FooterTemplateBuilder.FontName = options.FooterFontName; htmlToPdfOptions.FooterTemplateBuilder.Html = options.FooterHtml; // global header/footer variables // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF Dictionary <string, string> variables = new Dictionary <string, string> { { "page", "<span class=\"pageNumber\"></span>" }, { "date", dtNow.ToString("d") }, // M/dd/yyyy { "title", "<span class=\"title\"></span>" }, { "frompage", (options.PageOffset + 1).ToString() }, { "isodate", dtNow.ToString("yyyy-MM-dd") }, // ISO 8601 extended format { "time", dtNow.ToString("h:mm:ss tt") }, // ex. 3:58:45 PM { "doctitle", title }, }; // count the number of PDF pages each HTML file will be printed as var tasks = options.Inputs .Where(x => htmlToPdfFiles.All(y => y.Input != x)) .Select(async input => { // print as pdf // insert an empty page to avoid unexpected margins on the first page, which would affect the page count // https://stackoverflow.com/a/55480268/90287 // https://github.com/puppeteer/puppeteer/issues/2592 HtmlToPdfOptions tempHtmlToPdfOptions = htmlToPdfOptions.Copy(); tempHtmlToPdfOptions.PageOffset = 1; string pdfFile = await pdfPrinter.PrintAsPdfAsync( input, tempHtmlToPdfOptions, variables); // count the number of pages int numberOfPages = PdfDocument.CountNumberOfPages(pdfFile); logger.LogDebug($"\"{input}\" contains number of PDF pages: {numberOfPages}."); HtmlToPdfFile htmlToPdfFile = new HtmlToPdfFile { Input = input, Index = options.Inputs.IndexOf(input), PdfFilePath = pdfFile, PrintHeaderAndFooter = true, NumberOfPages = numberOfPages, }; htmlToPdfFiles.Add(htmlToPdfFile); }); await Task.WhenAll(tasks); variables.Add("topage", htmlToPdfFiles.Sum(x => x.NumberOfPages).ToString()); // update models with title and headings List <Task> updateTitleAndHeadingsTasks = new List <Task>(); foreach (HtmlToPdfFile htmlToPdfFile in htmlToPdfFiles) { updateTitleAndHeadingsTasks.Add(Task.Run(() => { // set the title and headings HtmlFileParser htmlFileParser = new HtmlFileParser(htmlToPdfFile.Input); htmlToPdfFile.TitleAndHeadings = htmlFileParser.GetTitleAndHeadings(options.AddTableOfContents); htmlToPdfFile.Title = htmlToPdfFile.TitleAndHeadings.First().Text; })); } await Task.WhenAll(updateTitleAndHeadingsTasks); // create table of contents if (options.AddTableOfContents) { await PdfOutlineBuilder.BuildOutlineAsync( coverAdded, options.AddTableOfContents, options.OutputDottedLinesInTableOfContents, htmlToPdfFiles, options.OutlineBuilder, options.DefaultTableOfContentsStyleSheetBuilder, pdfPrinter, htmlToPdfOptions, variables); } // update models and re-print HTML files to include headers/footers with page numbers tasks = htmlToPdfFiles.Select(async htmlToPdfFile => { if (string.IsNullOrEmpty(title) && (htmlToPdfFile.Index == 0)) { // set the PDF title title = htmlToPdfFile.Title; variables["doctitle"] = title; } // sum the number of pages in previous documents to get the current page number offset int currentPageNumber = htmlToPdfFiles .Where(x => x.Index < htmlToPdfFile.Index) .Sum(x => x.NumberOfPages) + 1; if ((currentPageNumber + htmlToPdfFile.NumberOfPages) <= (options.PageOffset + 1)) { logger.LogDebug($"Skipping printing {htmlToPdfFile.Input}"); htmlToPdfFile.Skip = true; return; } // print as pdf with page number offset htmlToPdfFile.OutputPdfFilePageNumber = currentPageNumber; logger.LogDebug($"'{htmlToPdfFile.Input}' mapped to output PDF file page number {currentPageNumber}."); htmlToPdfOptions.PageOffset = currentPageNumber - 1; htmlToPdfOptions.PageNumberOffset = options.PageOffset; htmlToPdfOptions.NumberOfPages = htmlToPdfFile.NumberOfPages; // TODO: only print as PDF again if topage variable is actually used in the header/footer if (htmlToPdfFile.PrintHeaderAndFooter) { // delete previously created PDF file File.Delete(htmlToPdfFile.PdfFilePath); // print as pdf string pdfFile = await pdfPrinter.PrintAsPdfAsync( htmlToPdfFile.Input, htmlToPdfOptions, variables); htmlToPdfFile.PdfFilePath = pdfFile; } // parse PDF to get heading page numbers PdfDocument.SetHeadingPageNumbers(htmlToPdfFile); }); await Task.WhenAll(tasks); } finally { await browser.CloseAsync(); } } }); } // merge pdf files List <string> pdfFilesToMerge = htmlToPdfFiles .Where(x => !x.Skip) .OrderBy(x => x.OutputPdfFilePageNumber) .Select(x => x.PdfFilePath) .ToList(); if (!string.IsNullOrEmpty(outputFilePath)) { byte[] mergedBytes = PdfMerger.Merge(pdfFilesToMerge); File.WriteAllBytes(outputFilePath, mergedBytes); try { // update external file links to internal document links PdfDocument.UpdateLinks(outputFilePath, htmlToPdfFiles, logger); } catch (Exception ex) { throw new UpdatePdfLinksException(outputFilePath, htmlToPdfFiles, ex); } PdfDocument.SetTitle(outputFilePath, title); } // delete temporary PDF files var deleteTempFileTasks = htmlToPdfFiles .Where(x => !string.IsNullOrEmpty(x.PdfFilePath)) .Select(async input => { await Task.Factory.StartNew(() => { if (File.Exists(input.PdfFilePath)) { File.Delete(input.PdfFilePath); } }); }); await Task.WhenAll(deleteTempFileTasks); return(htmlToPdfFiles); }
public async Task <IActionResult> GeneratePdfDocument([FromBody] DocumentData documentData) { string path = _configuration.GetSection("AppSettings:ChromeExecutablePath").Value; try { using (var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, ExecutablePath = path, IgnoreHTTPSErrors = true })) { using (var page = await browser.NewPageAsync()) { //await page.SetContentAsync(documentData.PdfBodyHTML); Dictionary <string, string> headers = new Dictionary <string, string>(); headers.Add("Accept-Charset", "utf-8"); headers.Add("Content-Type", "text/html; charset=utf-8"); var response = await page.GoToAsync("data:text/html," + documentData.PdfBodyHTML, WaitUntilNavigation.Networkidle0); await page.SetContentAsync(Encoding.UTF8.GetString(await response.BufferAsync())); await page.AddStyleTagAsync(new AddTagOptions { Url = documentData.PdfCssUrl }); await page.SetExtraHttpHeadersAsync(headers); await page.EvaluateExpressionAsync("window.scrollBy(0, window.innerHeight);"); MarginOptions marginOption; if (documentData.DocumentMargin == null) { marginOption = new MarginOptions { Top = _configuration.GetSection("AppSettings:Margin:Top").Value, Bottom = _configuration.GetSection("AppSettings:Margin:Bottom").Value, Left = _configuration.GetSection("AppSettings:Margin:Left").Value, Right = _configuration.GetSection("AppSettings:Margin:Right").Value }; } else { marginOption = documentData.DocumentMargin; } byte[] pdfFile = await page.PdfDataAsync(new PdfOptions { MarginOptions = marginOption, PrintBackground = true, DisplayHeaderFooter = true, HeaderTemplate = documentData.PdfHeaderHTML, FooterTemplate = documentData.PdfFooterHTML }); return(File(pdfFile, "application/pdf")); } } } catch (Exception ex) { return(null); } }