CrossEscPos.Transports
1.3.2
dotnet add package CrossEscPos.Transports --version 1.3.2
NuGet\Install-Package CrossEscPos.Transports -Version 1.3.2
<PackageReference Include="CrossEscPos.Transports" Version="1.3.2" />
<PackageVersion Include="CrossEscPos.Transports" Version="1.3.2" />
<PackageReference Include="CrossEscPos.Transports" />
paket add CrossEscPos.Transports --version 1.3.2
#r "nuget: CrossEscPos.Transports, 1.3.2"
#:package CrossEscPos.Transports@1.3.2
#addin nuget:?package=CrossEscPos.Transports&version=1.3.2
#tool nuget:?package=CrossEscPos.Transports&version=1.3.2

CrossEscPos
A receipt printer emulator for testing ESC/POS, on Windows, macOS, Linux and in the browser. Built with Avalonia.
Key features • Download • Quick start • WPF to Avalonia • Docs • Live demo

Testing receipt printing usually means a real printer, a roll of paper and a lot of walking back and forth. CrossEscPos stands in for the printer: point your point-of-sale software at it over TCP, serial or USB, and each receipt prints on screen. Status queries get real answers, so you can also test how your software handles paper-out, an open cover or a cash drawer.
Avalonia Port Challenge entry. CrossEscPos is a cross-platform port of roydejong/EscPosEmulator, a Windows-only WPF app. See From WPF to Avalonia for before/after screenshots and what the migration cost.
Key features
- The connections a real printer has. TCP/IP (port 9100) and serial on the desktop; Web Serial, WebUSB and a TCP proxy in the browser.
- Real receipts. Text styles, 1D barcodes, 2D codes (QR, PDF417, DataMatrix, Aztec), bit images and page mode. See Supported commands.
- It talks back. Answers
DLE EOT,GS rand Automatic Status Back. The Printer state panel simulates paper-out, an open cover, the cash drawer, offline and error states, and like real hardware the printer drops jobs while it isn't ready. - A built-in test client. The Monitor prints sample jobs and shows the status your software would receive.
- Buzzer and cash drawer. Both signal with a sound and an on-screen toast.
- PNG export. Save every receipt in one image, or one file per cut.
- Embeddable. A headless core and NuGet packages with swappable render backends (SkiaSharp, or ImageSharp with no native dependencies).
Download
| Platform | Download |
|---|---|
| x64 (.zip) | |
| Apple silicon (.zip) · Intel (.zip) | |
| x64 (.tar.gz) | |
| Run it online, no install |
The links download the latest release. The
builds are self-contained, so you don't need .NET installed. The libraries are on NuGet as
CrossEscPos.*; see Packages.
The macOS apps aren't notarized yet. If macOS says CrossEscPos "is damaged and can't be opened", see Troubleshooting.
Quick start
Start the app. It listens on TCP port 9100 on all interfaces.
Send it a receipt from a terminal, or point your POS software at
localhost:9100as a network printer:printf 'Hello from CrossEscPos\n\n\n\035V\000' | nc -w 1 localhost 9100Click Open monitor… to print sample receipts, barcodes and QR codes, and flip the switches in the Printer state panel to see the status your software would receive.
To change the port, open a serial port, or test serial without hardware, see Connecting.
To render ESC/POS from your own .NET code, start with Getting started:
var printer = new ReceiptPrinter(PaperConfiguration.Default,
new SkiaImageFactory(), new SkiaTypefaceProvider());
printer.FeedEscPos(escPosBytes); // ESC/POS is binary: feed the raw bytes
using var image = printer.CurrentReceipt.Render(); // IReceiptImage
new SkiaImageEncoder().EncodePng(image, outputStream);
From WPF to Avalonia: what the migration cost
Entered in the Avalonia Port Challenge. The same write-up is on the project site, next to a live browser demo.
| Before: WPF, Windows only | After: Avalonia 12 on macOS |
|---|---|
![]() |
![]() |
| After: Linux | After: browser (WebAssembly) |
![]() |
![]() |
Starting point
The upstream app (roydejong/EscPosEmulator, last
updated July 2025) targeted net9.0-windows7.0 with UseWPF: 48 C#/XAML files, about 2,400 lines.
Windows was wired in at four levels:
- Rendering. Every receipt line drew itself with GDI+ (
System.Drawing.BitmapandGraphics), which is Windows-only on .NET 6 and later. The GDI+ types were part of the core interface,IReceiptPrintable.Render(Bitmap, Graphics, int, int). To show a receipt, the window saved each bitmap into a BMPMemoryStreamand loaded it back as a WPFBitmapImage. - UI. Code-behind only.
MainWindow.xaml.cscreatedImagecontrols by hand, found them again by a GUID-derivedName, and added or removed them from aStackPanel. - OS calls. A
user32!FlashWindowP/Invoke andSystem.Media.SystemSoundssignalled new jobs. - Assumptions. TCP was the only transport, and the test receipt was read from the current working directory.
The ESC/POS interpreter (one command class per opcode, registered in EscPosInterpreter) had no UI
or Windows dependency, so its design carried over unchanged. It is now the headless
CrossEscPos.Core package.
What changed, and what each part cost
| Area | WPF original | Avalonia port | What it took |
|---|---|---|---|
| Rendering | GDI+ Bitmap / Graphics |
SkiaSharp, later behind a backend-neutral IReceiptCanvas, plus a fully managed ImageSharp backend |
The biggest single job. The text-line renderer (styles, sizes, justification, underline) was rewritten. System fonts differ per OS, so the same receipt measured differently on each; embedding JetBrains Mono (OFL) made output identical everywhere. The ImageSharp backend later needed its advance widths matched to Skia's. |
| UI | XAML + code-behind | AXAML + MVVM (CommunityToolkit.Mvvm); receipts bound to a reusable ReceiptView control |
The markup ported almost line for line: Window, DockPanel, StackPanel and ScrollViewer all exist in Avalonia. The work was moving the code-behind into view models and bindings. |
| Win32 calls | FlashWindow, SystemSounds |
INotificationService: afplay on macOS, Console.Beep on Windows, paplay/aplay on Linux, plus an in-window toast |
Avalonia has no cross-platform system-sound API, so each OS gets its own strategy. |
| Files | Relative to the working directory | Avalonia StorageProvider for PNG export; app-relative asset paths |
The working-directory assumption broke in the packaged app and was fixed right after the first release. |
| Transports | TCP | TCP and serial (System.IO.Ports); the Monitor adds direct USB (libusb) |
Port names differ per OS (COM3, /dev/ttyUSB0, /dev/cu.*). A Homebrew-installed libusb wasn't found until the app added the usual install paths to NATIVE_DLL_SEARCH_DIRECTORIES at startup. |
| Packaging | One Windows .exe |
Self-contained win-x64, linux-x64, osx-x64 and osx-arm64 builds from one CI matrix; a script builds the macOS .app |
Unsigned macOS bundles were reported as "damaged", so the bundle is now ad-hoc signed. Notarization needs a paid Apple ID, so the README documents clearing the quarantine flag instead. |
| Browser | n/a | The same app as an Avalonia WASM head (net10.0-browser) |
Platform edges sit behind IPlatformServices. A browser can't listen on TCP, so an ASP.NET Core SignalR host opens the socket and relays jobs to the page. Serial and USB go through Web Serial and WebUSB via JS interop. The storage API's save picker failed in the browser, so export downloads a JS blob instead. |
The numbers
- Time: 7 days with commits over 5 weeks, per the git history. The port and desktop parity landed on June 5–6, 2026 (PRs #1–#7), the layered-package refactor on June 20–21 (#8–#10), and the browser head on July 4–7 (#11–#16).
- Size: from 48 files and ~2.4k lines to 168 files and ~10k lines of C# and AXAML, across 11 projects, 2 samples and 2 test projects. Most of the growth is new features (barcodes, 2D codes, status commands, printer-state simulation, the Monitor, serial/USB, the browser head), not port overhead. The port PR itself (#1) was +2,525 / −487 lines across 60 files.
- Tests: 131 xUnit tests (89 test methods). They run the interpreter against a synthetic render backend, which proves the core is headless, and exercise the controls with Avalonia.Headless.
- Tools: built with AI assistance (Claude Code); the commits carry
Co-Authored-Bytrailers.
What was easy, and what hurt
- Easy: XAML to AXAML, the Fluent theme,
Dispatcher.UIThread, and headless UI testing. The UI layer was the smallest part of the port. - Hurt: removing GDI+. It was part of the core interfaces, not just the view, so the renderer had to be redesigned before anything else could move. After that came per-OS native dependencies (libusb, fontconfig on Linux, macOS signing) and the browser sandbox (no sockets, no raw file system).
- Would do again: put the drawing surface behind an interface first. Once
IReceiptCanvasexisted, the ImageSharp backend (a community contribution) and the browser head each landed within a few days.
Documentation
The long-form guides live in docs/ and are published as the
wiki.
| Guide | What's in it |
|---|---|
| Using the app | The Monitor, the printer state panel, PNG export, choosing the render backend |
| Connecting | TCP and serial, environment variables, testing serial without hardware |
| Browser app | The browser version, Web Serial and WebUSB, and the SignalR host for TCP |
| Supported commands | The emulated printer, the ESC/POS it understands, and what's missing |
| Getting started and Packages | Using the NuGet libraries in your own app |
| Architecture | The packages, one app with two heads, the bundled font |
| Building and testing | Building from source, running the tests, how releases are published |
Troubleshooting
macOS says the app "is damaged and can't be opened"
The .app is ad-hoc signed but not notarized, so macOS quarantines it after download. Clear the
quarantine flag once, then open it:
xattr -dr com.apple.quarantine /path/to/CrossEscPos.app
open /path/to/CrossEscPos.app
The app fails to start or text is missing on Linux
Install the font and rendering libraries if they're missing, for example on Debian or Ubuntu:
sudo apt install libfontconfig1 libfreetype6
Port 9100 is already in use
Pick another port in the TCP/IP panel and click Start, or start the app with
ESCPOS_TCP_PORT=9200. See Connecting for all the environment variables.
The Monitor can't find libusb (direct USB printing)
USB printing needs the native libusb library: brew install libusb on macOS, or
sudo apt install libusb-1.0-0 on Debian and Ubuntu. It ships with the Windows build. The operating
system must not already be holding the printer.
Web Serial or WebUSB says "unsupported" in the browser
Both APIs are only available in Chromium-based browsers such as Chrome and Edge, and only on HTTPS or
localhost. Receiving over TCP in the browser needs the relay host:
dotnet run --project samples/CrossEscPos.Host. See Browser app.
Limitations
ESC/POS is a large command set, and CrossEscPos covers the common part of it. Not implemented yet:
page-mode positioning, user-defined glyph substitution, MaxiCode and GS1 DataBar, the GS ( L graphics
commands, and Katakana/CJK code pages. See Not yet implemented
for the details, and expect gaps.
Contributing and support
- Found a bug, or a command that prints wrong? Open an issue.
Attach the ESC/POS bytes if you can: run the app with
ESCPOS_DEBUG_DUMP=1to save them. - Pull requests are welcome. New commands follow the
BaseCommandpattern described in Adding a command. Rundotnet test CrossEscPos.slnxbefore you open one; warnings fail the build. See Building and testing.
Credits and license
- The original emulator is EscPosEmulator by Roy de Jong, and all credit for its design goes to him and its contributors. Thanks to @yhonc9 for the ImageSharp render backend.
- Built with .NET 10, Avalonia 12, SkiaSharp, CommunityToolkit.Mvvm, ZXing.Net, QRCoder, Ardalis.SmartEnum, System.IO.Ports, ESC-POS-.NET (the Monitor) and LibUsbDotNet (direct USB printing).
- Released under the MIT License. Receipts are rendered with JetBrains Mono under the SIL Open Font License; see Fonts and license.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CrossEscPos.Core (>= 1.3.2)
- ESCPOS_NET (>= 3.0.0)
- LibUsbDotNet (>= 3.0.224)
- SixLabors.ImageSharp (>= 2.1.13)
- System.IO.Ports (>= 10.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.



