I'm developing a WPF application that needs to send print jobs to a USB-connected printer using custom paper sizes. The printer and its driver support this (verified using 3rd party software), but I can't get it to work programmatically.
Reverse engineering a working app:
Analyzed a third-party app that successfully handles this:
- Creates EMF-based
.SPL
spool files - No use of
SetPrinter()
orDocumentProperties()
- EMF includes
EMR_HEADER
defining custom page bounds - Uses
EMR_SETWORLDTRANSFORM
for layout/scaling
What I want to achieve:
- Render a WPF Canvas (or visual) into an EMF file
- Embed a non-standard paper size directly in the EMF metadata
- Send that EMF to the printer and have the driver respect the embedded size — without relying on
DEVMODE
Questions:
- How can I create a print job from a WPF Canvas and pass it to the printer with a custom paper size? The size will be set programmatically and will be within the allowable bounds set by the printers.
I am using an OKI B432 printer. Within the printer settings, there is a paper size option called 'User Defined Size'. When I select this, a dialog pops up allowing me to enter a specific size manually. This might be the paper size option I target unless it's bypassed completely.
Thanks in advance for any tips!
What I've tried:
- Printing directly via
PrintDialog
andXpsDocumentWriter
in WPF — the printer defaults to A4 and ignores custom sizes. - Setting
DEVMODE
parameters manually before printing — unreliable and varies by printer. - Using
System.Drawing.Printing
to generate EMF — but still couldn't enforce a custom size.
Code I have tried
EMFHelper.cs
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
public static class EMFHelper
{
public static void RenderCanvasToEMF(Canvas canvas, string emfPath, float widthMm, float heightMm)
{
int widthPx = (int)(canvas.ActualWidth);
int heightPx = (int)(canvas.ActualHeight);
using (Graphics refGraphics = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hdc = refGraphics.GetHdc();
var frameRect = new Rectangle(0, 0, (int)(widthMm * 100), (int)(heightMm * 100)); // hundredths of mm
using (Metafile metafile = new Metafile(emfPath, hdc, frameRect, MetafileFrameUnit.Millimeter))
using (Graphics g = Graphics.FromImage(metafile))
{
g.Clear(Color.White);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(widthPx, heightPx, 96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(canvas);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
using (Bitmap bitmap = new Bitmap(ms))
{
g.DrawImage(bitmap, 0, 0, widthPx, heightPx);
}
}
}
refGraphics.ReleaseHdc(hdc);
}
}
}
EMFPrinter.cs
using System.Drawing.Printing;
using System.Drawing;
public static class EMFPrinter
{
public static void PrintEMF(string emfPath, string printerName)
{
using (PrintDocument printDoc = new PrintDocument())
{
printDoc.PrinterSettings.PrinterName = printerName;
// 400mm ≈ 1574, 120mm ≈ 472 (hundredths of an inch)
PaperSize customSize = new PaperSize("User Defined Size", 1574, 472);
printDoc.DefaultPageSettings.PaperSize = customSize;
printDoc.DefaultPageSettings.Landscape = false;
printDoc.PrintPage += (sender, e) =>
{
using (Metafile mf = new Metafile(emfPath))
{
e.Graphics.DrawImage(mf, e.PageBounds);
}
};
printDoc.Print();
}
}
}
MainWindow.xaml.cs (Print Button)
private void PrintButton_Click(object sender, RoutedEventArgs e)
{
if (printerComboBox.SelectedItem is string printerName)
{
string emfPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "CanvasPrint.emf");
// 1. Render canvas to EMF with 400mm x 120mm
EMFHelper.RenderCanvasToEMF(myCanvas, emfPath, 400, 120);
// 2. Send EMF to printer
EMFPrinter.PrintEMF(emfPath, printerName);
MessageBox.Show("EMF sent to printer.");
}
else
{
MessageBox.Show("Please select a printer.");
}
}