几种 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);
        }
    }
相关推荐
bugcome_com3 小时前
简述 C# 成员修饰符(Modifier)——从整体到细节全面解析
c#
helloworddm3 小时前
防止应用多开-WPF
服务器·架构·c#
我是唐青枫4 小时前
深入理解 Parallel.ForEachAsync:C#.NET 并行调度模型揭秘
c#·.net
bugcome_com5 小时前
深入解析 C# 中 const 与 readonly 的核心区别
c#
kylezhao20195 小时前
工业机器视觉基础认知
计算机视觉·c#·visionpro
水龙吟啸5 小时前
项目设计与开发:智慧校园食堂系统
python·机器学习·前端框架·c#·团队开发·visual studio·数据库系统
flysh055 小时前
C#语言基础知识要点
开发语言·c#
闻缺陷则喜何志丹6 小时前
【三维建模】三维建模基础一
c#·计算几何·cad·三维建模·布尔运算·切点
我是唐青枫18 小时前
深入理解 C#.NET Interlocked.Increment:原子操作的核心
c#·.net