几种 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.Sections0.Margins.Left =10f;

server.Document.Sections0.Margins.Right = 10f;

server.Document.Sections0.Margins.Top = 10f;

server.Document.Sections0.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.Sections0.Margins.Left = 10f;

server.Document.Sections0.Margins.Right = 10f;

server.Document.Sections0.Margins.Top = 10f;

server.Document.Sections0.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);
        }
    }
相关推荐
asdzx6713 小时前
如何使用 C# 提取 PPT 文本与图片
c#·ppt·提取文本
2501_9307077820 小时前
使用C#代码使用条件格式为 Excel 隔行设置颜色
c#·excel
何以解忧唯有撸码1 天前
【开源】c#造个轮子-日程安排工具
c#·自定义控件
周杰伦fans1 天前
CAD菜单栏和工具栏都不见了,怎么显示?
其他·c#
曹牧1 天前
C#:HashSet<T> 去重
c#
czhc11400756631 天前
起点、口径与因果:今天用错的七把尺子
c#
泡海椒1 天前
PDF 表格样式优化:jquick-pdf 边框、圆角、背景色
java·开发语言·pdf
c#上位机2 天前
C#上位机项目实战——Task.Run执行带参数的方法
c#·上位机
Lost of 程序猿2 天前
用 YARP 为 .NET 微服务搭一座“立交桥“:路由转发与生产配置实践
c#·asp.net
泡海椒2 天前
jquick-pdf 表格实战:动态数据 PDF 报表生成
java·开发语言·pdf