几种 HTML 转 PDF的方式

起因

这是网友经常问俺的问题。

其实俺也经常完成这样的功能。

回答

其实这个有很多种方案,要根据实际的需求来分析。有简单的html  复杂的html。

先从最简单的html开始

先看结果

其实这个AI生成的markdown的内容,俺把markdown转成了html进行显示,另外也转成了pdf 。这种简单的html 可以使用 iTextSharp 完成。

NuGet:

iTextSharp 5.5.13.1

itextsharp.xmlworker 5.5.13.1

调用很简单

MarkdownToHtmlConverter hh = new MarkdownToHtmlConverter();
string html = hh.Convert(AI_answer);
var converter = new HtmlToPdfConverter(); 

 byte[] buff=  converter.ConvertHtmlStringToPdf(styledHtml);  

有点图的html

俺用Devexpress ,不仅可以生成pdf ,还可以生成pdf,类似word,但是对环境没有依赖。

也支撑

        public static void Html2Docx(string htm, Stream stream)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(htm); 
            RichEditDocumentServer server = new RichEditDocumentServer();
            MemoryStream ms = new MemoryStream(bytes);
            ms.Position = 0;
            server.LoadDocument(ms, DocumentFormat.Html);
            server.Document.Unit = DevExpress.Office.DocumentUnit.Millimeter;
            server.Document.Sections[0].Margins.Left =10f;
            server.Document.Sections[0].Margins.Right = 10f;
            server.Document.Sections[0].Margins.Top = 10f;
            server.Document.Sections[0].Margins.Bottom = 10f;
            server.SaveDocument(stream, DocumentFormat.OpenXml);            
            server.Dispose();
            ms.Dispose();
        }
        public static void Html2PDF(string htm, Stream stream)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(htm);
            RichEditDocumentServer server = new RichEditDocumentServer();
            MemoryStream ms = new MemoryStream(bytes);
            ms.Position = 0;
            server.LoadDocument(ms, DocumentFormat.Html);
            server.Document.Unit = DevExpress.Office.DocumentUnit.Millimeter;
            server.Document.Sections[0].Margins.Left = 10f;
            server.Document.Sections[0].Margins.Right = 10f;
            server.Document.Sections[0].Margins.Top = 10f;
            server.Document.Sections[0].Margins.Bottom = 10f;
            server.ExportToPdf(stream);
            server.Dispose();
            ms.Dispose();
        }

复杂的html

使用PuppeteerSharp 

           var page = await browser.NewPageAsync();
            await page.SetContentAsync(sb.ToString());
            var pdfOptions = new PdfOptions
            {
                Format = PuppeteerSharp.Media.PaperFormat.A4,
                PrintBackground = true,
                MarginOptions = new PuppeteerSharp.Media.MarginOptions
                {
                    Top = "20px",
                    Bottom = "20px",
                    Left = "20px",
                    Right = "20px"
                }
            };
            byte[] pdfBytes = await page.PdfDataAsync(pdfOptions);
            System.IO.File.WriteAllBytes(fn, pdfBytes);
            await page.DisposeAsync();

这里需要主要一点 ,如果是在web服务中使用,可以先手工下载好Chrome相关文件。

然后  手工指定目录,因为有时web服务中 会下载chorme失败。

browser = Puppeteer.LaunchAsync(new LaunchOptions
                        {
                            Headless = true, // 无头模式
                            ExecutablePath = System.IO.Path.Combine(webdir, "bin", "Chrome", "Win64-124.0.6367.201", "chrome-win64", "chrome.exe")
                        }).Result;

总结

以上3种都可以在Web服务和winform桌面上使用。

有了第3种为啥,还有使用前2种,这个主要是一个平衡的问题。第3种是比较强大,但是占用的计算资源也多。对付第1种那样的简单内容,就是杀鸡有牛刀了

代码

    public class MarkdownToHtmlConverter
    {

        public string Convert(string markdownText)
        {
            if (string.IsNullOrEmpty(markdownText))
                return string.Empty;

            try
            {
                string html = markdownText;

                // 1. 处理无序列表(ul/li)核心逻辑
                // 匹配以 "- " 或 "* " 开头的列表项,支持多行列表
                html = ProcessUnorderedLists(html);

                // 2. 基础 Markdown 语法转换(补充功能)
                html = ConvertHeadings(html);       // 标题(# ~ ######)
                html = ConvertBold(html);           // 粗体(**文本**)
                html = ConvertItalic(html);         // 斜体(*文本* 或 _文本_)
                html = ConvertLineBreaks(html);     // 换行(两个空格+回车 或 单独回车)
                html = ConvertParagraphs(html);     // 段落(空行分隔的文本块)
                html = html.Replace("</li><br />", "</li>");
                return html;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Markdown 转换 HTML 失败", ex);
            }
        }


        private string ProcessUnorderedLists(string text)
        {
            // 正则匹配连续的无序列表项(以 -/* 开头,换行分隔)
            var listRegex = new Regex(@"(^|\n)(-|\*) (.+?)(\n(?!(-|\*) ))", RegexOptions.Singleline | RegexOptions.Multiline);

            // 先将连续列表项包裹成 ul,再替换单个 li
            text = listRegex.Replace(text, match =>
            {
                string listContent = match.Value.Trim();
                // 替换单个列表项为 li 标签
                string liContent = Regex.Replace(listContent, @"(-|\*) (.+?)(\n|$)", "<li>$2</li>$3");
                return $"{match.Groups[1].Value}<ul>{liContent}</ul>{Environment.NewLine}";
            });

            // 处理单行列表项(非连续列表)
            text = Regex.Replace(text, @"(^|\n)(-|\*) (.+?)(\n|$)", "$1<ul><li>$3</li></ul>$4", RegexOptions.Multiline);

            return text;
        }

        /// <summary>
        /// 转换标题(# 到 ###### 对应 h1 到 h6)
        /// </summary>
        private string ConvertHeadings(string text)
        {
            for (int i = 6; i >= 1; i--)
            {
                string hash = new string('#', i);
                text = Regex.Replace(text, $"^{hash} (.+?)$", $"<h{i}>$1</h{i}>", RegexOptions.Multiline);
            }
            return text;
        }

        /// <summary>
        /// 转换粗体(**文本**)
        /// </summary>
        private string ConvertBold(string text)
        {
            return Regex.Replace(text, @"\*\*(.+?)\*\*", "<strong>$1</strong>");
        }

        /// <summary>
        /// 转换斜体(*文本* 或 _文本_)
        /// </summary>
        private string ConvertItalic(string text)
        {
            text = Regex.Replace(text, @"\*(.+?)\*", "<em>$1</em>");
            text = Regex.Replace(text, @"_(.+?_)_", "<em>$1</em>");
            return text;
        }

        /// <summary>
        /// 转换换行符
        /// </summary>
        private string ConvertLineBreaks(string text)
        {
            // 匹配两个空格+换行 或 单独换行
            return Regex.Replace(text, @"(\s{2}|\n)", "<br />");
        }

        /// <summary>
        /// 转换段落(空行分隔的文本块)
        /// </summary>
        private string ConvertParagraphs(string text)
        {
            return Regex.Replace(text, @"^(?!<h|<ul|<li|<strong|<em|<br)(.+?)$", "<p>$1</p>", RegexOptions.Multiline);
        }
    }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月巴月巴白勺合鸟月半

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值