To determine the actual media sizes supported by a printer, you must call the EnumForms function from the Windows Printing API. This function enumerates all custom and standard forms (paper sizes) defined for a specific printer device. By parsing the returned FORM_INFO_1 structures, applications can accurately identify available paper dimensions without relying on static assumptions.
The Limitation of Static Assumptions
Many developers mistakenly assume that printers support only standard ISO or ANSI paper sizes, or they attempt to query generic driver capabilities that may not reflect the physical hardware installed. The Win32 API provides a robust mechanism to discover exactly what the printer driver exposes to the operating system. The key function for this task isEnumForms, which queries the print spooler for the list of forms associated with a given printer.
Using older or incorrect functions like EnumPrinters will not provide detailed form information. EnumPrinters returns general status and configuration data about the printer itself, such as its name, location, and driver type, but it does not enumerate the specific media sizes. To get the actual width and height of the paper, you must interact directly with the forms collection.
Allocating the Buffer Correctly
The most common source of errors when implementingEnumForms is incorrect buffer management. The function requires a pointer to a buffer that receives the form information. However, the size of this buffer is not known in advance because the number of supported forms varies by printer model and driver.
To handle this safely, you should perform a two-step process:
- First Call: Call
EnumFormswith aNULLbuffer and a zero-sized buffer length. Pass a variable to receive the required buffer size. This call fails withERROR_INSUFFICIENT_BUFFER, but it populates the required size parameter. - Second Call: Allocate a buffer of the calculated size using
LocalAllocorHeapAlloc. CallEnumFormsagain with the allocated buffer.
Parsing FORM_INFO_1 Structures
WhenEnumForms succeeds, it returns the number of forms copied into the buffer. Each element in the buffer is a FORM_INFO_1 structure. This structure contains critical fields for determining media size:
- Flags: Indicates if the form is standard or custom.
- Name: A string identifier for the form (e.g., "A4", "Letter", or a custom name).
- Size: A
SIZEstructure containing thecx(width) andcy(height) of the form.
cx and cy are in hundredths of a millimeter. To convert these values to inches or centimeters, you must divide by 100. For example, an A4 sheet is approximately 210mm wide and 297mm high, which corresponds to 21000 and 29700 in the SIZE structure respectively.
It is important to check the Flags field. Forms with the BMD_USER flag indicate user-defined custom sizes, while others are standard forms provided by the driver. Filtering these flags allows your application to distinguish between pre-set options and user-created configurations.
Implementation Example
Below is a simplified C++ snippet demonstrating the correct usage ofEnumForms. Note the use of LocalFree to release the allocated memory after processing.
BOOL EnumeratePrinterForms(LPCTSTR pszPrinterName) {
DWORD cbBuf = 0;
DWORD cbNeeded = 0;
DWORD cReturned = 0;
PFORM_INFO_1 pFormInfo = NULL;
// First call to get required buffer size
if (!EnumForms(pszPrinterName, 1, NULL, 0, &cbNeeded, &cReturned)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
return FALSE;
}
}
// Allocate buffer
pFormInfo = (PFORM_INFO_1)LocalAlloc(LPTR, cbNeeded);
if (pFormInfo == NULL) {
return FALSE;
}
// Second call to retrieve data
if (EnumForms(pszPrinterName, 1, pFormInfo, cbNeeded, &cbNeeded, &cReturned)) {
for (DWORD i = 0; i < cReturned; i++) {
// Access pFormInfo[i].Size.cx and .cy
// Check pFormInfo[i].Flags for BMD_USER
}
}
LocalFree(pFormInfo);
return TRUE;
}
If you encounter issues where the printer queue behaves unexpectedly after changing forms, ensure that the print spooler service is running correctly. You might also need to restart the spooler to clear cached form definitions. For more details on managing the print spooler, see our guide on [Print Spooler Service Hangs](/news/1454997/print-spooler-service-hangs).
Handling Driver Variations
Not all printer drivers populate theEnumForms output identically. Some PostScript drivers may return a large number of virtual forms, while basic USB printers might return only a few standard sizes. It is crucial to iterate through all returned forms rather than assuming a specific index exists.
Additionally, some modern drivers may expose media types via other APIs, such as DocumentProperties or the Print Ticket schema. However, EnumForms remains the most reliable method for retrieving raw dimensional data for legacy and COM-based printing applications. If you are troubleshooting margin issues on newer OS versions, be aware that Windows 11 may enforce stricter validation on form sizes. Refer to our article on [Windows 11 Home Laptop Prints Different Margins](/news/1129244/windows-11-printing-margin-issues) for context on how OS-level changes affect form handling.
Frequently Asked Questions
Why does EnumForms return an empty list?
An empty list usually indicates that the printer driver does not define any custom forms, or the printer is offline and disconnected from the spooler. Ensure the printer is online and that the driver is fully installed. Some minimal drivers may only report default sizes, so verify the driver version.
How do I convert the Size.cx value to inches?
The cx and cy values are in hundredths of a millimeter. To convert to inches, divide the value by 254 (since 1 inch = 25.4 mm = 254 hundredths of a mm). For example, 8500 / 254 equals approximately 33.46 inches.
Can I modify the forms list programmatically?
No, EnumForms is read-only. To add or delete forms, you must use the AddForm and DeleteForm functions. These require administrative privileges and typically involve setting up a FORM_INFO_1 structure with the desired dimensions before calling the modification API.
What is the difference between FORM_INFO_1 and FORM_INFO_2?
FORM_INFO_1 is the standard structure used by EnumForms and contains basic size and name information. FORM_INFO_2 is used by AddForm and includes additional fields for specifying color modes and bitmaps. Always use level 1 for enumeration to keep the code simple and compatible.
Does this work for network printers?
Yes, as long as you pass the correct printer name (e.g., \\Server\Printer) to EnumForms. The function queries the local spooler, which communicates with the remote printer's driver database. Network latency does not affect the accuracy of the form data retrieval.