// ===========================================================================
// ©2013-2017 WebSupergoo. All rights reserved.
//
// This source code is for use exclusively with the ABCpdf product with
// which it is distributed, under the terms of the license for that
// product. Details can be found at
//
// http://www.websupergoo.com/
//
// This copyright notice must not be deleted and must be reproduced alongside
// any sections of code extracted from this module.
// ===========================================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Printing;
using System.Security;
using WebSupergoo.ABCpdf14;
using WebSupergoo.ABCpdf14.Objects;
namespace ABCpdfPrinting
{
public sealed class NativePrint
{
public static void Print(Doc doc, string docName, PrinterSettings printerSettings) {
List<int> pages = GetPages(doc, printerSettings);
if (pages.Count <= 0)
return;
// save state
double oldDotsPerInch = doc.Rendering.DotsPerInch;
XRendering.ColorSpaceType oldColorSpace = doc.Rendering.ColorSpace;
int oldBitsPerChannel = doc.Rendering.BitsPerChannel;
bool oldAutoRotate = doc.Rendering.AutoRotate;
string oldRect = doc.Rect.String;
int oldPage = doc.Page;
// print job variables
IntPtr hdc = IntPtr.Zero;
IntPtr hPrinter = IntPtr.Zero;
Graphics graphics = null;
int printJobID = 0;
try {
IntPtr hDevMode = printerSettings.GetHdevmode();
hdc = SafePInvoke.CreateDC("WINSPOOL", printerSettings.PrinterName, SafePInvoke.GlobalLock(hDevMode)); // NB driver "winspool" is likely ignored
hDevMode = SafePInvoke.GlobalFree(hDevMode);
Debug.Assert(hDevMode == IntPtr.Zero);
if (hdc == IntPtr.Zero)
throw new Exception("Unable to create printer device context.", new InvalidPrinterException(printerSettings));
bool ok = SafePInvoke.OpenPrinter(printerSettings.PrinterName, ref hPrinter, IntPtr.Zero);
Debug.Assert((ok) || (hPrinter != IntPtr.Zero), "OpenPrinter succeeded but print handle is null.");
if ((!ok) || (hPrinter == IntPtr.Zero))
throw new Exception("Unable to open printer.", new InvalidPrinterException(printerSettings));
int logPixelsX = SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.LogPixelsX);
int logPixelsY = SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.LogPixelsY);
double pageWidth = (double)SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.PhysicalWidth) / logPixelsX * 100;
double pageHeight = (double)SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.PhysicalHeight) / logPixelsY * 100;
double dstWidth = (double)SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.HorzRes) / logPixelsX * 100;
double dstHeight = (double)SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.VertRes) / logPixelsY * 100;
int res = 72;
if (doc.Encryption.CanPrintHi) {
res = logPixelsX;
int colors = SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.NumColors);
if (colors == 1) {
int planes = SafePInvoke.GetDeviceCaps(hdc, SafePInvoke.DeviceCap.Planes);
if (planes > 1)
colors = 1 << Math.Min(6, planes);
}
if (colors > 2) // color is generally CMYK so to translate from dpi to ppi we divide by four
res /= 4;
if (res <= 0) // Invalid printer resolution - use the default value
res = 72;
}
doc.Rendering.DotsPerInch = res;
doc.Rendering.ColorSpace = XRendering ColorSpaceType.Rgb;
doc.Rendering.BitsPerChannel = 8;
doc.Rendering.AutoRotate = false;
SafePInvoke.DocInfo di = new SafePInvoke.DocInfo();
di.Size = Marshal.SizeOf(typeof(SafePInvoke.DocInfo));
di.DocName = docName;
printJobID = SafePInvoke.StartDoc(hdc, ref di);
if (printJobID <= 0)
throw new Exception("Unable to start document printing.", new InvalidPrinterException(printerSettings));
graphics = Graphics.FromHdc(hdc, hPrinter);
foreach (int page in pages) {
doc.PageNumber = page;
if (doc.Page == 0)
break; // should never happen
doc.Rect.SetRect(doc.CropBox);
double srcWidth = (doc.Rect.Width / 72) * 100;
double srcHeight = (doc.Rect.Height / 72) * 100;
double pageW = pageWidth, pageH = pageHeight;
double dstW = dstWidth, dstH = dstHeight;
const bool autoRotate = true;
int rotate = ((Page)doc.ObjectSoup[doc.Page]).Rotation % 360;
if (autoRotate && srcWidth != srcHeight && pageW != pageH && (srcWidth > srcHeight) != (pageW > pageH)) {
double temp = pageW;
pageW = pageH;
pageH = temp;
temp = dstW;
dstW = dstH;
dstH = temp;
if (rotate <= -180)
rotate += 360;
else if (rotate > 180)
rotate -= 360;
rotate = rotate > 0 ? 90 : -90; // default to -90
// Use -90 because we want the staple to be at a top corner
// of the page. Assuming a rotation of "rotate" (0 or 180 degrees)
// produces upright contents, a rotation of -90 or 90 degrees
// (respectively) produces outputs whose top is at the
// left edge of the portrait page. The staple at the top-left
// corner of the portrait page will be at the top-right corner of the
// contents.
}
else {
if (rotate != 180 && rotate != -180)
rotate = 0;
}
// if source bigger than destination then scale
if ((srcWidth > pageW) || (srcHeight > pageH)) {
double sx = pageW / srcWidth;
double sy = pageH / srcHeight;
double s = Math.Min(sx, sy);
srcWidth *= s;
srcHeight *= s;
}
// now center
double x = (dstW - srcWidth) / 2;
double y = (dstH - srcHeight) / 2;
Matrix oldTransform = null;
Matrix matrix = null;
Image image;
if (!doc.Encryption.CanPrintHi)
image = doc.Rendering.GetBitmap();
else {
byte[] data = doc.Rendering.GetData(".emf");
MemoryStream stream = new MemoryStream(data);
image = new Metafile(stream);
}
try {
if (SafePInvoke.StartPage(hdc) <= 0)
throw new Exception("Unable to start document page printing."); ;
switch (rotate) {
case 90:
matrix = new Matrix(0, 1, -1, 0, (float)(2 * y + srcHeight), 0);
break;
case -90:
matrix = new Matrix(0, -1, 1, 0, 0, (float)(2 * x + srcWidth));
break;
case 180:
case -180:
matrix = new Matrix(-1, 0, 0, -1,
(float)(2 * x + srcWidth), (float)(2 * y + srcHeight));
break;
}
if (matrix != null) {
oldTransform = graphics.Transform;
graphics.MultiplyTransform(matrix);
}
RectangleF rect = new RectangleF((float)x, (float)y, (float)srcWidth, (float)srcHeight);
#if !DISPLAY_PRINT_AREA
graphics.SetClip(rect);
#endif
graphics.DrawImage(image, rect);
#if DISPLAY_PRINT_AREA
using(Pen pen = new Pen(Color.Red)) {
g.DrawRectangle(pen,
rect.X, rect.Y, rect.Width, rect.Height);
}
#endif
if (oldTransform != null)
graphics.Transform = oldTransform;
graphics.Flush();
if (SafePInvoke.EndPage(hdc) <= 0)
throw new Exception("Unable to end document page printing."); ;
}
finally {
if (matrix != null)
matrix.Dispose();
if (oldTransform != null)
oldTransform.Dispose();
image.Dispose();
}
}
}
finally {
// disppose of print job variables
if (graphics != null)
graphics.Dispose();
if (printJobID > 0) {
int ok = SafePInvoke.EndDoc(hdc);
Debug.Assert(ok > 0);
}
if (hdc != IntPtr.Zero) {
bool ok = SafePInvoke.DeleteDC(hdc);
Debug.Assert(ok);
}
if (hPrinter != IntPtr.Zero) {
bool ok = SafePInvoke.ClosePrinter(hPrinter);
Debug.Assert(ok);
}
// restore state
doc.Rendering.DotsPerInch = oldDotsPerInch;
doc.Rendering.ColorSpace = oldColorSpace;
doc.Rendering.BitsPerChannel = oldBitsPerChannel;
doc.Rendering.AutoRotate = oldAutoRotate;
doc.Rect.String = oldRect;
doc.Page = oldPage;
}
}
private static List<int> GetPages(Doc doc, PrinterSettings printerSettings) {
List<int> pages = new List<int>();
switch (printerSettings.PrintRange) {
case PrintRange.AllPages:
for (int i = 1; i <= doc.PageCount; i++)
pages.Add(i);
break;
case PrintRange.CurrentPage:
pages.Add(doc.PageNumber);
break;
case PrintRange.Selection:
case PrintRange.SomePages:
for (int i = Math.Max(1, printerSettings.FromPage); i <= Math.Min(doc.PageCount, printerSettings.ToPage); i++)
pages.Add(i);
break;
}
return pages;
}
}
[SecuritySafeCritical]
internal static class SafePInvoke
{
public static IntPtr GlobalLock(IntPtr p) { return N.GlobalLock(p); }
public static IntPtr GlobalFree(IntPtr p) { return N.GlobalFree(p); }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DocInfo
{
public int Size;
[MarshalAs(UnmanagedType.LPWStr)]
public string DocName;
[MarshalAs(UnmanagedType.LPWStr)]
public string OutputFile;
[MarshalAs(UnmanagedType.LPWStr)]
public string DataType;
public uint Type;
}
public enum DeviceCap
{
HorzRes = 8,
VertRes = 10,
Planes = 14,
NumColors = 24,
LogPixelsX = 88,
LogPixelsY = 90,
PhysicalWidth = 110,
PhysicalHeight = 111,
PhysicalOffsetX = 112,
PhysicalOffsetY = 113,
ScalingFactorX = 114,
ScalingFactorY = 115,
}
public static IntPtr CreateDC(string driver, string device, IntPtr initData) { return N.CreateDC(driver, device, null, initData); }
public static bool DeleteDC(IntPtr hdc) { return N.DeleteDC(hdc); }
public static int GetDeviceCaps(IntPtr hdc, DeviceCap index) { return N.GetDeviceCaps(hdc, (int)index); }
public static int StartDoc(IntPtr hdc, [In] ref DocInfo di) { return N.StartDoc(hdc, ref di); }
public static int EndDoc(IntPtr hdc) { return N.EndDoc(hdc); }
public static int StartPage(IntPtr hdc) { return N.StartPage(hdc); }
public static int EndPage(IntPtr hdc) { return N.EndPage(hdc); }
public static bool OpenPrinter(string printerName, ref IntPtr hPrinter, IntPtr pDefault) { return N.OpenPrinter(printerName, out hPrinter, pDefault); }
public static bool ClosePrinter(IntPtr hPrinter) { return N.ClosePrinter(hPrinter); }
[SuppressUnmanagedCodeSecurity]
private static class N
{
[DllImport("kernel32.dll")]
public static extern IntPtr GlobalLock(IntPtr p);
[DllImport("kernel32.dll")]
public static extern IntPtr GlobalFree(IntPtr p);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode)] // PrinterSettings.GetHdevmode returns a Unicode DEVMODE
public static extern IntPtr CreateDC(string driver, string device, string output, IntPtr initData);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int index);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
public static extern int StartDoc(IntPtr hdc, [In] ref DocInfo di);
[DllImport("gdi32.dll")]
public static extern int EndDoc(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern int EndPage(IntPtr hdc);
[DllImport("winspool.drv", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool OpenPrinter(string printerName, out IntPtr hPrinter, IntPtr pDefault);
[DllImport("winspool.drv", SetLastError = true)]
public static extern bool ClosePrinter(IntPtr hPrinter);
}
}
}