From f43915c29fa57a81aba9528cb2a15be63cf1282a Mon Sep 17 00:00:00 2001 From: acn Date: Wed, 1 Jul 2026 11:16:02 +0200 Subject: [PATCH] Erste funktionierende Version --- .gitattributes | 63 ------------- BonNotiz/BonNotiz.csproj | 18 ++++ BonNotiz/BonNotiz.ico | Bin 0 -> 9662 bytes BonNotiz/EscPosPrinter.cs | 50 +++++------ BonNotiz/Main.Designer.cs | 22 ++++- BonNotiz/Main.cs | 24 ++++- BonNotiz/Main.resx | 167 +++++++++++++++++++++++++++++++++++ BonNotiz/MarkdownToEscPos.cs | 110 +++++++++++++++++++++++ BonNotiz/MiniMarkdown.cs | 156 ++++++++++++++++++++++++++++++++ BonNotiz/RawPrinter.cs | 2 +- 10 files changed, 517 insertions(+), 95 deletions(-) delete mode 100644 .gitattributes create mode 100644 BonNotiz/BonNotiz.ico create mode 100644 BonNotiz/MarkdownToEscPos.cs create mode 100644 BonNotiz/MiniMarkdown.cs diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 1ff0c42..0000000 --- a/.gitattributes +++ /dev/null @@ -1,63 +0,0 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain diff --git a/BonNotiz/BonNotiz.csproj b/BonNotiz/BonNotiz.csproj index b73b207..b3ea26d 100644 --- a/BonNotiz/BonNotiz.csproj +++ b/BonNotiz/BonNotiz.csproj @@ -7,9 +7,27 @@ net10.0-windows enable true + true disable BonNotiz.Program none + BonNotiz.ico + BonNotiz + Anna Christina Naß + + LICENSE.txt + + + + + + + True + \ + + + + \ No newline at end of file diff --git a/BonNotiz/BonNotiz.ico b/BonNotiz/BonNotiz.ico new file mode 100644 index 0000000000000000000000000000000000000000..7d36a8f9eac26f92065ed924040e0b406b8bb5c1 GIT binary patch literal 9662 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 pAmdP#q$j0j(hV3Gg; literal 0 HcmV?d00001 diff --git a/BonNotiz/EscPosPrinter.cs b/BonNotiz/EscPosPrinter.cs index 0178234..9da5c88 100644 --- a/BonNotiz/EscPosPrinter.cs +++ b/BonNotiz/EscPosPrinter.cs @@ -3,7 +3,7 @@ using System.Text; using System.IO; using System.Drawing; -namespace KohaCompanion.Shared +namespace BonNotiz { /// /// Stellt einen zustandsbehafteten ESC/POS-Drucker dar. @@ -34,10 +34,10 @@ namespace KohaCompanion.Shared 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)); + public static readonly PrinterCodepage Cp437 = new(0, CodePagesEncodingProvider.Instance.GetEncoding(437)); + public static readonly PrinterCodepage Cp852 = new(18, CodePagesEncodingProvider.Instance.GetEncoding(852)); + public static readonly PrinterCodepage Cp866 = new(17, CodePagesEncodingProvider.Instance.GetEncoding(866)); + public static readonly PrinterCodepage Cp1252 = new(16, CodePagesEncodingProvider.Instance.GetEncoding(1252)); private readonly byte _pageNumber; private readonly Encoding? _encoding; @@ -52,8 +52,10 @@ namespace KohaCompanion.Shared public Encoding Encoding { get { return _encoding; } } } - // Steuercodes für neue Zeile - private static readonly byte[] newLineBytes = { 0xA, 0xD }; + // Steuercodes + private static readonly byte[] newLineBytes = [0xA, 0xD]; + private static readonly byte esc = 0x1b; + private static readonly byte gs = 0x1d; // Interner Buffer private MemoryStream memstr = new(); @@ -63,10 +65,8 @@ namespace KohaCompanion.Shared 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; /// @@ -80,7 +80,7 @@ namespace KohaCompanion.Shared Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Initialize Printer Command senden - byte[] buf = new byte[] { 0x1B, 0x40 }; + byte[] buf = [esc, 0x40]; memstr.Write(buf, 0, buf.Length); } @@ -119,12 +119,10 @@ namespace KohaCompanion.Shared 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 + for (int i = 0; i < buf.Length; i++) { + if ((int)buf[i] < 0x20 || (int)buf[i] == 0x7F) + buf[i] = 0x20; + } memstr.Write(buf, 0, buf.Length); } @@ -170,7 +168,7 @@ namespace KohaCompanion.Shared byte[] buf; - byte[] zeilenAnfang = new byte[] { 0x1B, 0x2A, 33, System.Convert.ToByte(bmp.Width & 0xFF), System.Convert.ToByte((bmp.Width >> 8) & 0xFF) }; + byte[] zeilenAnfang = [esc, 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++) @@ -201,10 +199,10 @@ namespace KohaCompanion.Shared // Zeile, aber geht mit kleineren Werten auch) // Nicht LF verwenden, weil bei LF der normale Zeilenabstand verwendet // wird! - buf = new byte[] { 0x1B, 0x4A, 0 }; + buf = [esc, 0x4A, 0]; else // Am Schluss normaler Zeilenabstand nach unten. - buf = new byte[] { 0xA }; + buf = [0xA]; memstr.Write(buf, 0, buf.Length); } } @@ -217,13 +215,13 @@ namespace KohaCompanion.Shared /// sonst false /// true, wenn die Linie 2 Pixel statt 1 Pixel /// dick sein soll - public void SetUnderline(bool value, bool doubleThickness) + public void SetUnderline(bool value, bool doubleThickness = false) { if (value != underline || (value & doubleThickness != underlineDouble)) { underline = value; underlineDouble = doubleThickness; - byte[] buf = new byte[] { 0x1B, 0x2D, System.Convert.ToByte(value ? doubleThickness ? 2 : 1 : 0) }; + byte[] buf = [esc, 0x2D, System.Convert.ToByte(value ? doubleThickness ? 2 : 1 : 0)]; memstr.Write(buf, 0, buf.Length); } } @@ -238,7 +236,7 @@ namespace KohaCompanion.Shared if (value != emphasized) { emphasized = value; - byte[] buf = new byte[] { 0x1B, 0x45, System.Convert.ToByte(value ? 1 : 0) }; + byte[] buf = [esc, 0x45, System.Convert.ToByte(value ? 1 : 0)]; memstr.Write(buf, 0, buf.Length); } } @@ -260,7 +258,7 @@ namespace KohaCompanion.Shared currentFontX = x; currentFontY = y; - byte[] buf = new byte[] { 0x1D, 0x21, System.Convert.ToByte((x << 4) | y) }; + byte[] buf = [gs, 0x21, System.Convert.ToByte((x << 4) | y)]; memstr.Write(buf, 0, buf.Length); } } @@ -274,7 +272,7 @@ namespace KohaCompanion.Shared if (printerEnc != codepage) { printerEnc = codepage; - byte[] buf = new byte[] { 0x1B, 0x74, codepage.PageNumber }; + byte[] buf = [esc, 0x74, codepage.PageNumber]; memstr.Write(buf, 0, buf.Length); } } @@ -289,7 +287,7 @@ namespace KohaCompanion.Shared if (_alignment != alignment) { _alignment = alignment; - byte[] buf = new byte[] { 0x1B, 0x61, System.Convert.ToByte(alignment) }; + byte[] buf = [esc, 0x61, System.Convert.ToByte(alignment)]; memstr.Write(buf, 0, buf.Length); } } @@ -303,7 +301,7 @@ namespace KohaCompanion.Shared /// werden soll public void CutPaper(bool fullCut) { - byte[] buf = new byte[] { 0x1D, 0x56, System.Convert.ToByte(fullCut ? 65 : 66), 0x40 }; + byte[] buf = [gs, 0x56, System.Convert.ToByte(fullCut ? 65 : 66), 0x40]; memstr.Write(buf, 0, buf.Length); } diff --git a/BonNotiz/Main.Designer.cs b/BonNotiz/Main.Designer.cs index ed51b97..6154647 100644 --- a/BonNotiz/Main.Designer.cs +++ b/BonNotiz/Main.Designer.cs @@ -31,9 +31,11 @@ namespace BonNotiz /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); TextBox = new TextBox(); PrinterSelect = new ComboBox(); Print = new Button(); + Info = new Button(); SuspendLayout(); // // TextBox @@ -58,23 +60,38 @@ namespace BonNotiz // // Print // - Print.Location = new Point(407, 357); + Print.Location = new Point(389, 357); Print.Name = "Print"; - Print.Size = new Size(75, 23); + Print.Size = new Size(93, 23); Print.TabIndex = 3; Print.Text = "&Drucken"; Print.UseVisualStyleBackColor = true; Print.Click += Print_Click; // + // Info + // + Info.Location = new Point(245, 357); + Info.Name = "Info"; + Info.Size = new Size(109, 23); + Info.TabIndex = 4; + Info.Text = "&Info..."; + Info.UseVisualStyleBackColor = true; + Info.Click += Info_Click; + // // Main // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(493, 391); + Controls.Add(Info); Controls.Add(Print); Controls.Add(PrinterSelect); Controls.Add(TextBox); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; Name = "Main"; + SizeGripStyle = SizeGripStyle.Hide; Text = "BonNotiz"; ResumeLayout(false); PerformLayout(); @@ -85,5 +102,6 @@ namespace BonNotiz private TextBox TextBox; private ComboBox PrinterSelect; private Button Print; + private Button Info; } } diff --git a/BonNotiz/Main.cs b/BonNotiz/Main.cs index b4c4481..c53d9f0 100644 --- a/BonNotiz/Main.cs +++ b/BonNotiz/Main.cs @@ -1,4 +1,3 @@ -using KohaCompanion.Shared; using System; using System.Windows.Forms; @@ -32,13 +31,16 @@ namespace BonNotiz static void Drucken(String bondrucker, String text) { - EscPosPrinter pos = new(); + /* EscPosPrinter pos = new(); pos.Write(text); pos.WriteLine(); pos.WriteLine(); pos.CutPaper(true); - Byte[] buf = pos.GetCurrentBuffer(); + Byte[] buf = pos.GetCurrentBuffer(); */ + + MarkdownToEscPos md = new(); + Byte[] buf = md.Render(text); using RawPrinter prn = new(bondrucker); using RawPrinter.RawDocumentStream doc = prn.CreateDocument("BonNotiz"); @@ -56,5 +58,21 @@ namespace BonNotiz Drucken(PrinterSelect.SelectedItem.ToString()!, TextBox.Text); } } + + private void Info_Click(object sender, EventArgs e) + { + String text = "BonNotiz\n(c) 2026 Anna Christina Naß\n\n" + + "Folgende Markdown-Formatierungen werden umgesetzt:\n\n" + + "# Überschrift 1\n" + + "## Überschrift 2\n" + + "### Überschrift 3 (und weitere analog)\n" + + "--- (Horizontale Linie)\n" + + "- Aufzählung\n" + + "**fett**\n" + + "*kursiv* (wird ebenfalls fett gedruckt)\n" + + "_unterstrichen_"; + + MessageBox.Show(text, "Über BonNotiz", MessageBoxButtons.OK, MessageBoxIcon.Information); + } } } diff --git a/BonNotiz/Main.resx b/BonNotiz/Main.resx index 8b2ff64..d91ce66 100644 --- a/BonNotiz/Main.resx +++ b/BonNotiz/Main.resx @@ -117,4 +117,171 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAAEAMDAAAAEAIACoJQAAFgAAACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAACkAAAA5AAAAOQAA + ADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOgAAEkYhJUK9ISVAtgAA + E0wAAAA6AAAAOQAAADkAAAA5AAAAOQAAADkAAAAoAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAA + AGOrq6vCsbGxw7GxscOxsbHDsbGxw7GxscOxsbHDsbGxw7GxscOxsbHDsbGxw7GxscOxsbHDsLCww1J6 + peVHhbH+Jk19+GJmd+CqqqrFsbGxw7GxscOxsbHDsbGxw6urq8IAAABjAAAADwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAACgAAAFzs7Ozs//////////////////////////////////////////////////////// + /////////////zdlpP9NwN7/GEqN/05gff/Gys//+/v7/////////////////+zs7OsAAABcAAAACgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAB6enr/d3d3/1VVVf/FxcX///////////////////////////////////////// + /////////////////////////////36gy/8lirv/L7nh/xlNkv9rc4T/5+ft//7+/v///////////8TE + xP9VVVX/d3d3/3p6ev8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6enr/fn5+53V1demvr6/5+vn5//39/f////////////// + //////////////////////////////////////////////D0+P8aRoX/ONj3/yB4rv8wW47/lZmo//j4 + +P/9/f3/+vn5/66urvh2dnbpfn5+53p6ev8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEhIT/hISE/7Gxsf+lpKT/9fT0//j3 + 9//6+vr//f39//////////////////////////////////////////////////////8yX5//Kbrn/yXF + 7P8XUJb/TWF+/8LGy//08/P/9fT0/6Sjo/+wsLD/g4OD/4SEhP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOjo7/d3d3/4yM + jP9+fX3/7evr//Hv7//08/P/9/b2//n5+f/7+vr//Pz8//79/f////////////////////////////// + //+GpMz/IXmt/xSy6/8nrdr/G1eY/2tzgv/b2d7/7Orq/359ff+NjY3/d3d3/46Ojv8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADm5ub/jIyM/1lZWf9UU1P/5OLi/+jm5v/s6ur/7+3t//Lw8P/08/P/9vX1//f29v/49/f/+fj4//n4 + +P/5+Pj/+fj4//j39//u7/L/F0J+/yG78P8St+j/H3u0/zFgjv+Ljpz/3tzc/1RUVP9ZWVn/jIyM/+bm + 5v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADd3d3/gICA/y4uLv8oJyf/19TU/9/c3P/i4OD/5ePj/+jm5v/q6en/7Ovr/+7s + 7P/v7u7/8O/v//Hv7//x7+//8O/v/+/u7v/u7Oz/NF+a/yGv3/8CoeP/IL/p/xhdn/9JXHn/qauw/ycm + Jv8uLi7/gICA/93d3f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkIiIH5aQ + kPSbmpr+m5qa/puamv6bmpr+m5qa/n18fP7n5+f/hYWF/yEhIf8XFxf/s7Cw/9TQ0P/Y1NT/29jY/9/c + 3P/h3t7/4+Dg/+Ti4v/l4+P/5uTk/+bk5P/m5OT/5uTk/+Xj4//k4uL/gJ2+/xpkl/8Sq+//BKbg/yGq + 2P8bY53/Vl1s/xUVGv8hISH/hYWF/+fn5/99fHz+m5qa/puamv6bmpr+m5qa/puamv6WkJD0kIiIHwAA + AAAAAAAAkYeHMqahof++vr7/vr6+/76+vv++vr7/vr6+/5iYmP/w8PD/iYmJ/yEhIf8YGBj/j4yM/8jD + w//Mx8f/z8vL/9LOzv/V0dH/19PT/9nV1f/a19f/29jY/9vY2P/b2Nj/29jY/9rX1//Z1dX/08/R/xY/ + eP8dufD/AqTj/w636P8agrn/KF6G/x0gL/8gICD/iYmJ//Dw8P+YmJj/vr6+/76+vv++vr7/vr6+/76+ + vv+moaH/jYeHMAAAAAAAAAAAlo+PQ7GsrP/Hx8f/x8fH/8fHx//Hx8f/x8fH/6Ojo//6+vr/ioqK/xsb + G/8VFRX/YV5e/6WgoP+oo6P/qqam/62oqP+vq6v/sa2t/7Ourv+0sLD/tbGx/7Wxsf+1sbH/tbGx/7Sw + sP+zrq7/sa2t/y5Zi/8co9T/BqTr/wCp4f8Yvun/FG2p/xw0TP8dISb/iIiI//r6+v+jo6P/x8fH/8fH + x//Hx8f/x8fH/8fHx/+wrKz/l4uLQQAAAAAAAAAApp2dVcC9vf/R0dH/0dHR/9HR0f/R0dH/0dHR/6+v + r///////hoaG/w0NDf8LCwv/CQkJ/wgICP8ICAj/CAgI/wgICP8ICAj/CAgI/wgICP8ICAj/CAgI/wgI + CP8ICAj/CAgI/wgICP8ICAj/CAgI/womQP8YUX//FLHx/wGq5P8Dr+T/G6va/xZpm/8dJTT/e3uA//7+ + /v+vr6//0dHR/9HR0f/U1NT/0tLS/9HR0f+/vLz/pp2dUgAAAAAAAAAAtK+vZc/Nzf/b29v/29vb/9vb + 2//b29v/29vb/8PDw/98fHz/QkJC/wgICP8ICAj/CAgI/wgICP8ICAj/CAgI/wgICP8ICAj/CAgI/wgI + CP8ICAj/CAgI/wgICP8ICAj/CAgI/wgICP8ICAj/CAgI/wgICP8SOWv/Gbbs/waq6/8Ar+T/Cbzq/xiN + v/8YU3b/MzlD/3l5ef/FxcX/29vb/9vb2/+1tbX/o6Oj/9vb2//OzMz/tbCwYwAAAAAAAAAAxL+/btnX + 1//i4uL/4uLi/+Li4v/i4uL/4uLi/+Pj4/+QkJD/EBAQ/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMD + A/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8NO2b/GZTD/w2u + 8v8BsOX/ALPm/xPA6/8YgbL/GzVL/3V5f//g4OD/4uLi/+Li4v+3uLj/rq6u/+Li4v/Y1tb/xMDAawAA + AAAAAAAA0s3NdOLh4f/p6en/6enp/+np6f/p6en/6enp/+jo6P/b29v/5eXl/+Xl5f/l5eX/5OTk/+Pj + 4//i4uL/4eHh/+Dg4P/f39//3t7e/93d3f/c3Nz/29vb/9ra2v/a2tr/29vb/9zc3P/d3d3/3d3d/97e + 3v+Zssb/E0Bm/xK38v8Fsez/ALTm/wK56P8Vsdz/IYOr/2VtfP/V1dr/6Ojo/+np6f/Q0ND/xMTE/+np + 6f/j4uL/0c/PcgAAAAAAAAAA3Nrae+vq6v/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw + 8P/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw8P/w8PD/8PDw//Dw + 8P/w8PD/8PDw//Dw8P/v7+//Fj5p/xOz5/8LsvL/ALbo/wC56f8Fwu3/Fp3I/zh3lv+SmKL/6enp//Dw + 8P/Gx8f/x8fH/+3t7f/r6ur/3NnZeAAAAAAAAAAAwsDAje/u7v/29vb/9vb2//b29v/29vb/9vb2//b2 + 9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2 + 9v/29vb/9vb2//b29v/29vb/9vb2//b29v/19fX/Rnec/xGAqv8OtfP/Bbft/wC66f8Avuv/DMbt/xiT + vv9Ran//xMTL//Pz8//W1tb/yMjI//b29v/v7u7/wsDAjAAAAAAAAAAAzMzLmtPT0//R0dH/0dHR/9HR + 0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR + 0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0NDQ/8bGxv+8vML/j6S3/w0tTv8Qu/L/C7ny/wC7 + 6v8Av+v/AMPt/xG43/8iia//Y2p5/8DAxf/Q0ND/0dHR/9HR0f/T09P/zMzKmAAAAAAAAAAA09XTcqWl + pf+RkZH/kZGR/5GRkf+RkZH/kZGR/5GRkf+RkZH/b29v/2JiYv9iYmL/YmJi/2JiYv9iYmL/YmJi/2Ji + Yv9iYmL/UlJS/1JSUv9SUlL/UlJS/1JSUv9SUlL/UlJS/1JSUv9SUlL/SUlP/y80Qv8kLT3/Ii08/xE8 + Y/8Or9//Dbv0/wS87f8AwOz/AMPt/wDJ8P8Rpc//LGuK/19lcP+NjY3/kZGR/5GRkf+lpaX/1NTUbwAA + AAAAAAAA0NLSa+3u7v/6+vr/+vr6//r6+v/6+vr/+vr6//r6+v/6+vr/jY2N/0FBQf9BQUH/QUFB/0FB + Qf9BQUH/QUFB/0FBQf9BQUH/YmJi/2VlZf9lZWX/ZWVl/2VlZf9lZWX/ZWVl/2VlZf9kZGT/Q3SV/0uR + t/8ZV5P/GV+a/xZRgv8Na4//Db30/wu/8v8Awez/AMTu/wDI7/8DyvD/FpnG/1VrgP/IzNH/9/f3//r6 + +v/t7u3/0dTRaAAAAAAAAAAAzdLQX+jq6f/39/f/9/f3//f39//39/f/9/f3//f39//39/f/mJiY/1BQ + UP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/ZmZm/3h4eP93d3f/d3d3/3d3d/93d3f/d3d3/3d3 + d/94eHj/Jl2Q/yCLxf8Vr/L/DrDy/wy28/8Nu/T/Dr71/w7B9f8Dwu//AMbu/wDJ8P8AzPH/Cbrj/yOM + tf9qc4f/4+Po//b29v/o6en/ztHOXQAAAAAAAAAAzNDMT+Pl5f/09PT/9PT0//T09P/09PT/9PT0//T0 + 9P/09PT/m5ub/1VVVf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VV + Vf9VVVX/VVVV/1VVVf9VVVX/Olp1/xZPjf8ZsfL/Jrjz/yy/9f8owvX/Lcf2/zvO+P890/j/EMvw/wDK + 8P8AzvL/ANL0/w6j1v86dJj/mZyn/+7u7v/j5eT/ytHOTAAAAAAAAAAAxsrKPd3f3v/x8fH/8fHx//Hx + 8f/x8fH/8fHx//Hx8f/x8fH/np6e/1VVVf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VV + Vf9VVVX/VVVV/1VVVf9VVVX/VVVV/1VVVf9VVVX/VFVV/xdQj/8JpuX/C7Dy/w228/8Wy/r/FdP9/xjW + /f8c2P3/QuH9/2Di+v9J3vf/BdP0/wLQ8/8Tks7/U2qC/9bW2f/b3t3/xMjIOwAAAAAAAAAAu8fBLNbZ + 1//u7u7/7u7u/+7u7v/u7u7/7u7u/+7u7v/u7u7/0NDQ/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm + 5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/+bm5v/m5ub/5ubm/0J9sf8OhcP/C7Dy/wy2 + 8/8Kwff/ANX//wDX//8A2P//ANr//wDb//8Y3///XOf9/1Lm+/8Ouej/Koe8/9fc3P/U2Nb/vsS+KgAA + AAAAAAAAqqqqF8TGxc3f4ODi3+Hg4uDi4eLh4+Li4uPj4uLk4+Li5OPiqKio8oaHh/CGh4fwh4eH8IeH + h/CHh4fwh4mH8IeJh/CHiYfwh4mJ8IeJifCHiYnwh4mJ8IeJifCHiYnwh4mJ8IeJifCHiYnwh4mJ8Fh6 + lvUVVJP/CrLz/wy28/8OvfX/A9H9/wDX//8A2f//ANr//wm85P8LtuH/Crjj/xm95/9h0fD/icfo/N7f + 3+LExsXNqqqqFwAAAAAAAAAAAAAAAJOTk+ucnJz/pKSk/6urq/+zs7P/u7u7/76+vv/AwMD/d3d3/0FB + Qf9BQUH/QUFB/0FBQf9BQUH/QUFB/0FBQf9BQUH/QUFB/0FBQf9BQUH/QUFB/0FBQf9BQUH/QUFB/0FB + Qf9BQUH/QUFB/z9AQv8WVJP/Cqzq/wy28/8OvfX/Csn5/wDY//8A2f//ANv//wm+5/8Vj8H/KWeJ/2OZ + tv9soLv/laSt/5ycnP+Tk5PsAAAAAAAAAAAAAAAAAAAAAJCQkL2YmJj/n5+f/6enp/+urq7/tra2/7y8 + vP++vr7/aGho/0RERP9AQED/QEBA/0BAQP9AQED/QEBA/0BAQP9AQED/QEBA/0BAQP9AQED/QEBA/0BA + QP9AQED/QEBA/0BAQP9AQED/QEBA/0BAQP8cW47/DY7K/wy28/8OvfX/D8T2/wLW/v8A2f//ANv//wHb + /v8Rn9D/H2SI/2Noc/+ioqL/n5+f/5iYmP+Pj4+8AAAAAAAAAAAAAAAAAAAAAImJiY6Tk5P/m5ub/6Ki + ov+pqan/sLCw/7e3t/+8vLz/aWlp/ycnJ/9YVVX/WVZW/1tYWP9cWlr/XVpa/11bW/9eXFz/Xlxc/19d + Xf9gXl7/YF5e/2BeXv9gXl7/YF5e/2BeXv9fXV3/Xlxc/15cXP8+YHz/FFyb/wu39P8OvfX/D8T2/wnQ + +v8A2v//ANv//wDd//8KyvD/EonD/zJFVf+JiYz/mpqa/5OTk/+KioqNAAAAAAAAAAAAAAAAAAAAAIiI + iF+Ojo7/lpaW/52dnf+kpKT/q6ur/7Kysv+4uLj/dnZ2/yUlJf+1r6//urS0/764uP/BvLz/xcDA/8fD + w//KxcX/zMjI/87Kyv/Py8v/0MzM/9DMzP/QzMz/0MzM/8/Ly//Oysr/zMjI/8rFxf/Cv8H/GV2b/wqz + 7P8NvPT/D8P2/w/L9/8B2f//ANz//wDd//8C3///EabZ/xhrnP9NUF3/kJCQ/46Ojv+GhoZeAAAAAAAA + AAAAAAAAAAAAAIeHhzCJiYn/kJCQ/5eXl/+enp7/paWl/6ysrP+ysrL/rq6u/ysrK/+9t7f/wby8/8XA + wP/JxcX/zMjI/8/Ly//Szs7/1NHR/9bT0//X1NT/2NXV/9nV1f/Z1dX/2NXV/9fU1P/W0tL/1NHR/9LO + zv/Py8v/PHqu/w2V0P8NvPT/D8P2/xDK9/8I1vz/ANz//wDe//8A3///C9b2/xCIzP8kRl7/a3B2/4eH + h/+FhYUvAAAAAAAAAAAAAAAAAAAAAG1tbQaIiIiIioqKiJGRkYiXl5eInp6eiKSkpIirq6uInJyc9jEx + Mf/Ev7//ycTE/83Jyf/Rzc3/1NHR/9fU1P/a19f/3dnZ/97b2//f3Nz/4N3d/+De3v/g3t7/4N3d/9/c + 3P/e29v/3dnZ/9rX1//X1NT/hafD/xRjo/8Nu/T/D8L2/xDK9/8P0vn/ANz//wDe//8A4P//AeH//xKu + 4v8Rbqz+MDdK5Hd3d5FtbW0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAmZmZ5TY2Nv/Lx8f/0MzM/9TR0f/Y1dX/3NnZ/9/c3P/h39//4+Hh/+Xi4v/m4+P/5+Tk/+fl + 5f/n5eX/5+Tk/+bj4//l4uL/4+Hh/+Hf3//f3Nz/1tbX/xhen/8LuO//DsL1/xDJ9/8R0fj/Btv9/wDe + //8A4P//AOL//wzd+/8NhtT/GUZw9CYsOnEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAnJyc3z09Pf/Sz8//19TU/9zZ2f/g3d3/4+Dg/+Xj4//n5eX/6efn/+vp + 6f/s6ur/7evr/+3s7P/t7Oz/7evr/+zq6v/r6en/6efn/+fl5f/l4+P/4+Dg/0B/tf8NnNb/DsD1/xDI + 9/8R0Pj/D9j6/wDf//8A4P//AOL//wDj//8buuv/C26+/iUsQNwAKiojAAAAAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo6Oj1URERP/Z1tb/3tvb/+Lf3//l4+P/6Obm/+vp + 6f/u7Oz/8O7u//Hw8P/z8fH/8/Ly//Tz8//08/P/8/Ly//Px8f/x8PD/8O7u/+7s7P/r6en/6Obm/42w + zv8Raav/DsD1/w/H9v8Rz/j/Etf5/wXf/v8A4P//AOL//wDk//8R5f7/FYzb/xVMhvgbLjyTAAAACAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJyc1UlJSf/g3d3/5OHh/+jl + 5f/r6en/7u3t//Hw8P/08/P/9vX1//f29v/49/f/+Pj4//n4+P/5+Pj/+Pj4//j39//39vb/9vX1//Ty + 8v/x8PD/7u3t/+Xl5/8YX6T/C7zx/w/F9v8Rzvj/Etb5/w7e/P8A4P//AOL//wDk//8A5v//K8ny/wdr + zv8fM0jqISEyPAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlpaW1FNT + U//l4uL/6efn/+3r6//x7+//9PPz//f29v/4+Pj/+vn5//v7+//8/Pz//Pz8//39/f/9/f3//Pz8//z8 + /P/7+/v/+vn5//j4+P/39vb/9PPz//Hv7/9CgLr/C5/b/w/D9v8QzPf/EtX5/xPd+v8D4v//AOL//wDk + //8A5v//Euj//ySX4/8PUZn8IilAswAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcnJyUkZGRsPq6Oj/7u3t//Lx8f/29fX/+Pj4//r6+v/8/Pz//v7+//////////////////// + /////////////////////////v7+//z8/P/6+vr/+Pj4//b19f+Rtdb/D22z/w7B9f8Qyvf/EtP5/xPc + +v8N4v3/AOL//wDk//8A5v//AOf//z3Z+P8FbNn/HDVa8RspOlsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAGjv7e3/8/Ly//f29v/6+fn//Pz8//7+/v////////////// + ///////////////////////////////////////////////////+/v7//Pz8//r5+f/u8fT/Fl2o/wu+ + 8/8Px/b/EdH4/xPa+v8T4/v/AuP//wDk//8A5f//AOf//wvp//87re3/C1qx/iEqOswAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAGj08vL/9/f3//r6+v/9/f3///////// + //////////////////////////////////////////////////////////////////////////////39 + /f/6+vr/Q4C//wqj3v8PxPb/Ec74/xLX+f8U4fv/C+b+/wDj//8A5f//AOb//wDn//9F4vv/CnXi/xpA + c90AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAGj39/f/+/r6//79 + /f////////////////////////////////////////////////////////////////////////////// + ///////////////////+/f3/ob/i/3Ck0v80seL/NbXl/ze55/84ven/Ob3p/zax5/81sun/NbTr/zW1 + 7f84t/D/jsvy/zGA2I8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAA + AGj6+vr//f39//////////////////////////////////////////////////////////////////// + /////////////////////////////////////////f39/6TB4/8GQ4e4CViujQdev4EIYMF+BmHGewZi + x3kEZcx2BGPPcwRn0HECZ9ZuAmrZXSBgwAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAEgAAAGj9/f3///////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////39/f8AAABoAAAAEgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAGj///////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //8AAABoAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAGj///////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////8AAABoAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAE4AAABoAAAAaAAA + AGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAA + AGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABOAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAoAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAA + ABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAAKAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAA/8AAP/AAAAD/wAA/8AAAAP/ + AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/AACAAAAAAAEAAIAA + AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAAB + AACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA + AAAAAQAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAA/8AAAAAD + AAD/wAAAAAEAAP/AAAAAAQAA/8AAAAAAAAD/wAAAAAAAAP/AAAAAAAAA/8AAAAAAAAD/wAAAAAAAAP/A + AAAAAAAA/8AAAAAAAAD/wAAAA/8AAP/AAAAD/wAA/8AAAAP/AAD/wAAAA/8AAP/gAAAH/wAA//////// + AAA= + + \ No newline at end of file diff --git a/BonNotiz/MarkdownToEscPos.cs b/BonNotiz/MarkdownToEscPos.cs new file mode 100644 index 0000000..edc4b0d --- /dev/null +++ b/BonNotiz/MarkdownToEscPos.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using static BonNotiz.MiniMarkdown; + +namespace BonNotiz +{ + public class MarkdownToEscPos + { + private readonly EscPosPrinter epp; + + public MarkdownToEscPos() + { + epp = new(); + } + + public byte[] Render(String text) + { + MdDocument doc = MiniMarkdown.Parse(text); + + return Render(doc); + } + + public byte[] Render(MdDocument doc) + { + foreach (MdBlock block in doc.Blocks) + RenderBlock(block); + + epp.WriteLine(); + epp.CutPaper(true); + + return epp.GetCurrentBuffer(); + } + + private void RenderBlock(MdBlock block) + { + switch (block) + { + case MdHeading h: + switch (h.Level) + { + case 1: + epp.SetFontSize(1, 1); + break; + case 2: + epp.SetFontSize(1, 0); + break; + default: + epp.SetFontSize(0, 1); + break; + } + RenderInline(h.Content); + epp.WriteLine(); + epp.WriteLine(); + epp.SetFontSize(0, 0); + break; + + case MdParagraph p: + RenderInline(p.Content); + epp.WriteLine(); + epp.WriteLine(); + break; + + case MdList l: + foreach (var item in l.Items) + { + epp.Write(new string([(char)0xBB, ' '])); + RenderInline(item.Content); + epp.WriteLine(); + } + break; + + case MdHorizontalRule: + epp.WriteLine(new string((char)0x2550, 42)); + break; + } + } + + private void RenderInline(List inlines) + { + foreach (var inline in inlines) + { + switch (inline) + { + case MdText t: + epp.Write(t.Value); + break; + + case MdBold b: + epp.SetEmphasized(true); + RenderInline(b.Content); + epp.SetEmphasized(false); + break; + + case MdItalic i: + epp.SetEmphasized(true); + RenderInline(i.Content); + epp.SetEmphasized(false); + break; + + case MdUnderline u: + epp.SetUnderline(true); + RenderInline(u.Content); + epp.SetUnderline(false); + break; + } + } + } + } + +} diff --git a/BonNotiz/MiniMarkdown.cs b/BonNotiz/MiniMarkdown.cs new file mode 100644 index 0000000..7e857b3 --- /dev/null +++ b/BonNotiz/MiniMarkdown.cs @@ -0,0 +1,156 @@ +using System.Collections.Generic; +using System.Linq; + +namespace BonNotiz +{ + public static class MiniMarkdown + { + public abstract record MdBlock; + public record MdDocument(List Blocks) : MdBlock; + public record MdHeading(int Level, List Content) : MdBlock; + public record MdParagraph(List Content) : MdBlock; + public record MdList(List Items) : MdBlock; + public record MdListItem(List Content); + public record MdHorizontalRule() : MdBlock; + public abstract record MdInline; + public record MdText(string Value) : MdInline; + public record MdBold(List Content) : MdInline; + public record MdItalic(List Content) : MdInline; + public record MdUnderline(List Content) : MdInline; + + public static MdDocument Parse(string markdown) + { + List blocks = []; + List listBuffer = []; + + string[] lines = markdown.Replace("\r\n", "\n").Split('\n'); + + // Wenn listBuffer etwas enthält, dieses an blocks anhängen und listBuffer leeren + void FlushList() + { + if (listBuffer.Count > 0) + { + blocks.Add(new MdList(listBuffer.ToList())); + listBuffer.Clear(); + } + } + + foreach (string rawLine in lines) + { + string line = rawLine.TrimEnd(); + + if (string.IsNullOrWhiteSpace(line)) + { + FlushList(); + continue; + } + + // Horizontal rule + if (line.StartsWith("---")) + { + FlushList(); + blocks.Add(new MdHorizontalRule()); + continue; + } + + // Heading + if (line.StartsWith('#')) + { + FlushList(); + + int level = line.TakeWhile(c => c == '#').Count(); + string text = line[level..].Trim(); + + blocks.Add(new MdHeading(level, ParseInline(text))); + continue; + } + + // List item + if (line.StartsWith("- ")) + { + listBuffer.Add(new MdListItem(ParseInline(line[2..]))); + continue; + } + + // Paragraph + FlushList(); + blocks.Add(new MdParagraph(ParseInline(line))); + } + + FlushList(); + + return new MdDocument(blocks); + } + + private static List ParseInline(string text) + { + List result = []; + + int i = 0; + + while (i < text.Length) + { + // Bold ** + if (i + 1 < text.Length && text[i] == '*' && text[i + 1] == '*') + { + int end = text.IndexOf("**", i + 2); + if (end == -1) break; + + string inner = text[(i + 2)..end]; + result.Add(new MdBold(ParseInline(inner))); + + i = end + 2; + continue; + } + + // Italic * + if (text[i] == '*') + { + int end = text.IndexOf('*', i + 1); + if (end == -1) break; + + string inner = text[(i + 1)..end]; + result.Add(new MdItalic(ParseInline(inner))); + + i = end + 1; + continue; + } + + // Underline _ + if (i + 1 < text.Length && text[i] == '_') + { + int end = text.IndexOf("_", i + 1); + if (end == -1) break; + + string inner = text[(i + 1)..end]; + result.Add(new MdUnderline(ParseInline(inner))); + + i = end + 1; + continue; + } + + // Plain text + int nextSpecial = FindNextSpecial(text, i); + result.Add(new MdText(text[i..nextSpecial])); + i = nextSpecial; + } + + return result; + } + + private static int FindNextSpecial(string text, int start) + { + int[] candidates = [ text.IndexOf('*', start), text.IndexOf("_", start) ]; + + int min = int.MaxValue; + + foreach (var c in candidates) + if (c != -1 && c < min) + min = c; + + return min == int.MaxValue ? text.Length : min; + } + } + +} + diff --git a/BonNotiz/RawPrinter.cs b/BonNotiz/RawPrinter.cs index d0820d2..d32cf08 100644 --- a/BonNotiz/RawPrinter.cs +++ b/BonNotiz/RawPrinter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Runtime.InteropServices; -namespace KohaCompanion.Shared +namespace BonNotiz { /// /// Eine Klasse zum Kapseln eines Druckers. Mit der Methode