SkiaSharp跨平台绘图研究4-在PDF上绘图
在很多应用场景中,软件系统是要有打印报告功能的,要知道SkiaSharp还能够在PDF上绘图,都不知道该怎么夸它了,一套绘图代码,可以在PC端,移动端,Linux云服务器到处用,还能导出PDF,打印到A4纸,这跨平台能力也太强大了。
继续沿用之前的Asp.Net Core项目,在网页上增加一个导出PDF报告的功能,然后创建一个PDF文件,可以下载,也可以直接打印。
在PDF上绘图
在PDF上绘图的代码跟WPF项目基本一样。
using SkiaSharp; namespace WebMvcDemo { /// <summary> /// 创建PDF绘图文件 /// </summary> public class SkiaPdfMaker { /// <summary> /// 每毫米对应多少打印点数 = 每英寸打印多少点数 / 每英寸多少毫米 = 2.83464567 /// Dpi打印分辨率,Dot Per Inch,每英寸打印点数,默认72 /// 1英寸=25.4毫米 /// </summary> public const float DotPermm = SKDocument.DefaultRasterDpi / 25.4f; /// <summary> /// 导出PDF文件 /// </summary> /// <returns></returns> public byte[] ExportToPdf() { Console.WriteLine($"{DateTime.Now}, 开始导出PDF"); using MemoryStream stream = new MemoryStream(); var pdfMetadata = new SKDocumentPdfMetadata { Author = "SkiaPdfMaker", }; //初始化一个PdfDocument类实例 using SKDocument pdfDocument = SKDocument.CreatePdf(stream, pdfMetadata); //新添加一页纸张,创建纸张大小的画布,A4尺寸 = 210 X 297 mm float paperWidth = DotPermm * 210; float paperHeight = DotPermm * 297; var canvas = pdfDocument.BeginPage(paperWidth, paperHeight); using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, Typeface = SkiaChinaFont.ChinaFont, TextSize = 24 }; string msg = $"{DateTimeOffset.Now:T}, 还有1万行Skia绘图代码..."; canvas.DrawText(msg, 0, 30, paint); using var linePaint = new SKPaint() { Color = (DateTimeOffset.Now.Second % 4 <= 1) ? SKColors.Red : SKColors.Green, Style = SKPaintStyle.Stroke,//不填充 StrokeWidth = 3, }; canvas.DrawRect(10, 50, paperWidth - 20, paperHeight - 60, linePaint); msg += $", linePaint.Color={linePaint.Color}, skContainer.CanvasSize={paperWidth} x {paperHeight}"; Console.WriteLine(msg); //结束一页内容 pdfDocument.EndPage(); //结束文档内容 pdfDocument.Close(); byte[] ary = stream.ToArray(); return ary; } } }
在页面下载PDF
控制器Action就是返回一个PDF文件类型
/// <summary> /// 导出Skia绘图PDF文件 /// </summary> /// <returns></returns> public IActionResult ExportSkiaPdf() { //导出PDF文件 var skiaPdfMaker = new SkiaPdfMaker(); var ary = skiaPdfMaker.ExportToPdf(); string httpHeader = $"attachment; filename={DateTime.Now:yyyyMMdd-HHmmss}.pdf"; HttpContext.Response.Headers.Add("content-disposition", httpHeader); return File(ary, "application/pdf"); }
主页增加一个超级链接按钮
<a class="btn btn-info m-1" href="/Home/ExportSkiaPdf" target="_blank">导出PDF</a>
运行网站,在浏览器中点击【导出PDF】超级链接,就会下载创建的PDF文件。
发布到腾讯云CentOS操作系统容器里运行也一样的结果。
该方案存在的问题
这个如此简单的PDF文件居然有1.7M字节,我不知道该说什么好了。为什么会这样,我也不知道。我曾经对比过其他PDF生成方案创建的PDF文件,比SkiaSharp小多了!SkiaSharp生成的PDF如果不使用中文字库,仍然有500k字节,貌似SkiaSharp把整个中文字库全部嵌入到PDF里边去了。
使用老牌iTextSharp创建PDF方案做个对比,NuGet安装iTextSharp.LGPLv2.Core,这个是iText免费版的绝版。
<PackageReference Include="iTextSharp.LGPLv2.Core" Version="1.7.5" />
使用iTextSharp创建同样的PDF文件,iText的坐标系是倒置的,绘图代码跟SkiaSharp不能共用。
using iTextSharp.text; using iTextSharp.text.pdf; namespace WebMvcDemo { /// <summary> /// 创建PDF绘图文件 /// </summary> public class ItextPdfMaker { /// <summary> /// 导出PDF文件 /// </summary> /// <returns></returns> public byte[] ExportToPdf() { Console.WriteLine($"{DateTime.Now}, iText开始导出PDF"); using MemoryStream stream = new MemoryStream(); //初始化一个Document类实例,创建纸张大小的画布,A4尺寸 = 210 X 297 mm,= 595 x 842,设置页边距全0,整个页面全部作为画布 var document = new Document(PageSize.A4, 0, 0, 0, 0); //必须先获取PdfWriter,然后document.Open,如果交换顺序,报错Document not open PdfWriter writer = PdfWriter.GetInstance(document, stream); document.AddAuthor("ItextPdfMaker"); document.AddCreator("ItextPdfMaker"); document.Open(); var canvas = writer.DirectContent; var textfont = new Font(ItextChinaFont.ChinaFont) { Color = BaseColor.Black, Size = 24 }; string msg = $"{DateTimeOffset.Now:T}, 还有1万行iText绘图代码..."; ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(msg, textfont), 0, canvas.PdfDocument.Top - 30, 0); var lineColor = (DateTimeOffset.Now.Second % 4 <= 1) ? BaseColor.Red : BaseColor.Green; canvas.SetColorStroke(lineColor); canvas.SetLineWidth(3); //左边-底部-右边-顶部 canvas.Rectangle(10, canvas.PdfDocument.Top - (PageSize.A4.Height - 60), PageSize.A4.Width - 20, canvas.PdfDocument.Top - 120); canvas.Stroke(); msg += $", lineColor={lineColor.ToArgb():X2}, CanvasSize={PageSize.A4}"; Console.WriteLine(msg); //结束一页内容 //document.NewPage(); //结束文档内容 document.Close(); byte[] ary = stream.ToArray(); return ary; } } }
创建中文字体类
using iTextSharp.text.pdf; namespace WebMvcDemo { /// <summary> /// iText中文字体 /// </summary> public static class ItextChinaFont { public static BaseFont ChinaFont { get; private set; } static ItextChinaFont() { //加载字体资源文件方案,需要把字体文件复制运行目录下,设置文件属性为如果较新则复制 string fontPath = Path.Combine(AppContext.BaseDirectory, "DroidSansFallback.ttf"); ChinaFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); } } }
iTextSharp创建的PDF才40k字节,部署到腾讯云CentOS容器也没有什么依赖库。所以最终我没有把SkiaSharp方案用于产品中,仍然使用了老掉牙的iTextSharp免费版本,真是非常遗憾。希望将来能找到问题原因,重新把SkiaSharp创建PDF用起来。
DEMO源代码参见:https://gitee.com/woodsun/skia-sharp-demo