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();
+ }
+ }
+ }
+ }
+
+}