内容纲要
引入三个库:
<ItemGroup>
<PackageReference Include="Pdfium.Net.SDK" Version="4.87.2704" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="Vanara.PInvoke.Printing" Version="3.4.17" />
</ItemGroup>
定义两个传递配置的模型类:
/// <summary>
/// 打印机配置
/// </summary>
public class PrintOption
{
/// <summary>
/// 打印机名称<br />
/// <para>如果为空,则使用默认打印机</para>
/// </summary>
public string? PrinterName { get; set; }
/// <summary>
/// 是否自动打印,即静默打印。
/// <para>默认使用静默打印。</para>
/// </summary>
public bool IsAutoPrint { get; set; } = true;
/// <summary>
/// 是否彩色打印
/// </summary>
public bool? Color { get; set; }
/// <summary>
/// 页边距
/// </summary>
public Margins? Margins { get; set; }
/// <summary>
/// 打印纸张大小名称。
/// <para><see cref="PaperName"/> 跟 <see cref="CustomSize"/> 二选一,<see cref="CustomSize"/> 优先级高。</para>
/// </summary>
public string? PaperName { get; set; }
/// <summary>
/// 自定义纸张大小。
/// <para><see cref="PaperName"/> 跟 <see cref="CustomSize"/> 二选一,<see cref="CustomSize"/> 优先级高</para>
/// </summary>
public Size? CustomSize { get; set; }
/// <summary>
/// 打印方向设置为横向。
/// </summary>
public bool? Landscape { get; set; }
/// <summary>
/// 要打印多少份,默认为 1 份。
/// </summary>
public short Count { get; set; } = 1;
}
public class PrintImageOption : PrintOption
{
/// <summary>
/// 用于指定在图像缩放或变换时使用的插值算法。
/// <para>Mode 和 Dpi 不冲突</para>
/// </summary>
/// <remarks>
/// <see cref="InterpolationMode.Default"/> 使用默认的插值模式。通常为Bilinear。<br />
/// <see cref="InterpolationMode.Low"/>: 低质量的插值模式,用于快速处理较大的图像。<br />
/// <see cref="InterpolationMode.High"/>: 高质量的插值模式,用于确保在图像缩放或变换时获得更好的细节和平滑度。<br />
/// <see cref="InterpolationMode.Bilinear"/>: 双线性插值模式,以平均周围4个像素的颜色来计算新像素的颜色值。<br />
/// <see cref="InterpolationMode.Bicubic"/>: 双三次插值模式,以周围16个像素的颜色加权平均来计算新像素的颜色值。<br />
/// <see cref="InterpolationMode.NearestNeighbor"/>: 最近邻插值模式,使用与目标像素最接近的原始像素的颜色值。<br />
/// <see cref="InterpolationMode.HighQualityBilinear"/>: 高质量双线性插值模式,类似于Bilinear,但具有更好的质量。<br />
/// <see cref="InterpolationMode.HighQualityBicubic"/>: 高质量双三次插值模式,类似于Bicubic,但具有更好的质量。<br />
/// </remarks>
public InterpolationMode? Mode { get; set; }
/// <summary>
/// 分辨率,默认打印机 dpi 96,dpi 影响打印机打印的物理成像。
/// <para>Mode 和 Dpi 不冲突</para>
/// </summary>
public int? Dpi { get; set; } = 300;
/// <summary>
/// 自动缩放,如果图片过大,则会自动缩小;如果图片过小,则会自动放大。
/// </summary>
public bool IsAutoScale { get; set; } = true;
}
定义从 PrintOption 配置整理到打印机设置的函数。
private static void BuildOption(PrintDocument pd, PrintOption printOption)
{
if (printOption == null) return;
// 设置打印机名称
if (!string.IsNullOrEmpty(printOption.PrinterName))
pd.PrinterSettings.PrinterName = printOption.PrinterName;
// 是否静默打印
if (printOption.IsAutoPrint == true)
pd.PrintController = new StandardPrintController();
// 打印份数
pd.PrinterSettings.Copies = printOption.Count;
// 是否彩色打印
if (printOption.Color != null && pd.PrinterSettings.SupportsColor)
pd.PrinterSettings.DefaultPageSettings.Color = printOption.Color.GetValueOrDefault();
// 是否横向打印
if (printOption.Landscape != null)
pd.PrinterSettings.DefaultPageSettings.Landscape = printOption.Landscape.GetValueOrDefault();
// 设置页边距
if (printOption.Margins != null)
pd.PrinterSettings.DefaultPageSettings.Margins = printOption.Margins;
// 设置纸张大小
if (printOption.CustomSize != null)
{
var parper = new PaperSize("Custom", printOption.CustomSize.Value.Width, printOption.CustomSize.Value.Height)
{
PaperName = "自定义",
RawKind = (int)PaperKind.Custom
};
pd.DefaultPageSettings.PaperSize = parper;
}
else if (printOption.PaperName != null)
{
for (int i = 0; i < pd.PrinterSettings.PaperSizes.Count; i++)
{
if (pd.PrinterSettings.PaperSizes[i].PaperName == printOption.PaperName)
{
pd.PrinterSettings.DefaultPageSettings.PaperSize = pd.PrinterSettings.PaperSizes[i];
break;
}
}
}
}
打印文字:
public static void PrintText(string[] text, PrintOption? printOption)
{
if (printOption == null) printOption = new PrintOption();
PrintDocument pd = new PrintDocument();
BuildOption(pd, printOption);
pd.PrintPage += PrintTxt;
pd.Print();
void PrintTxt(object sender, PrintPageEventArgs ev)
{
var printFont = new System.Drawing.Font(System.Drawing.SystemFonts.DefaultFont.Name, System.Drawing.SystemFonts.DefaultFont.Size);
float linesPerPage = 0;
float yPos = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = string.Empty;
// 计算高度,一页能够打印多少行
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics!);
int index = 0;
// 打印每一行
while (count < linesPerPage && index < text.Length)
{
line = text[index];
yPos = topMargin + (count * printFont.GetHeight(ev.Graphics!));
ev.Graphics!.DrawString(line, printFont, Brushes.Black, leftMargin, yPos, new StringFormat());
count++;
index++;
}
if (string.IsNullOrEmpty(line))
ev.HasMorePages = true;
else
ev.HasMorePages = false;
}
}
打印图片:
public static void PrintImage(Stream[] streams, PrintImageOption? printOption)
{
if (printOption == null) printOption = new PrintImageOption();
PrintDocument pd = new PrintDocument();
BuildOption(pd, printOption);
pd.PrintPage += PrintImage;
pd.Print();
void PrintImage(object sender, PrintPageEventArgs e)
{
if (streams.Length > 1) e.HasMorePages = true;
foreach (var stream in streams)
{
if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream);
using Graphics graphics = e.Graphics!;
// 高质量图片
if (printOption.Mode != null)
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
if (printOption.Dpi != null)
{
pd.DefaultPageSettings.PrinterResolution.X = printOption.Dpi.GetValueOrDefault();
pd.DefaultPageSettings.PrinterResolution.Y = printOption.Dpi.GetValueOrDefault();
}
if (printOption.IsAutoScale)
{
var size = GetSize(e.PageBounds, image);
graphics.DrawImage(image, e.MarginBounds.X, e.MarginBounds.Y, size.Width, size.Height);
}
else
{
// 不支持过大的图片跨页
graphics.DrawImage(image, e.MarginBounds.X, e.MarginBounds.Y);
}
}
}
}
private static Size GetSize(Rectangle page, Image image)
{
double imageWidth = image.Width;
double imageHeight = image.Height;
// ClientSize 获取到的才是真正可以显示的区域,去掉了边框,Size 是纸张全部区域
double pageWidth = page.Width;
double pageHeight = page.Height;
// 最终计算结果
double width = image.Width;
double height = image.Height;
// 图片过长时
if (imageWidth >= pageWidth)
{
double ratio = imageWidth / pageWidth;
width = pageWidth;
height = imageHeight / ratio;
}
// 图片小于页面,则自动放大
else if (imageWidth < pageWidth)
{
double ratio = pageWidth / imageWidth;
width = pageWidth;
height = imageHeight * ratio;
}
return new Size(width: (int)width, height: (int)height);
}
打印 pdf:
public static void PrintPdf(Stream stream, PrintOption? printOption)
{
if (printOption == null) printOption = new PrintOption();
PdfPrintDocument doc = new PdfPrintDocument();
doc.Document = PdfDocument.Load(stream);
BuildOption(doc, printOption);
doc.Print();
}
由于生成的文件带有一些动态库,导致体积太大,不需要的情况下可以使用脚本自动删除。
<Target Name="DeletePdfiumFile" AfterTargets="Publish" Condition="'$(PublishDir)' != ''">
<Exec WorkingDirectory="./" Command="echo "DEL $(PublishDir)libpdfium.dylib"" />
<Exec WorkingDirectory="./" Command="DEL "$(PublishDir)libpdfium.dylib"" ContinueOnError="true" />
</Target>
写到主项目的 .csproj 文件中。
如果只需要 x64 不需要 x86,那么还可以减小体积。
<Target Name="DeletePdfiumFile" AfterTargets="Publish" Condition="'$(PublishDir)' != ''">
<Exec WorkingDirectory="./" Command="echo "删除pdfium文件"" />
<Exec WorkingDirectory="./" Command="echo "DEL $(PublishDir)x86\pdfium.dll"" />
<Exec WorkingDirectory="./" Command="DEL "$(PublishDir)x86\pdfium.dll"" ContinueOnError="true" />
<Exec WorkingDirectory="./" Command="echo "DEL $(PublishDir)libpdfium.dylib"" />
<Exec WorkingDirectory="./" Command="DEL "$(PublishDir)libpdfium.dylib"" ContinueOnError="true" />
</Target>
文章评论
网友需要:
支持双面打印。
支持挂后台,通过 http 调用,可考虑使用 HtpListener 做一个 AOP 服务。