Writing nice receipt in C# WPF for printing on thermal printer POS

In the past when doing this I split up the receipt into separate parts that used different fonts or alignments such as Header, Body, Footer.

I used the following class layout to encapsulate my printed text definition. (where you get the Font from and how you manage its lifetime is up to you)

public class PrintText
{
    public PrintText(string text, Font font) : this(text, font, new StringFormat()) {}

    public PrintText(string text, Font font, StringFormat stringFormat)
    {
        Text = text;
        Font = font;
        StringFormat = stringFormat;
    }

    public string Text { get; set; }

    public Font Font { get; set; }

    /// <summary> Default is horizontal string formatting </summary>
    public StringFormat StringFormat { get; set; }
}

When there are longer lists of texts using the same font & padding then using a stringbuilder to build up your text makes life easy so you get a visual of how it will look just from inspecting your code.

If you had static text you can fit it all together as so:

var sb = new StringBuilder();
sb.AppendLine("Start of receipt");
sb.AppendLine("================");
sb.AppendLine("Item 1");
sb.AppendLine("Item 2");
sb.AppendLine("================");

Or if the data is a bit dynamic pass in some object you can iterate over and append your formatted text:

private class ReceiptItem
{
    public string Name { get; set; }

    public decimal Cost { get; set; }

    public int Amount { get; set; }

    public int Discount { get; set; }

    public decimal Total { get { return Cost * Amount; } }
}
const int FIRST_COL_PAD = 20;
const int SECOND_COL_PAD = 7;
const int THIRD_COL_PAD = 20;

var sb = new StringBuilder();
sb.AppendLine("Start of receipt");
sb.AppendLine("================");

foreach (var item in receiptItems)
{
    sb.Append(item.Name.PadRight(FIRST_COL_PAD));

    var breakDown = item.Amount > 0 ? item.Amount + "x" + item.Cost : string.Empty;
    sb.Append(breakDown.PadRight(SECOND_COL_PAD));

    sb.AppendLine(string.Format("{0:0.00} A", item.Total).PadLeft(THIRD_COL_PAD));

    if (item.Discount > 0)
    {
        sb.Append(string.Format("DISCOUNT {0:D2}%", item.Discount).PadRight(FIRST_COL_PAD + SECOND_COL_PAD));
        sb.Append(string.Format("{0:0.00} A", -(item.Total / 100 * item.Discount)).PadLeft(THIRD_COL_PAD));
        sb.AppendLine();
    }
}

sb.AppendLine("================");

The output will look like:

Start of receipt
================
Joes Food           1x10      10.00 A
DISCOUNT 10%                  -1.00 A
Fun Facts           1x20      20.00 A
DISCOUNT 15%                  -3.00 A
Bag of Sand         7x40     280.00 A
================

Using the PrintText class earlier we can store our nicely formatted string builder output

var printText = new PrintText(sb.ToString(), new Font("Monospace Please...", 8));

Then finally use that when attempting to draw the string

var layoutArea = new SizeF(AvailableWidth, 0);
SizeF stringSize = g.MeasureString(printText.Text, printText.Font, layoutArea, printText.StringFormat);

RectangleF rectf = new RectangleF(new PointF(), new SizeF(AvailableWidth, stringSize.Height));

g.DrawString(printText.Text, printText.Font, Brushes.Black, rectf, printText.StringFormat);

You can also play around with a few different graphical tweaks if the text doesn't print quite right such as:

g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;

I have designed a simple and smooth receipt design, i hope it will help you.

public class PrintJob
{
    private PrintDocument PrintDocument;
    private Graphics graphics;
    private Order order { set; get; }
    private Shop shop { set; get; }
    private int InitialHeight = 360;
    public PrintJob(Order order, Shop shop)
    {
        this.order = order;
        this.shop = shop;
        AdjustHeight();
    }
    private void AdjustHeight()
    {
        var capacity = 5 * order.ItemTransactions.Capacity;
        InitialHeight += capacity;

        capacity = 5 * order.DealTransactions.Capacity;
        InitialHeight += capacity;
    }
    public void Print(string printername)
    {
        PrintDocument = new PrintDocument();
        PrintDocument.PrinterSettings.PrinterName = printername;

        PrintDocument.PrintPage += new PrintPageEventHandler(FormatPage);
        PrintDocument.Print();
    }
    void DrawAtStart(string text, int Offset)
    {
        int startX = 10;
        int startY = 5;
        Font minifont = new Font("Arial", 5);

        graphics.DrawString(text, minifont,
                 new SolidBrush(Color.Black), startX + 5, startY + Offset);
    }
    void InsertItem(string key, string value, int Offset)
    {
        Font minifont = new Font("Arial", 5);
        int startX = 10;
        int startY = 5;

        graphics.DrawString(key, minifont,
                     new SolidBrush(Color.Black), startX + 5, startY + Offset);

        graphics.DrawString(value, minifont,
                 new SolidBrush(Color.Black), startX + 130, startY + Offset);
    }
    void InsertHeaderStyleItem(string key, string value, int Offset)
    {
        int startX = 10;
        int startY = 5;
        Font itemfont = new Font("Arial", 6, FontStyle.Bold);

        graphics.DrawString(key, itemfont,
                     new SolidBrush(Color.Black), startX + 5, startY + Offset);

        graphics.DrawString(value, itemfont,
                 new SolidBrush(Color.Black), startX + 130, startY + Offset);
    }
    void DrawLine(string text, Font font, int Offset, int xOffset)
    {
        int startX = 10;
        int startY = 5;
        graphics.DrawString(text, font,
                 new SolidBrush(Color.Black), startX + xOffset, startY + Offset);
    }
    void DrawSimpleString(string text, Font font, int Offset, int xOffset)
    {
        int startX = 10;
        int startY = 5;
        graphics.DrawString(text, font,
                 new SolidBrush(Color.Black), startX + xOffset, startY + Offset);
    }
    private void FormatPage(object sender, PrintPageEventArgs e)
    {
        graphics = e.Graphics;
        Font minifont = new Font("Arial", 5);
        Font itemfont = new Font("Arial", 6);
        Font smallfont = new Font("Arial", 8);
        Font mediumfont = new Font("Arial", 10);
        Font largefont = new Font("Arial", 12);
        int Offset = 10;
        int smallinc = 10, mediuminc = 12, largeinc = 15;

        //Image image = Resources.logo;
        //e.Graphics.DrawImage(image, startX + 50, startY + Offset, 100, 30);

        //graphics.DrawString("Welcome to HOT AND CRISPY", smallfont,
        //      new SolidBrush(Color.Black), startX + 22, startY + Offset);

        Offset = Offset + largeinc + 10;

        String underLine = "-------------------------------------";
        DrawLine(underLine, largefont, Offset, 0);

        Offset = Offset + mediuminc;
        DrawAtStart("Invoice Number: " + order.Invoice, Offset);

        if (!String.Equals(order.Customer.Address, "N/A"))
        {
            Offset = Offset + mediuminc;
            DrawAtStart("Address: " + order.Customer.Address, Offset);
        }

        if (!String.Equals(order.Customer.Phone, "N/A"))
        {
            Offset = Offset + mediuminc;
            DrawAtStart("Phone # : " + order.Customer.Phone, Offset);
        }

        Offset = Offset + mediuminc;
        DrawAtStart("Date: " + order.Date, Offset);

        Offset = Offset + smallinc;
        underLine = "-------------------------";
        DrawLine(underLine, largefont, Offset, 30);

        Offset = Offset + largeinc;

        InsertHeaderStyleItem("Name. ", "Price. ", Offset);

        Offset = Offset + largeinc;
        foreach (var itran in order.ItemTransactions)
        {
            InsertItem(itran.Item.Name + " x " + itran.Quantity, itran.Total.CValue, Offset);
            Offset = Offset + smallinc;
        }
        foreach (var dtran in order.DealTransactions)
        {
            InsertItem(dtran.Deal.Name, dtran.Total.CValue, Offset);
            Offset = Offset + smallinc;

            foreach (var di in dtran.Deal.DealItems)
            {
                InsertItem(di.Item.Name + " x " + (dtran.Quantity * di.Quantity), "", Offset);
                Offset = Offset + smallinc;
            }
        }

        underLine = "-------------------------";
        DrawLine(underLine, largefont, Offset, 30);

        Offset = Offset + largeinc;
        InsertItem(" Net. Total: ", order.Total.CValue, Offset);

        if (!order.Cash.Discount.IsZero())
        {
            Offset = Offset + smallinc;
            InsertItem(" Discount: ", order.Cash.Discount.CValue, Offset);
        }

        Offset = Offset + smallinc;
        InsertHeaderStyleItem(" Amount Payable: ", order.GrossTotal.CValue, Offset);

        Offset = Offset + largeinc;
        String address = shop.Address;
        DrawSimpleString("Address: " + address, minifont, Offset, 15);

        Offset = Offset + smallinc;
        String number = "Tel: " + shop.Phone1 + " - OR - " + shop.Phone2;
        DrawSimpleString(number, minifont, Offset, 35);

        Offset = Offset + 7;
        underLine = "-------------------------------------";
        DrawLine(underLine, largefont, Offset, 0);

        Offset = Offset + mediuminc;
        String greetings = "Thanks for visiting us.";
        DrawSimpleString(greetings, mediumfont, Offset, 28);

        Offset = Offset + mediuminc;
        underLine = "-------------------------------------";
        DrawLine(underLine, largefont, Offset, 0);

        Offset += (2 * mediuminc);
        string tip = "TIP: -----------------------------";
        InsertItem(tip, "", Offset);

        Offset = Offset + largeinc;
        string DrawnBy = "Meganos Softwares: 0312-0459491 - OR - 0321-6228321";
        DrawSimpleString(DrawnBy, minifont, Offset, 15);
    }
}

enter image description here

Some code to add the image has been commented here due to our requirements you can add can add your logo at header, as you can see in second image.

enter image description here

Tags:

C#

Wpf

Printing