我的使用方式较为粗暴,使用MigraDoc编辑文档表格,再生成PDF文件。有时间再尝试封装个类似于QuestPDF的扩展库,太喜欢Fluent这种形式了。
using PdfSharp.Fonts; using System.Reflection; namespace pdfsharpDemo; /// <summary> /// 堆代码 duidaima.com /// 中文字体解析器 /// </summary> public class ChineseFontResolver : IFontResolver { /// <summary> /// 字体作为嵌入资源所在程序集 /// </summary> public static string FontAssemblyString { get; set; } = "pdfsharpDemo"; /// <summary> /// 字体作为嵌入资源所在命名空间 /// </summary> public static string FontNamespace { get; set; } = "pdfsharpDemo.Fonts"; /// <summary> /// 字体名称 /// </summary> public static class FamilyNames { // This implementation considers each font face as its own family. /// <summary> /// 仿宋 /// </summary> public const string SIMFANG = "simfang.ttf"; /// <summary> /// 黑体 /// </summary> public const string SIMHEI = "simhei.ttf"; /// <summary> /// 楷书 /// </summary> public const string SIMKAI = "simkai.ttf"; /// <summary> /// 隶书 /// </summary> public const string SIMLI = "simli.ttf"; /// <summary> /// 宋体 /// </summary> public const string SIMSUN = "simsun.ttf"; /// <summary> /// 宋体加粗 /// </summary> public const string SIMSUNB = "simsunb.ttf"; /// <summary> /// 幼圆 /// </summary> public const string SIMYOU = "simyou.ttf"; } /// <summary> /// Selects a physical font face based on the specified information /// of a required typeface. /// </summary> /// <param name="familyName">Name of the font family.</param> /// <param name="isBold">Set to <c>true</c> when a bold font face /// is required.</param> /// <param name="isItalic">Set to <c>true</c> when an italic font face /// is required.</param> /// <returns> /// Information about the physical font, or null if the request cannot be satisfied. /// </returns> public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic) { // Note: PDFsharp calls ResolveTypeface only once for each unique combination // of familyName, isBold, and isItalic. return new FontResolverInfo(familyName, isBold, isItalic); // Return null means that the typeface cannot be resolved and PDFsharp forwards // the typeface request depending on PDFsharp build flavor and operating system. // Alternatively forward call to PlatformFontResolver. //return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic); } /// <summary> /// Gets the bytes of a physical font face with specified face name. /// </summary> /// <param name="faceName">A face name previously retrieved by ResolveTypeface.</param> /// <returns> /// The bits of the font. /// </returns> public byte[]? GetFont(string faceName) { // Note: PDFsharp never calls GetFont twice with the same face name. // Note: If a typeface is resolved by the PlatformFontResolver.ResolveTypeface // you never come here. var name = $"{FontNamespace}.{faceName}"; using Stream stream = Assembly.Load(FontAssemblyString).GetManifestResourceStream(name) ?? throw new ArgumentException("No resource named '" + name + "'."); int num = (int)stream.Length; byte[] array = new byte[num]; stream.Read(array, 0, num); // Return the bytes of a font. return array; } }好了,开始制作我们的pdf吧
// See https://aka.ms/new-console-template for more information using Microsoft.Extensions.Configuration; using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.Rendering; using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; using PdfSharp.Pdf.IO; using pdfsharpDemo; using SkiaSharp; using System; using System.IO; using static pdfsharpDemo.ChineseFontResolver; Console.WriteLine("Hello, PDFSharp!"); // 设置PDFSharp全局字体为自定义解析器 GlobalFontSettings.FontResolver = new ChineseFontResolver(); #region pdf页面的基本设置 var document = new Document(); var _style = document.Styles["Normal"];//整体样式 _style.Font.Name = FamilyNames.SIMHEI; _style.Font.Size = 10; var _tableStyle = document.Styles.AddStyle("Table", "Normal");//表格样式 _tableStyle.Font.Name = _style.Font.Name; _tableStyle.Font.Size = _style.Font.Size; var _section = document.AddSection(); _section.PageSetup = document.DefaultPageSetup.Clone(); _section.PageSetup.PageFormat = PageFormat.A4; //A4纸规格 _section.PageSetup.Orientation = Orientation.Landscape;//纸张方向:横向,默认是竖向 _section.PageSetup.TopMargin = 50f;//上边距 50 _section.PageSetup.LeftMargin = 25f;//左边距 20 #endregion //这里采用三个表格实现标题栏、表格内容、底栏提示 //创建一个表格,并且设置边距 var topTable = _section.AddTable(); topTable.Style = _style.Name; topTable.TopPadding = 0; topTable.BottomPadding = 3; topTable.LeftPadding = 0; var tableWidth = _section.PageSetup.PageHeight - _section.PageSetup.LeftMargin * 2; // 标题栏分为三格 float[] topTableWidths = [tableWidth / 2, tableWidth / 2]; //生成对应的二列,并设置宽度 foreach (var item in topTableWidths) { var column = topTable.AddColumn(); column.Width = item; } //生成行,设置标题 var titleRow = topTable.AddRow(); titleRow.Cells[0].MergeRight = 1;//向右跨一列(合并列) titleRow.Cells[0].Format.Alignment = ParagraphAlignment.Center;//元素居中 var parVlaue = titleRow.Cells[0].AddParagraph(); parVlaue.Format = new ParagraphFormat(); parVlaue.Format.Font.Bold = true;//粗体 parVlaue.Format.Font.Size = 16;//字体大小 parVlaue.AddText("我的第一个PDFSharp例子"); //生成标题行,这里我们设置两行 var row2 = topTable.AddRow(); var noCell = row2.Cells[0]; noCell.Format.Alignment = ParagraphAlignment.Left; noCell.AddParagraph().AddText($"编号:00000001"); var orgNameCell = row2.Cells[1]; orgNameCell.Format.Alignment = ParagraphAlignment.Right; orgNameCell.AddParagraph().AddText("单位:PDFSharp研究小组"); var row3 = topTable.AddRow(); var createAtCell = row3.Cells[0]; createAtCell.Format.Alignment = ParagraphAlignment.Left; createAtCell.AddParagraph().AddText($"查询时间:{DateTime.Now.AddDays(-1):yyyy年MM月dd日 HH:mm}"); var printTimeCell = row3.Cells[1]; printTimeCell.Format.Alignment = ParagraphAlignment.Right; printTimeCell.AddParagraph().AddText($"打印时间:{DateTime.Now:yyyy年MM月dd日 HH:mm}"); //表格内容 var contentTable = _section.AddTable(); contentTable.Style = _style.Name; contentTable.Borders = new Borders { Color = Colors.Black, Width = 0.25 }; contentTable.Borders.Left.Width = 0.5; contentTable.Borders.Right.Width = 0.5; contentTable.TopPadding = 6; contentTable.BottomPadding = 0; //这里设置8列好了 var tableWidths = new float[8]; tableWidths[0] = 30; tableWidths[1] = 60; tableWidths[2] = 40; tableWidths[5] = 60; tableWidths[6] = 80; float w2 = (_section.PageSetup.PageHeight - (_section.PageSetup.LeftMargin * 2) - tableWidths.Sum()) / 2;//假装自适应,哈哈哈 tableWidths[3] = w2; tableWidths[4] = w2; //生成列 foreach (var item in tableWidths) { var column = contentTable.AddColumn(); column.Width = item; column.Format.Alignment = ParagraphAlignment.Center; } //生成标题行 var headRow = contentTable.AddRow(); headRow.TopPadding = 6; headRow.BottomPadding = 6; headRow.Format.Font.Bold = true; headRow.Format.Font.Size = "12"; headRow.VerticalAlignment = VerticalAlignment.Center; headRow.Cells[0].AddParagraph().AddText("序号"); headRow.Cells[1].AddParagraph().AddText("姓名"); headRow.Cells[2].AddParagraph().AddText("性别"); headRow.Cells[3].AddParagraph().AddText("家庭地址"); headRow.Cells[4].AddParagraph().AddText("工作单位"); var cParVlaue = headRow.Cells[5].AddParagraph(); "银行卡总额(元)".ToList()?.ForEach(o => cParVlaue.AddChar(o));//自动换行 使用AddChar headRow.Cells[6].AddParagraph().AddText("联系电话"); //内容列,随便填点吧 用元组实现,懒得搞个类了 List<(string name, string sex, string addree, string workplace, decimal? amount, string phone)> contentData = new() { new () {name="张珊",sex="女",addree="市政府宿舍",workplace="市政府",amount=12002M,phone="138********3333"}, new () {name="李思",sex="女",addree="省政府宿舍大楼下的小破店旁边的垃圾桶前面的别墅",workplace="省教育局",amount=220000M,phone="158********3456"}, new () {name="王武",sex="男",addree="凤凰村",workplace="老破小公司",amount=-8765M,phone="199********6543"}, new () {name="",sex="",addree="",workplace="",amount=null,phone=""}, }; var index = 1; foreach (var (name, sex, addree, workplace, amount, phone) in contentData) { var dataRow = contentTable.AddRow(); dataRow.TopPadding = 6; dataRow.BottomPadding = 6; dataRow.Cells[0].AddParagraph().AddText($"{index++}"); dataRow.Cells[1].AddParagraph().AddText(name); dataRow.Cells[2].AddParagraph().AddText(sex); var addreeParVlaue = dataRow.Cells[3].AddParagraph(); addree?.ToList()?.ForEach(o => addreeParVlaue.AddChar(o));//自动换行 使用AddChar dataRow.Cells[4].AddParagraph().AddText(workplace); dataRow.Cells[5].AddParagraph().AddText(amount?.ToString() ?? ""); dataRow.Cells[6].AddParagraph().AddText(phone); } //空白 段落 分隔下间距 Paragraph paragraph = new();// 设置段落格式 paragraph.Format.SpaceBefore = "18pt"; // 设置空行高度为 12 磅 document.LastSection.Add(paragraph); // 将段落添加到文档中 //底栏提示 var tipsTable = _section.AddTable(); tipsTable.Style = _style.Name; tipsTable.TopPadding = 3; var tipsTableColumn = tipsTable.AddColumn(); tipsTableColumn.Width = _section.PageSetup.PageHeight - _section.PageSetup.LeftMargin * 2; var tipsParagraph = tipsTable.AddRow().Cells[0].AddParagraph(); tipsParagraph.Format.Font.Bold = true; tipsParagraph.Format.Font.Color = Colors.Red; //设置红色 tipsParagraph.AddText($"注:隐私信息是我们必须要注重的废话连篇的东西,切记切记,不可忽视,因小失大;"); #region 页码 _section.PageSetup.DifferentFirstPageHeaderFooter = false; var pager = _section.Footers.Primary.AddParagraph(); pager.AddText($"第\t"); pager.AddPageField(); pager.AddText($"\t页"); pager.Format.Alignment = ParagraphAlignment.Center; #endregion //生成PDF var pdfRenderer = new PdfDocumentRenderer(); using var memoryStream = new MemoryStream(); pdfRenderer.Document = document; pdfRenderer.RenderDocument(); pdfRenderer.PdfDocument.Save(memoryStream); var pdfDocument = PdfReader.Open(memoryStream); //为了跨平台 用的是SkiaSharp,大家自己转为System.Drawing实现即可,较为简单就不写了 #region 水印 using var watermarkMemoryStream = new MemoryStream(); var watermarkImgPath = "D:\\logo.png"; using var watermarkFile = System.IO.File.OpenRead(watermarkImgPath);// 读取文件 using var fileStream = new SKManagedStream(watermarkFile); using var bitmap = SKBitmap.Decode(fileStream); //设置半透明 var transparent = new SKColor(0, 0, 0, 0); for (int w = 0; w < bitmap.Width; w++) { for (int h = 0; h < bitmap.Height; h++) { SKColor c = bitmap.GetPixel(w, h); SKColor newC = c.Equals(transparent) ? c : new SKColor(c.Red, c.Green, c.Blue, 70); bitmap.SetPixel(w, h, newC); } } using var resized = bitmap.Resize(new SKImageInfo(200, 80), SKFilterQuality.High); using var newImage = SKImage.FromBitmap(resized); newImage.Encode(SKEncodedImageFormat.Png, 90).SaveTo(watermarkMemoryStream); // 保存文件 using var image = XImage.FromStream(watermarkMemoryStream); var xPoints = 6; var yPoints = 4; for (int i = 0; i <= xPoints; i++) { var xPoint = image.PointWidth * i * 1.2; var xTranslateTransform = xPoint + image.PointWidth / 2; for (int j = 0; j <= yPoints; j++) { var yPoint = image.PointHeight * j * 1.2; var yTranslateTransform = yPoint + image.PointHeight / 8; foreach (var page in pdfDocument.Pages) { using var xgr = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend); xgr.TranslateTransform(xTranslateTransform, yTranslateTransform); xgr.RotateTransform(-45); xgr.TranslateTransform(-xTranslateTransform, -yTranslateTransform); xgr.DrawImage(image, xPoint, yPoint, 200, 80); } } } #endregion pdfDocument.Save(memoryStream); var outputPdfFilePath = "D:\\pdfdemo.pdf"; //保存到本地 using var fs = new FileStream(outputPdfFilePath, FileMode.Create); byte[] bytes = new byte[memoryStream.Length]; memoryStream.Seek(0, SeekOrigin.Begin); memoryStream.Read(bytes, 0, (int)memoryStream.Length); fs.Write(bytes, 0, bytes.Length); Console.WriteLine("生成成功!");至此我们就制作好了一个简单的pdf,当然了这里没有加上文件信息那些,仅仅是生成内容罢了,有那些需要的可以自己根据文档站点看看如何设置。