diff --git a/BonNotiz.slnx b/BonNotiz.slnx new file mode 100644 index 0000000..1bd1037 --- /dev/null +++ b/BonNotiz.slnx @@ -0,0 +1,3 @@ + + + diff --git a/BonNotiz/BonNotiz.csproj b/BonNotiz/BonNotiz.csproj new file mode 100644 index 0000000..b73b207 --- /dev/null +++ b/BonNotiz/BonNotiz.csproj @@ -0,0 +1,15 @@ + + + + WinExe + true + true + net10.0-windows + enable + true + disable + BonNotiz.Program + none + + + \ No newline at end of file diff --git a/BonNotiz/EscPosPrinter.cs b/BonNotiz/EscPosPrinter.cs new file mode 100644 index 0000000..0178234 --- /dev/null +++ b/BonNotiz/EscPosPrinter.cs @@ -0,0 +1,322 @@ +using System; +using System.Text; +using System.IO; +using System.Drawing; + +namespace KohaCompanion.Shared +{ + /// + /// Stellt einen zustandsbehafteten ESC/POS-Drucker dar. + /// + /// + /// Diese Klasse bietet Methoden, die die ESC/POS-Befehle kapseln. + /// Zum Schreiben eines Strings können Sie die Write(String)-Methode verwenden. + /// + /// Bei jedem Aufruf einer Methode werden dabei die Kommandos in einen internen + /// Puffer geschrieben, der am Schluss mit GetCurrentPuffer() geholt werden + /// kann, um das Bytearray an den Drucker zu senden. + /// + /// Quelle: https://www.vbarchiv.net/tipps/tipp_2375-kassenbon-drucker-mit-vbnet-oder-c-per-esc-pos-befehle-ansprechen.html + /// Benötigt nuget-Paket: System.Text.Encoding.CodePages + /// Originalautor: Konstantin Preißer + /// Originallizenz: MIT + /// Angepaßt an C# von Anna Christina Naß + /// + + public class EscPosPrinter + { + public enum Alignment + { + Left = 0, + Center, + Right + } + + public class PrinterCodepage + { + public static readonly PrinterCodepage Cp437 = new PrinterCodepage(0, CodePagesEncodingProvider.Instance.GetEncoding(437)); + public static readonly PrinterCodepage Cp852 = new PrinterCodepage(18, CodePagesEncodingProvider.Instance.GetEncoding(852)); + public static readonly PrinterCodepage Cp866 = new PrinterCodepage(17, CodePagesEncodingProvider.Instance.GetEncoding(866)); + public static readonly PrinterCodepage Cp1252 = new PrinterCodepage(16, CodePagesEncodingProvider.Instance.GetEncoding(1252)); + private readonly byte _pageNumber; + private readonly Encoding? _encoding; + + private PrinterCodepage(byte pageNumber, Encoding? encoding) + { + this._pageNumber = pageNumber; + this._encoding = encoding; + } + + public byte PageNumber { get { return _pageNumber; } } + + public Encoding Encoding { get { return _encoding; } } + } + + // Steuercodes für neue Zeile + private static readonly byte[] newLineBytes = { 0xA, 0xD }; + + // Interner Buffer + private MemoryStream memstr = new(); + + // Zustand des Druckers + private PrinterCodepage printerEnc = PrinterCodepage.Cp437; + private bool underline = false; + private bool underlineDouble = false; + private bool emphasized = false; + //private bool redColor = false; * unused + private int currentFontX = 0; + private int currentFontY = 0; + //private bool panelButtonsEnabled = true; * unused + private Alignment _alignment = Alignment.Left; + + /// + /// Erstellt eine neue Instanz. Dadurch wird automatisch ein + /// "Initialize Printer"-Command (ESC '@') in den Puffer geschrieben, der + /// den Drucker auf die Standardwerte zurücksetzt, damit der Zustand des + /// Druckers dem Zustand dieses Objektes entspricht. + /// + public EscPosPrinter() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // Initialize Printer Command senden + byte[] buf = new byte[] { 0x1B, 0x40 }; + memstr.Write(buf, 0, buf.Length); + } + + /// + /// Schreibt den angegebenen String. + /// + /// + /// Bei diesem Vorgang werden automatisch + /// Bytes, die in den ASCII-Steuerzeichenbereich fallen (0x00-0x1F sowie 0x7F), + /// durch Leerzeichen ersetzt. So wird verhindert, dass Strings aus fremden + /// Quellen den Zustand des Druckers verändern können. + /// + /// Dies betrifft auch Zeilenumbrüche. Um einen Zeilenumbruch zu schreiben, + /// verwenden Sie WriteLine() oder WriteLine(String). + /// + /// Beachten Sie, dass bei den meisten Bondruckern eine Zeile erst gedruckt + /// wird, sobald diese mit einem Zeilenumbruch abgeschlossen wird (auch wenn + /// die Zeile voll ist, also so viele Zeichen belegt, wie der Drucker in eine + /// Zeile drucken kann). + /// + /// Beachten Sie, dass ESC/POS-Drucker standardmäßig die DOS-Codepage 437 + /// verwenden, die u. a.Rahmenzeichen, einige griechische Zeichen und + /// mathematischen Symbole enthält. In den meisten Fällen empfiehlt sich für + /// Textdruck (v.a. europäische Texte) aber ein Umschalten auf die Windows- + /// Codepage 1252 (Methode SetCodepage(PrinterCodepage)), da hier mehr + /// unterschiedliche Buchstaben (z.B. "ß"), typographische Satzzeichen und + /// auch das €-Zeichen enthalten sind. + /// + /// Für Texte in anderen europäischen Sprachen empfehlen sich noch die + /// Codepage 852, die Zeichen für mitteleuropäische Sprachen enthält, sowie + /// die Codepage 866 mit kyrillischen Zeichen. + /// + /// der String, der ausgegeben werden soll + public void Write(string str) + { + byte[] buf = printerEnc.Encoding.GetBytes(str); + + // Aufpassen, dass kein ASCII-Steuerzeichen vorkommt. + // For i As Integer = 0 To buf.Length - 1 + // Dim c As Byte = buf(i) + // If Not (c >= &H20 AndAlso c <> &H7F) Then + // buf(i) = CByte(AscW(" "c)) + // End If + // Next + + memstr.Write(buf, 0, buf.Length); + } + + /// + /// Schreibt einen Zeilenumbruch (LF) zum Abschließen der aktuellen Zeile. + /// + public void WriteLine() + { + memstr.Write(newLineBytes, 0, newLineBytes.Length); + } + + /// + /// Schreibt den angegebenen String und danach einen Zeilenumbruch (LF). + /// Siehe Dokumentation zur Write(String)-Methode. + /// + /// der String, der ausgegeben werden soll + public void WriteLine(string str) + { + Write(str); + WriteLine(); + } + + /// + /// ESC '*' 33 nL nH d1…dk: + /// Druckt das angegebene Bild in Schwarz/Weiß. Die Höhe muss hierbei ein + /// Vielfaches von 24 sein, da das Bild wie Textzeilen ohne Zeilenabstand + /// gedruckt wird und eine Zeile (Font A) aus 24 Pixeln besteht. Die Breite + /// sollte der Druckerauflösung entsprechen (z.B. 384 Pixel beim PRP-058- + /// Drucker mit 32 Zeichen pro Zeile). + /// + /// + /// Die Druckauflösung hängt vom Druckermodell ab und beträgt normalerweise + /// 203,2 dpi (z.B. PRP-058) oder 180 dpi. + /// + /// Die zu druckende Bitmap + public void PrintImage(Bitmap bmp) + { + if (bmp.Height % 24 != 0) + throw new ArgumentException("Die Bildhöhe muss ein Vielfaches von 24 sein."); + if (bmp.Width > 0x3FF) + throw new ArgumentException("Die Bildbreite darf nicht größer als 1023 sein."); + + byte[] buf; + + byte[] zeilenAnfang = new byte[] { 0x1B, 0x2A, 33, System.Convert.ToByte(bmp.Width & 0xFF), System.Convert.ToByte((bmp.Width >> 8) & 0xFF) }; + byte[] bildBuf = new byte[bmp.Width * 3 - 1 + 1]; + + for (int i = 0; i <= bmp.Height / 24 - 1; i++) + { + // Durch die einzelnen Zeilen gehen + buf = zeilenAnfang; + memstr.Write(buf, 0, buf.Length); + + buf = bildBuf; + Array.Clear(buf, 0, buf.Length); + + for (int x = 0; x <= bmp.Width - 1; x++) + { + for (int y = 0; y <= 23; y++) + { + int byteIdx = y / 8 + x * 3; + Color c = bmp.GetPixel(x, i * 24 + y); + bool bit = c.GetBrightness() < 0.5F; + if (bit) + buf[byteIdx] = (byte)(buf[byteIdx] | System.Convert.ToByte(1 << (7 - (y % 8)))); + } + } + + memstr.Write(buf, 0, buf.Length); + + if (i != bmp.Height / 24 - 1) + // ESC J n für Paper Feed (n (hier 0) müsste eigentlich 48 sein für eine + // Zeile, aber geht mit kleineren Werten auch) + // Nicht LF verwenden, weil bei LF der normale Zeilenabstand verwendet + // wird! + buf = new byte[] { 0x1B, 0x4A, 0 }; + else + // Am Schluss normaler Zeilenabstand nach unten. + buf = new byte[] { 0xA }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// ESC '-' n: + /// Aktiviert oder deaktiviert den Underline-Modus. + /// + /// true, wenn der Underline-Modus aktiviert werden soll, + /// sonst false + /// true, wenn die Linie 2 Pixel statt 1 Pixel + /// dick sein soll + public void SetUnderline(bool value, bool doubleThickness) + { + if (value != underline || (value & doubleThickness != underlineDouble)) + { + underline = value; + underlineDouble = doubleThickness; + byte[] buf = new byte[] { 0x1B, 0x2D, System.Convert.ToByte(value ? doubleThickness ? 2 : 1 : 0) }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// ESC 'E' n: + /// Aktiviert oder deaktiviert den Fettschrift-Modus. + /// + /// + public void SetEmphasized(bool value) + { + if (value != emphasized) + { + emphasized = value; + byte[] buf = new byte[] { 0x1B, 0x45, System.Convert.ToByte(value ? 1 : 0) }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// GS '!' n: + /// Ändert die Font-Größe (Parameter jeweils von 0-7). + /// Standardwerte: x = 0, y = 0 + /// + /// Horizontale Größe + /// Vertikale Größe + public void SetFontSize(int x, int y) + { + if ((x < 0 || x > 7) || (y < 0 || y > 7)) + throw new ArgumentException("x und y müssen im Bereich 0-7 liegen."); + + if (x != currentFontX || y != currentFontY) + { + currentFontX = x; + currentFontY = y; + + byte[] buf = new byte[] { 0x1D, 0x21, System.Convert.ToByte((x << 4) | y) }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// ESC 't' n: + /// Schaltet auf die angegebene Codepage um. + /// + public void SetCodepage(PrinterCodepage codepage) + { + if (printerEnc != codepage) + { + printerEnc = codepage; + byte[] buf = new byte[] { 0x1B, 0x74, codepage.PageNumber }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// ESC 'a' n: + /// Setzt die angegebene Textausrichtung. + /// + /// + public void SetAlignment(Alignment alignment) + { + if (_alignment != alignment) + { + _alignment = alignment; + byte[] buf = new byte[] { 0x1B, 0x61, System.Convert.ToByte(alignment) }; + memstr.Write(buf, 0, buf.Length); + } + } + + /// + /// GS 'V' m: + /// Schneidet das Papier (nur bei Modellen mit einer Auto-Cut-Funktion). + /// + /// true, wenn ein voller Schnitt durchgeführt + /// werden soll; false, wenn ein kleines Stück freigelassen + /// werden soll + public void CutPaper(bool fullCut) + { + byte[] buf = new byte[] { 0x1D, 0x56, System.Convert.ToByte(fullCut ? 65 : 66), 0x40 }; + memstr.Write(buf, 0, buf.Length); + } + + /// + /// Gibt den aktuellen Pufferinhalt zurück und leert diesen anschließend. + /// + /// + public byte[] GetCurrentBuffer() + { + byte[] buf = memstr.ToArray(); + memstr.Close(); + memstr = new MemoryStream(); + return buf; + } + } +} diff --git a/BonNotiz/Main.Designer.cs b/BonNotiz/Main.Designer.cs new file mode 100644 index 0000000..ed51b97 --- /dev/null +++ b/BonNotiz/Main.Designer.cs @@ -0,0 +1,89 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace BonNotiz +{ + partial class Main + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + TextBox = new TextBox(); + PrinterSelect = new ComboBox(); + Print = new Button(); + SuspendLayout(); + // + // TextBox + // + TextBox.AcceptsReturn = true; + TextBox.Font = new Font("Consolas", 12F, FontStyle.Regular, GraphicsUnit.Point, 0); + TextBox.Location = new Point(12, 12); + TextBox.Multiline = true; + TextBox.Name = "TextBox"; + TextBox.ScrollBars = ScrollBars.Vertical; + TextBox.Size = new Size(470, 339); + TextBox.TabIndex = 0; + TextBox.Text = "Text eingeben..."; + // + // PrinterSelect + // + PrinterSelect.FormattingEnabled = true; + PrinterSelect.Location = new Point(12, 357); + PrinterSelect.Name = "PrinterSelect"; + PrinterSelect.Size = new Size(177, 23); + PrinterSelect.TabIndex = 2; + // + // Print + // + Print.Location = new Point(407, 357); + Print.Name = "Print"; + Print.Size = new Size(75, 23); + Print.TabIndex = 3; + Print.Text = "&Drucken"; + Print.UseVisualStyleBackColor = true; + Print.Click += Print_Click; + // + // Main + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(493, 391); + Controls.Add(Print); + Controls.Add(PrinterSelect); + Controls.Add(TextBox); + Name = "Main"; + Text = "BonNotiz"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private TextBox TextBox; + private ComboBox PrinterSelect; + private Button Print; + } +} diff --git a/BonNotiz/Main.cs b/BonNotiz/Main.cs new file mode 100644 index 0000000..b4c4481 --- /dev/null +++ b/BonNotiz/Main.cs @@ -0,0 +1,60 @@ +using KohaCompanion.Shared; +using System; +using System.Windows.Forms; + +namespace BonNotiz +{ + public partial class Main : Form + { + public Main() + { + InitializeComponent(); + + FillPrinters(); + } + + private void FillPrinters() + { + // Auswahllisten für Drucker befüllen + foreach (String p in System.Drawing.Printing.PrinterSettings.InstalledPrinters) + { + PrinterSelect.Items.Add(p); + } + + foreach (String p in PrinterSelect.Items) + { + if (p.StartsWith("Epson")) + { + PrinterSelect.SelectedItem = p; + } + } + } + + static void Drucken(String bondrucker, String text) + { + EscPosPrinter pos = new(); + pos.Write(text); + pos.WriteLine(); + pos.WriteLine(); + + pos.CutPaper(true); + Byte[] buf = pos.GetCurrentBuffer(); + + using RawPrinter prn = new(bondrucker); + using RawPrinter.RawDocumentStream doc = prn.CreateDocument("BonNotiz"); + doc.Write(buf, 0, buf.Length); + } + + private void Print_Click(object sender, EventArgs e) + { + if (PrinterSelect.SelectedItem == null || PrinterSelect.SelectedItem.ToString() == null) + { + MessageBox.Show("Kein Drucker ausgewählt.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + Drucken(PrinterSelect.SelectedItem.ToString()!, TextBox.Text); + } + } + } +} diff --git a/BonNotiz/Main.resx b/BonNotiz/Main.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/BonNotiz/Main.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BonNotiz/Program.cs b/BonNotiz/Program.cs new file mode 100644 index 0000000..97c4e49 --- /dev/null +++ b/BonNotiz/Program.cs @@ -0,0 +1,20 @@ +using System; +using System.Windows.Forms; + +namespace BonNotiz +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Main()); + } + } +} \ No newline at end of file diff --git a/BonNotiz/RawPrinter.cs b/BonNotiz/RawPrinter.cs new file mode 100644 index 0000000..d0820d2 --- /dev/null +++ b/BonNotiz/RawPrinter.cs @@ -0,0 +1,397 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace KohaCompanion.Shared +{ + /// + /// Eine Klasse zum Kapseln eines Druckers. Mit der Methode + /// CreateDocument(String) + /// kann ein neues Dokument im RAW-Format erstellt werden. + /// + /// Pro RawPrinter-Instanz kann immer nur eine RawDocumentStream-Instanz + /// gleichzeitig aktiv sein. Nach der Freigabe einer RawDocumentStream- + /// Instanz kann wieder ein neues Dokument erstellt werden. + /// + /// + /// Diese Klasse ist nicht threadsicher. Wenn verschiedene Threads drucken + /// wollen, muss für jeden Thread ein eigenens RawPrinter-Objekt + /// erstellt werden. + /// + /// Quelle: https://www.vbarchiv.net/tipps/tipp_2375-kassenbon-drucker-mit-vbnet-oder-c-per-esc-pos-befehle-ansprechen.html + /// Originalautor: Konstantin Preißer + /// Originallizenz: MIT + /// Angepaßt an C# von Anna Christina Naß + /// + + public class RawPrinter : IDisposable + { + // API-Deklarationen + [DllImport("winspool.Drv", EntryPoint = "OpenPrinterW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool OpenPrinter(string szPrinter, ref IntPtr hPrinter, IntPtr pd); + + [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool ClosePrinter(IntPtr hPrinter); + + [DllImport("winspool.drv", EntryPoint = "GetPrinterDriver2W", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool GetPrinterDriver2(IntPtr hWnd, IntPtr hPrinter, string? pEnvironment, int Level, IntPtr pDriverInfo, int cbBuf, ref int pcbNeeded); + + private const int ERROR_INSUFFICIENT_BUFFER = 122; + private const int PRINTER_DRIVER_XPS = 0x2; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private class DRIVER_INFO_8 + { + public uint cVersion; + public string? pName; + public string? pEnvironment; + public string? pDriverPath; + public string? pDataFile; + public string? pConfigFile; + public string? pHelpFile; + public string? pDependentFiles; + public string? pMonitorName; + public string? pDefaultDataType; + public string? pszzPreviousNames; + public System.Runtime.InteropServices.ComTypes.FILETIME ftDriverDate; + public ulong dwlDriverVersion; + public string? pszMfgName; + public string? pszOEMUrl; + public string? pszHardwareID; + public string? pszProvider; + public string? pszPrintProcessor; + public string? pszVendorSetup; + public string? pszzColorProfiles; + public string? pszInfPath; + public uint dwPrinterDriverAttributes; + public string? pszzCoreDriverDependencies; + public System.Runtime.InteropServices.ComTypes.FILETIME ftMinInboxDriverVerDate; + public ulong dwlMinInboxDriverVerVersion; + } + + // Instanz-Variablen + private readonly IntPtr hPrinter = IntPtr.Zero; + private readonly bool xpsDriver; + private RawDocumentStream? currentDoc = null; + private bool disposed = false; + + private IntPtr HandlePrinter + { + get + { + if (disposed) + throw new ObjectDisposedException("RawPrinter"); + + return hPrinter; + } + } + + /// + /// Erstellt eine neue Instanz. + /// + /// Der Name des zu verwendenden Druckers + /// Beim Aufruf einer + /// Win32-API trat ein Fehler auf. + public RawPrinter(string printerName) + { + CheckApiCall(OpenPrinter(printerName, ref hPrinter, IntPtr.Zero)); + + xpsDriver = IsXpsDriver(); + } + + /// + /// Überprüft den Rückgabewert einer Win32-API-Funktion und wirft bei Bedarf + /// eine Exception. + /// + /// + private static void CheckApiCall(bool retVal) + { + if (!retVal) + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + + /// + /// Gibt die nativen Resourcen dieses Objekts frei. + /// + /// Beim Aufruf einer + /// Win32-API trat ein Fehler auf. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + ClosePrinter(HandlePrinter); + disposed = true; + } + } + + ~RawPrinter() + { + Dispose(false); + } + + private bool canCreateDocument = false; + /// + /// Erstellt ein neues RawPrintDocument. + /// + /// Der Name des Dokuments + /// Das neue Dokument + /// Beim Aufruf einer + /// Win32-API trat ein Fehler auf. + /// Es wurde bereits ein Dokument + /// für diesen Drucker erstellt und noch nicht freigegeben. + public RawDocumentStream CreateDocument(string docName) + { + if (disposed) + throw new ObjectDisposedException("RawPrinter"); + + if (currentDoc != null) + throw new InvalidOperationException("Pro RawPrinter kann immer nur " + "1 RawDocumentStream-Objekt erzeugt werden."); + + canCreateDocument = true; + try + { + currentDoc = new RawDocumentStream(this, docName); + return currentDoc; + } + finally + { + canCreateDocument = false; + } + } + + /// + /// Überprüft, ob es sich bei dem Treiber dieses Druckers um einen + /// XPS-Treiber handelt. + /// + /// + /// Bei Windows-Versionen kleiner als Windows 8 (NT 6.2), also + /// Windows Vista und Windows 7, wird immer + /// false zurückgegeben. + /// + /// true, wenn es sich um einen XPS-Treiber handelt + private bool IsXpsDriver() + { + int level = 8; + + // Nachschauen, wieviel Bytes als Buffer reserviert werden müssen. + // In diesem Fall muss GetPrinterDriver2 mit ERROR_INSUFFICIENT_BUFFER + // failen. + int bytesNeeded = 0; + GetPrinterDriver2(IntPtr.Zero, HandlePrinter, null, level, IntPtr.Zero, 0, ref bytesNeeded); + if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) + CheckApiCall(false); + + DRIVER_INFO_8 driverInf; + + // Nativen Speicher mit geforderter Größe erstellen + int pBytesLength = Math.Max(bytesNeeded, Marshal.SizeOf(typeof(DRIVER_INFO_8))); + IntPtr pBytes = Marshal.AllocHGlobal(pBytesLength); + try + { + CheckApiCall(GetPrinterDriver2(IntPtr.Zero, HandlePrinter, null, level, pBytes, pBytesLength, ref bytesNeeded)); + + // Der Anfang des Bytesarrays enthält die Structure; der hintere Teil kann + // die Strings enthalten, auf die die Pointer in der Structure zeigen. + // Hier jetzt die Structure in ein verwaltetes Objekt marshallen. + driverInf = (DRIVER_INFO_8)Marshal.PtrToStructure(pBytes, typeof(DRIVER_INFO_8)); + } + finally + { + Marshal.FreeHGlobal(pBytes); + pBytes = IntPtr.Zero; + }// Pointer clearen + + // Prüfen, ob das Flag PRINTER_DRIVER_XPS gesetzt ist + return (driverInf.dwPrinterDriverAttributes & PRINTER_DRIVER_XPS) != 0; + } + + /// + /// Ermöglicht das Senden von RAW-Dokumenten (Bytes) an den Drucker. + /// + /// + /// Die Methoden StartPage() und EndPage() dienen zum Unterteilen des + /// Druckauftrags in Seiten (wichtig, wenn der Windows Spooler verwendet + /// wird und man Dokumente senden will, die lange zum Erstellen brauchen, + /// da dieser bis zur Fertigestellung einer Seite wartet, bevor diese + /// tatsächlich zum Drucker gesendet wird). + /// + public class RawDocumentStream : Stream + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private class DOCINFO + { + public string? pDocName; + public string? pOutputFile; + public string? pDataType; + } + + // API-Deklarationen + [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In()][MarshalAs(UnmanagedType.LPStruct)] DOCINFO di); + + [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool EndDocPrinter(IntPtr hPrinter); + + [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool StartPagePrinter(IntPtr hPrinter); + + [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool EndPagePrinter(IntPtr hPrinter); + + [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] + private static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, int dwCount, ref int dwWritten); + + private readonly RawPrinter printer; + private bool pageStarted = false; + private bool disposed = false; + + internal RawDocumentStream(RawPrinter printer, string docName) + { + if (!printer.canCreateDocument) + // Schauen, dass das über den Printer erstellt wird + throw new InvalidOperationException(); + + this.printer = printer; + + DOCINFO di = new DOCINFO(); + // Wenn es sich um einen v4-Treiber (XPS-basiert, eingeführt mit + // Windows 8) handelt, muss "XPS_PASS" statt "RAW" verwendet werden. + // Siehe: http://support.microsoft.com/kb/2779300 + di.pDataType = printer.xpsDriver ? "XPS_PASS" : "RAW"; + di.pDocName = docName; + + CheckApiCall(StartDocPrinter(printer.HandlePrinter, 1, di)); + } + + /// + /// Startet eine neue Seite (nur wichtig für den Windows Spooler). + /// + /// Beim + /// Aufruf einer Win32-API trat ein Fehler auf. + public void StartPage() + { + if (!pageStarted) + { + CheckApiCall(StartPagePrinter(printer.HandlePrinter)); + pageStarted = true; + } + } + + /// + /// Beendet die aktuelle Seite. + /// + /// Beim + /// Aufruf einer Win32-API trat ein Fehler auf. + public void FinishPage() + { + if (pageStarted) + { + CheckApiCall(EndPagePrinter(printer.HandlePrinter)); + pageStarted = false; + } + } + + protected override void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + if (pageStarted) + FinishPage(); + + CheckApiCall(EndDocPrinter(printer.HandlePrinter)); + + printer.currentDoc = null; + } + + disposed = true; + } + + base.Dispose(disposing); + } + + public override bool CanRead { get { return false; } } + + public override bool CanSeek { get { return false; } } + + public override bool CanWrite { get { return true; } } + + public override void Flush() { } + + public override long Length { get { throw new NotSupportedException(); } } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Sendet die angegebenen Bytes an den Drucker. + /// + /// + /// + /// + /// Beim Aufruf + /// einer Win32-API trat ein Fehler auf. + public override void Write(byte[] buffer, int offset, int count) + { + if (disposed) + throw new ObjectDisposedException("RawDocumentStream"); + + if (offset < 0 || count < 0 || offset + count > buffer.Length) + throw new ArgumentException(); + + if (!pageStarted) + StartPage(); + + // Pointer auf Byte-Array holen. + // In C#: fixed (byte* pBytes = buffer) { ... }, + // dann Pointerarithmetik verwenden + GCHandle hgc = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + int bytesCompleted = 0; + while (count - bytesCompleted > 0) + { + int tempBytesWritten = 0; + + CheckApiCall(WritePrinter(printer.HandlePrinter, Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset + bytesCompleted), count - bytesCompleted, ref tempBytesWritten)); + + if (!(tempBytesWritten > 0)) + break; + + bytesCompleted += tempBytesWritten; + } + } + finally + { + hgc.Free(); + } + } + } + } + +}