几种 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种那样的简单内容,就是杀鸡有牛刀了

代码

cs 复制代码
    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);
        }
    }
相关推荐
View121381 天前
在 .NET 中使用 Moonshot Kimi + AgentFramework:从 SDK 到 Agent 的完整实践
c#·agent·kimi
德育处主任Pro1 天前
『NAS』在飞牛部署PDF全能工具-StirlingPDF
pdf·nas
FlDmr4i281 天前
.NET 10 & C# 14 New Features 新增功能介绍-扩展成员Extension Members
开发语言·c#·.net
QJtDK1R5a1 天前
C# 14 中的新增功能
开发语言·c#
其实秋天的枫1 天前
【26大英赛】全国大学生英语竞赛C类历年真题及答案电子版PDF(2012-2025年)
经验分享·pdf
开开心心_Every1 天前
免费轻量电子书阅读器,多系统记笔记听书
linux·运维·服务器·神经网络·安全·机器学习·pdf
雨浓YN1 天前
WebApi 通讯-DeepSeek API调用文档
c#
优化控制仿真模型1 天前
【26年四级最新】英语四级高频核心词汇1500+真题PDF电子版
经验分享·pdf
yuan199971 天前
C# 断点续传下载文件工具设计与实现
开发语言·c#
优化控制仿真模型1 天前
2026年新高考英语大纲词汇表3500个电子版PDF(含正序版、乱序版和默写版)
经验分享·pdf