经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.NET下免费开源的PDF类库(PDFSharp)
来源:cnblogs  作者:Hi_Carl  时间:2024/5/24 10:27:08  对本文有异议

前言

目前.NET 体系下常见的PDF类库有AsposeQuestPDFSpireiTextSharp等,有一说一都挺好用的,我个人特别喜欢QuestPDF它基于 C# Fluent API 提供全面的布局引擎;但是这些库要么属于商业库价格不菲(能理解收费),但是年费太贵了。要么是有条件限制开源的,如Spire开源版本有各种限制。iTextSharp虽然没有限制,但是开源协议不友好(AGPL),用于闭源商业软件属于要挂耻辱柱的行为了。

无意间发现了另一款基于.NET6的跨平台、免费开源(MIT协议)pdf处理库:PDFSharp,该库还有基于.NET Framework的版本https://pdfsharp.net/ ,.NET6版本好像是去年刚发布的,还有一个较为活跃的社区https://forum.pdfsharp.net/。

尝试使用了下,还不错,该有的都有,简单的pdf文件可以直接使用PDFSharp库生成,复杂点的则提供了MigraDoc来编辑。我自己的小应用都已经上生成环境了,个人觉得该库是挺ok的了。

.NET Framework文档站点下有很多例子大家可以看看:

image

image

我的使用方式较为粗暴,使用MigraDoc编辑文档表格,再生成PDF文件。有时间再尝试封装个类似于QuestPDF的扩展库,太喜欢Fluent这种形式了。

代码例子

让我们来制作下图的pdf吧
image

新建一个项目,通过Nuget引入PDFsharp、PDFsharp-MigraDoc,
若用System.Drawing图形库则不用引用SkiaSharp,我的例子使用SkiaSharp图形库便于跨平台。

首先是字体的导入

  • 因为PDFSharp本身不支持中文字体,但提供了自定义解析器的处理,所以我们先实现下中文字体解析器。先将黑体作为嵌入资源导入项目中,路径是/Fonts/下
  • 新建一个文件ChineseFontResolver.cs用来实现我们的中文解析器
  1. using PdfSharp.Fonts;
  2. using System.Reflection;
  3. namespace pdfsharpDemo;
  4. /// <summary>
  5. /// 中文字体解析器
  6. /// </summary>
  7. public class ChineseFontResolver : IFontResolver
  8. {
  9. /// <summary>
  10. /// 字体作为嵌入资源所在程序集
  11. /// </summary>
  12. public static string FontAssemblyString { get; set; } = "pdfsharpDemo";
  13. /// <summary>
  14. /// 字体作为嵌入资源所在命名空间
  15. /// </summary>
  16. public static string FontNamespace { get; set; } = "pdfsharpDemo.Fonts";
  17. /// <summary>
  18. /// 字体名称
  19. /// </summary>
  20. public static class FamilyNames
  21. {
  22. // This implementation considers each font face as its own family.
  23. /// <summary>
  24. /// 仿宋
  25. /// </summary>
  26. public const string SIMFANG = "simfang.ttf";
  27. /// <summary>
  28. /// 黑体
  29. /// </summary>
  30. public const string SIMHEI = "simhei.ttf";
  31. /// <summary>
  32. /// 楷书
  33. /// </summary>
  34. public const string SIMKAI = "simkai.ttf";
  35. /// <summary>
  36. /// 隶书
  37. /// </summary>
  38. public const string SIMLI = "simli.ttf";
  39. /// <summary>
  40. /// 宋体
  41. /// </summary>
  42. public const string SIMSUN = "simsun.ttf";
  43. /// <summary>
  44. /// 宋体加粗
  45. /// </summary>
  46. public const string SIMSUNB = "simsunb.ttf";
  47. /// <summary>
  48. /// 幼圆
  49. /// </summary>
  50. public const string SIMYOU = "simyou.ttf";
  51. }
  52. /// <summary>
  53. /// Selects a physical font face based on the specified information
  54. /// of a required typeface.
  55. /// </summary>
  56. /// <param name="familyName">Name of the font family.</param>
  57. /// <param name="isBold">Set to <c>true</c> when a bold font face
  58. /// is required.</param>
  59. /// <param name="isItalic">Set to <c>true</c> when an italic font face
  60. /// is required.</param>
  61. /// <returns>
  62. /// Information about the physical font, or null if the request cannot be satisfied.
  63. /// </returns>
  64. public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
  65. {
  66. // Note: PDFsharp calls ResolveTypeface only once for each unique combination
  67. // of familyName, isBold, and isItalic.
  68. return new FontResolverInfo(familyName, isBold, isItalic);
  69. // Return null means that the typeface cannot be resolved and PDFsharp forwards
  70. // the typeface request depending on PDFsharp build flavor and operating system.
  71. // Alternatively forward call to PlatformFontResolver.
  72. //return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
  73. }
  74. /// <summary>
  75. /// Gets the bytes of a physical font face with specified face name.
  76. /// </summary>
  77. /// <param name="faceName">A face name previously retrieved by ResolveTypeface.</param>
  78. /// <returns>
  79. /// The bits of the font.
  80. /// </returns>
  81. public byte[]? GetFont(string faceName)
  82. {
  83. // Note: PDFsharp never calls GetFont twice with the same face name.
  84. // Note: If a typeface is resolved by the PlatformFontResolver.ResolveTypeface
  85. // you never come here.
  86. var name = $"{FontNamespace}.{faceName}";
  87. using Stream stream = Assembly.Load(FontAssemblyString).GetManifestResourceStream(name) ?? throw new ArgumentException("No resource named '" + name + "'.");
  88. int num = (int)stream.Length;
  89. byte[] array = new byte[num];
  90. stream.Read(array, 0, num);
  91. // Return the bytes of a font.
  92. return array;
  93. }
  94. }

好了,开始制作我们的pdf吧

  1. // See https://aka.ms/new-console-template for more information
  2. using Microsoft.Extensions.Configuration;
  3. using MigraDoc.DocumentObjectModel;
  4. using MigraDoc.DocumentObjectModel.Tables;
  5. using MigraDoc.Rendering;
  6. using PdfSharp.Drawing;
  7. using PdfSharp.Fonts;
  8. using PdfSharp.Pdf;
  9. using PdfSharp.Pdf.IO;
  10. using pdfsharpDemo;
  11. using SkiaSharp;
  12. using System;
  13. using System.IO;
  14. using static pdfsharpDemo.ChineseFontResolver;
  15. Console.WriteLine("Hello, PDFSharp!");
  16. // 设置PDFSharp全局字体为自定义解析器
  17. GlobalFontSettings.FontResolver = new ChineseFontResolver();
  18. #region pdf页面的基本设置
  19. var document = new Document();
  20. var _style = document.Styles["Normal"];//整体样式
  21. _style.Font.Name = FamilyNames.SIMHEI;
  22. _style.Font.Size = 10;
  23. var _tableStyle = document.Styles.AddStyle("Table", "Normal");//表格样式
  24. _tableStyle.Font.Name = _style.Font.Name;
  25. _tableStyle.Font.Size = _style.Font.Size;
  26. var _section = document.AddSection();
  27. _section.PageSetup = document.DefaultPageSetup.Clone();
  28. _section.PageSetup.PageFormat = PageFormat.A4; //A4纸规格
  29. _section.PageSetup.Orientation = Orientation.Landscape;//纸张方向:横向,默认是竖向
  30. _section.PageSetup.TopMargin = 50f;//上边距 50
  31. _section.PageSetup.LeftMargin = 25f;//左边距 20
  32. #endregion
  33. //这里采用三个表格实现标题栏、表格内容、底栏提示
  34. //创建一个表格,并且设置边距
  35. var topTable = _section.AddTable();
  36. topTable.Style = _style.Name;
  37. topTable.TopPadding = 0;
  38. topTable.BottomPadding = 3;
  39. topTable.LeftPadding = 0;
  40. var tableWidth = _section.PageSetup.PageHeight - _section.PageSetup.LeftMargin * 2;
  41. // 标题栏分为三格
  42. float[] topTableWidths = [tableWidth / 2, tableWidth / 2];
  43. //生成对应的二列,并设置宽度
  44. foreach (var item in topTableWidths)
  45. {
  46. var column = topTable.AddColumn();
  47. column.Width = item;
  48. }
  49. //生成行,设置标题
  50. var titleRow = topTable.AddRow();
  51. titleRow.Cells[0].MergeRight = 1;//向右跨一列(合并列)
  52. titleRow.Cells[0].Format.Alignment = ParagraphAlignment.Center;//元素居中
  53. var parVlaue = titleRow.Cells[0].AddParagraph();
  54. parVlaue.Format = new ParagraphFormat();
  55. parVlaue.Format.Font.Bold = true;//粗体
  56. parVlaue.Format.Font.Size = 16;//字体大小
  57. parVlaue.AddText("我的第一个PDFSharp例子");
  58. //生成标题行,这里我们设置两行
  59. var row2 = topTable.AddRow();
  60. var noCell = row2.Cells[0];
  61. noCell.Format.Alignment = ParagraphAlignment.Left;
  62. noCell.AddParagraph().AddText($"编号:00000001");
  63. var orgNameCell = row2.Cells[1];
  64. orgNameCell.Format.Alignment = ParagraphAlignment.Right;
  65. orgNameCell.AddParagraph().AddText("单位:PDFSharp研究小组");
  66. var row3 = topTable.AddRow();
  67. var createAtCell = row3.Cells[0];
  68. createAtCell.Format.Alignment = ParagraphAlignment.Left;
  69. createAtCell.AddParagraph().AddText($"查询时间:{DateTime.Now.AddDays(-1):yyyy年MM月dd日 HH:mm}");
  70. var printTimeCell = row3.Cells[1];
  71. printTimeCell.Format.Alignment = ParagraphAlignment.Right;
  72. printTimeCell.AddParagraph().AddText($"打印时间:{DateTime.Now:yyyy年MM月dd日 HH:mm}");
  73. //表格内容
  74. var contentTable = _section.AddTable();
  75. contentTable.Style = _style.Name;
  76. contentTable.Borders = new Borders
  77. {
  78. Color = Colors.Black,
  79. Width = 0.25
  80. };
  81. contentTable.Borders.Left.Width = 0.5;
  82. contentTable.Borders.Right.Width = 0.5;
  83. contentTable.TopPadding = 6;
  84. contentTable.BottomPadding = 0;
  85. //这里设置8列好了
  86. var tableWidths = new float[8];
  87. tableWidths[0] = 30;
  88. tableWidths[1] = 60;
  89. tableWidths[2] = 40;
  90. tableWidths[5] = 60;
  91. tableWidths[6] = 80;
  92. float w2 = (_section.PageSetup.PageHeight - (_section.PageSetup.LeftMargin * 2) - tableWidths.Sum()) / 2;//假装自适应,哈哈哈
  93. tableWidths[3] = w2;
  94. tableWidths[4] = w2;
  95. //生成列
  96. foreach (var item in tableWidths)
  97. {
  98. var column = contentTable.AddColumn();
  99. column.Width = item;
  100. column.Format.Alignment = ParagraphAlignment.Center;
  101. }
  102. //生成标题行
  103. var headRow = contentTable.AddRow();
  104. headRow.TopPadding = 6;
  105. headRow.BottomPadding = 6;
  106. headRow.Format.Font.Bold = true;
  107. headRow.Format.Font.Size = "12";
  108. headRow.VerticalAlignment = VerticalAlignment.Center;
  109. headRow.Cells[0].AddParagraph().AddText("序号");
  110. headRow.Cells[1].AddParagraph().AddText("姓名");
  111. headRow.Cells[2].AddParagraph().AddText("性别");
  112. headRow.Cells[3].AddParagraph().AddText("家庭地址");
  113. headRow.Cells[4].AddParagraph().AddText("工作单位");
  114. var cParVlaue = headRow.Cells[5].AddParagraph();
  115. "银行卡总额(元)".ToList()?.ForEach(o => cParVlaue.AddChar(o));//自动换行 使用AddChar
  116. headRow.Cells[6].AddParagraph().AddText("联系电话");
  117. //内容列,随便填点吧 用元组实现,懒得搞个类了
  118. List<(string name, string sex, string addree, string workplace, decimal? amount, string phone)> contentData = new()
  119. {
  120. new () {name="张珊",sex="女",addree="市政府宿舍",workplace="市政府",amount=12002M,phone="138********3333"},
  121. new () {name="李思",sex="女",addree="省政府宿舍大楼下的小破店旁边的垃圾桶前面的别墅",workplace="省教育局",amount=220000M,phone="158********3456"},
  122. new () {name="王武",sex="男",addree="凤凰村",workplace="老破小公司",amount=-8765M,phone="199********6543"},
  123. new () {name="",sex="",addree="",workplace="",amount=null,phone=""},
  124. };
  125. var index = 1;
  126. foreach (var (name, sex, addree, workplace, amount, phone) in contentData)
  127. {
  128. var dataRow = contentTable.AddRow();
  129. dataRow.TopPadding = 6;
  130. dataRow.BottomPadding = 6;
  131. dataRow.Cells[0].AddParagraph().AddText($"{index++}");
  132. dataRow.Cells[1].AddParagraph().AddText(name);
  133. dataRow.Cells[2].AddParagraph().AddText(sex);
  134. var addreeParVlaue = dataRow.Cells[3].AddParagraph();
  135. addree?.ToList()?.ForEach(o => addreeParVlaue.AddChar(o));//自动换行 使用AddChar
  136. dataRow.Cells[4].AddParagraph().AddText(workplace);
  137. dataRow.Cells[5].AddParagraph().AddText(amount?.ToString() ?? "");
  138. dataRow.Cells[6].AddParagraph().AddText(phone);
  139. }
  140. //空白 段落 分隔下间距
  141. Paragraph paragraph = new();// 设置段落格式
  142. paragraph.Format.SpaceBefore = "18pt"; // 设置空行高度为 12 磅
  143. document.LastSection.Add(paragraph); // 将段落添加到文档中
  144. //底栏提示
  145. var tipsTable = _section.AddTable();
  146. tipsTable.Style = _style.Name;
  147. tipsTable.TopPadding = 3;
  148. var tipsTableColumn = tipsTable.AddColumn();
  149. tipsTableColumn.Width = _section.PageSetup.PageHeight - _section.PageSetup.LeftMargin * 2;
  150. var tipsParagraph = tipsTable.AddRow().Cells[0].AddParagraph();
  151. tipsParagraph.Format.Font.Bold = true;
  152. tipsParagraph.Format.Font.Color = Colors.Red; //设置红色
  153. tipsParagraph.AddText($"注:隐私信息是我们必须要注重的废话连篇的东西,切记切记,不可忽视,因小失大;");
  154. #region 页码
  155. _section.PageSetup.DifferentFirstPageHeaderFooter = false;
  156. var pager = _section.Footers.Primary.AddParagraph();
  157. pager.AddText($"第\t");
  158. pager.AddPageField();
  159. pager.AddText($"\t页");
  160. pager.Format.Alignment = ParagraphAlignment.Center;
  161. #endregion
  162. //生成PDF
  163. var pdfRenderer = new PdfDocumentRenderer();
  164. using var memoryStream = new MemoryStream();
  165. pdfRenderer.Document = document;
  166. pdfRenderer.RenderDocument();
  167. pdfRenderer.PdfDocument.Save(memoryStream);
  168. var pdfDocument = PdfReader.Open(memoryStream);
  169. //为了跨平台 用的是SkiaSharp,大家自己转为System.Drawing实现即可,较为简单就不写了
  170. #region 水印
  171. using var watermarkMemoryStream = new MemoryStream();
  172. var watermarkImgPath = "D:\\logo.png";
  173. using var watermarkFile = System.IO.File.OpenRead(watermarkImgPath);// 读取文件
  174. using var fileStream = new SKManagedStream(watermarkFile);
  175. using var bitmap = SKBitmap.Decode(fileStream);
  176. //设置半透明
  177. var transparent = new SKColor(0, 0, 0, 0);
  178. for (int w = 0; w < bitmap.Width; w++)
  179. {
  180. for (int h = 0; h < bitmap.Height; h++)
  181. {
  182. SKColor c = bitmap.GetPixel(w, h);
  183. SKColor newC = c.Equals(transparent) ? c : new SKColor(c.Red, c.Green, c.Blue, 70);
  184. bitmap.SetPixel(w, h, newC);
  185. }
  186. }
  187. using var resized = bitmap.Resize(new SKImageInfo(200, 80), SKFilterQuality.High);
  188. using var newImage = SKImage.FromBitmap(resized);
  189. newImage.Encode(SKEncodedImageFormat.Png, 90).SaveTo(watermarkMemoryStream); // 保存文件
  190. using var image = XImage.FromStream(watermarkMemoryStream);
  191. var xPoints = 6;
  192. var yPoints = 4;
  193. for (int i = 0; i <= xPoints; i++)
  194. {
  195. var xPoint = image.PointWidth * i * 1.2;
  196. var xTranslateTransform = xPoint + image.PointWidth / 2;
  197. for (int j = 0; j <= yPoints; j++)
  198. {
  199. var yPoint = image.PointHeight * j * 1.2;
  200. var yTranslateTransform = yPoint + image.PointHeight / 8;
  201. foreach (var page in pdfDocument.Pages)
  202. {
  203. using var xgr = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);
  204. xgr.TranslateTransform(xTranslateTransform, yTranslateTransform);
  205. xgr.RotateTransform(-45);
  206. xgr.TranslateTransform(-xTranslateTransform, -yTranslateTransform);
  207. xgr.DrawImage(image, xPoint, yPoint, 200, 80);
  208. }
  209. }
  210. }
  211. #endregion
  212. pdfDocument.Save(memoryStream);
  213. var outputPdfFilePath = "D:\\pdfdemo.pdf";
  214. //保存到本地
  215. using var fs = new FileStream(outputPdfFilePath, FileMode.Create);
  216. byte[] bytes = new byte[memoryStream.Length];
  217. memoryStream.Seek(0, SeekOrigin.Begin);
  218. memoryStream.Read(bytes, 0, (int)memoryStream.Length);
  219. fs.Write(bytes, 0, bytes.Length);
  220. Console.WriteLine("生成成功!");

至此我们就制作好了一个简单的pdf,当然了这里没有加上文件信息那些,仅仅是生成内容罢了,有那些需要的可以自己根据文档站点看看如何设置。

上述代码的源码地址:https://gitee.com/huangguishen/MyFile/tree/master/PDFSharpDemo

原文链接:https://www.cnblogs.com/laikwan/p/18206787

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号