Spire.pdf and other libraries for printing PDFs require payment, so this article demonstrates printing using Google's open-source PDFium project, which is open-source and cross-platform.
bblanchon.PDFium.Win32 is a library that wraps PDFium using C#.
Introducing three libraries:
<ItemGroup>
<PackageReference Include="bblanchon.PDFium.Win32" Version="122.0.6259" />
<PackageReference Include="PdfiumPrinter" Version="1.4.1" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="Vanara.PInvoke.Printing" Version="3.4.17" />
</ItemGroup>
Theoretically, if you use bblanchon.PDFium.Win32, it will automatically carry the PDFium dynamic library. For example:
bblanchon.PDFium also has wrapper libraries for other systems that automatically include the corresponding PDFium dynamic libraries.
Normally, after compiling the project, these dynamic libraries will automatically be included in the directory. However, in some cases, using the Release configuration to publish the project will not include these dynamic libraries. For example, if the program is set to the framework net8.0-windows
or the system and CPU architecture are specified during compilation, such as dotnet publish -c Release -r win-x64
.
In this case, the compiled program will not include the dynamic libraries. You can manually download the compiled dynamic libraries from this repository: https://github.com/bblanchon/pdfium-binaries
And then manually place them in the project directory:
├─runtimes
│ ├─linux-arm
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-arm64
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-musl-arm64
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-musl-x64
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-musl-x86
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-x64
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─linux-x86
│ │ └─native
│ │ libpdfium.so
│ │
│ ├─win-arm64
│ │ └─native
│ │ pdfium.dll
│ │
│ ├─win-x64
│ │ └─native
│ │ pdfium.dll
│ │
│ └─win-x86
│ └─native
│ pdfium.dll
You do not need to use all of the dynamic libraries; for example, if you only need to use it under win x64, you only need to copy this file to the project:
Now let's discuss the code.
Define two model classes for configuration passing:
/// <summary>
/// Printer Configuration
/// </summary>
public class PrintOption
{
/// <summary>
/// Printer Name<br />
/// <para>If empty, the default printer will be used</para>
/// </summary>
public string? PrinterName { get; set; }
/// <summary>
/// Whether to print automatically, i.e., silently printing.
/// <para>Defaults to silent printing.</para>
/// </summary>
public bool IsAutoPrint { get; set; } = true;
/// <summary>
/// Whether to print in color
/// </summary>
public bool? Color { get; set; }
/// <summary>
/// Page Margins
/// </summary>
public Margins? Margins { get; set; }
/// <summary>
/// Name of print paper size.
/// <para><see cref="PaperName"/> and <see cref="CustomSize"/> are mutually exclusive, <see cref="CustomSize"/> takes priority.</para>
/// </summary>
public string? PaperName { get; set; }
/// <summary>
/// Custom paper size.
/// <para><see cref="PaperName"/> and <see cref="CustomSize"/> are mutually exclusive, <see cref="CustomSize"/> takes priority.</para>
/// </summary>
public Size? CustomSize { get; set; }
/// <summary>
/// Set print orientation to landscape.
/// </summary>
public bool? Landscape { get; set; }
/// <summary>
/// The number of copies to print, default is 1.
/// </summary>
public short Count { get; set; } = 1;
}
public class PrintImageOption : PrintOption
{
/// <summary>
/// Used to specify the interpolation algorithm to use when scaling or transforming the image.
/// <para>Mode and Dpi do not conflict</para>
/// </summary>
/// <remarks>
/// <see cref="InterpolationMode.Default"/> uses the default interpolation mode, usually Bilinear.<br />
/// <see cref="InterpolationMode.Low"/>: Low-quality interpolation mode for fast processing of large images.<br />
/// <see cref="InterpolationMode.High"/>: High-quality interpolation mode to ensure better detail and smoothness during image scaling or transformation.<br />
/// <see cref="InterpolationMode.Bilinear"/>: Bilinear interpolation mode calculates the color value of new pixels by averaging the colors of four surrounding pixels.<br />
/// <see cref="InterpolationMode.Bicubic"/>: Bicubic interpolation mode calculates the color value of new pixels by weighted averaging the colors of 16 surrounding pixels.<br />
/// <see cref="InterpolationMode.NearestNeighbor"/>: Nearest neighbor interpolation mode uses the color value of the original pixel closest to the target pixel.<br />
/// <see cref="InterpolationMode.HighQualityBilinear"/>: High-quality bilinear interpolation mode, similar to Bilinear but with better quality.<br />
/// <see cref="InterpolationMode.HighQualityBicubic"/>: High-quality bicubic interpolation mode, similar to Bicubic but with better quality.<br />
/// </remarks>
public InterpolationMode? Mode { get; set; }
/// <summary>
/// Resolution, default printer dpi 96. dpi affects the physical imaging of the printer.
/// <para>Mode and Dpi do not conflict</para>
/// </summary>
public int? Dpi { get; set; } = 300;
/// <summary>
/// Automatic scaling; if the image is too large, it will be reduced; if the image is too small, it will be enlarged automatically.
/// </summary>
public bool IsAutoScale { get; set; } = true;
}
Define a function to organize the PrintOption configuration into printer settings.
private static void BuildOption(PrintDocument pd, PrintOption printOption)
{
if (printOption == null) return;
// Set printer name
if (!string.IsNullOrEmpty(printOption.PrinterName))
{
pd.PrinterSettings.PrinterName = printOption.PrinterName;
pd.DefaultPageSettings.PrinterSettings.PrinterName = printOption.PrinterName;
}
// Silent printing
if (printOption.IsAutoPrint)
{
pd.PrintController = new StandardPrintController();
}
// Number of copies
pd.PrinterSettings.Copies = printOption.Count;
// Color printing
if (printOption.Color != null && pd.PrinterSettings.SupportsColor)
{
pd.PrinterSettings.DefaultPageSettings.Color = printOption.Color.GetValueOrDefault();
pd.DefaultPageSettings.Color = printOption.Color.GetValueOrDefault();
}
// Landscape printing
if (printOption.Landscape != null)
{
pd.PrinterSettings.DefaultPageSettings.Landscape = printOption.Landscape.GetValueOrDefault();
pd.DefaultPageSettings.Landscape = printOption.Landscape.GetValueOrDefault();
}
// Set margins
if (printOption.Margins != null)
{
pd.PrinterSettings.DefaultPageSettings.Margins = printOption.Margins;
pd.DefaultPageSettings.Margins = printOption.Margins;
}
// Set paper size
if (printOption.CustomSize != null)
{
var paper = new PaperSize("custom", printOption.CustomSize.Value.Width, printOption.CustomSize.Value.Height)
{
PaperName = "custom",
RawKind = (int)PaperKind.Custom
};
pd.PrinterSettings.DefaultPageSettings.PaperSize = paper;
pd.DefaultPageSettings.PaperSize = paper;
}
else if (printOption.PaperName != null)
{
for (int i = 0; i < pd.PrinterSettings.PaperSizes.Count; i++)
{
if (pd.PrinterSettings.PaperSizes[i].PaperName == printOption.PaperName)
{
var paper = pd.PrinterSettings.PaperSizes[i];
pd.PrinterSettings.DefaultPageSettings.PaperSize = new PaperSize(paper.PaperName, paper.Width, paper.Height);
pd.DefaultPageSettings.PaperSize = new PaperSize(paper.PaperName, paper.Width, paper.Height);
break;
}
}
}
}
Print text:
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;
// Calculate height, how many lines can be printed on one page
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics!);
int index = 0;
// Print each line
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;
}
}
Print images:
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!;
// High-quality image
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
{
// Doesn't support oversized images spanning multiple pages
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 gets the actual display area, removing borders; Size is the entire paper area
double pageWidth = page.Width;
double pageHeight = page.Height;
// Final calculation result
double width = image.Width;
double height = image.Height;
// Image too long
if (imageWidth >= pageWidth)
{
double ratio = imageWidth / pageWidth;
width = pageWidth;
height = imageHeight / ratio;
}
// Image smaller than the page, automatically enlarge
else if (imageWidth < pageWidth)
{
double ratio = pageWidth / imageWidth;
width = pageWidth;
height = imageHeight * ratio;
}
return new Size(width: (int)width, height: (int)height);
}
Print PDF:
public static void PrintPdf(Stream stream, PrintOption printOption)
{
if (printOption == null) printOption = new PrintOption();
PdfDocument doc = PdfDocument.Load(stream);
var printDocument = doc.CreatePrintDocument();
BuildOption(printDocument, printOption);
printDocument.Print();
}
Since the generated files come with some dynamic libraries that make them too large, you can use a script to automatically delete them when not needed.
<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>
Write this in the main project's .csproj file.
If only x64 is needed without x86, then the size can be further reduced.
<Target Name="DeletePdfiumFile" AfterTargets="Publish" Condition="'$(PublishDir)' != ''">
<Exec WorkingDirectory="./" Command="echo "Deleting pdfium file"" />
<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>
文章评论