Skip to content

Commit 060ff6c

Browse files
Copilotlaeubi
authored andcommitted
Add native PDF output support
Currently if one wants to create a PDF file it requires external libraries and as SWT does not allows an abstraction like Grahics2D in AWT one can not export real content of SWT components (e.g. Canvas) except exporting as an raster image or using some hacks. This now introduce a new PDFDocument to enable direct PDF generation from SWT widgets via Control.print(GC). This allows applications to export widget content to PDF files using the standard GC drawing API as well as even creating completely customized documents.
1 parent 26784bd commit 060ff6c

File tree

4 files changed

+184
-13
lines changed
  • bundles/org.eclipse.swt
    • Eclipse SWT PI/win32/org/eclipse/swt/internal/win32
    • Eclipse SWT Printing/win32/org/eclipse/swt/printing
    • Eclipse SWT/win32/org/eclipse/swt/widgets
  • examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets

4 files changed

+184
-13
lines changed

bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,18 @@ public class OS extends C {
337337
public static final int DM_COPIES = 0x00000100;
338338
public static final int DM_DUPLEX = 0x00001000;
339339
public static final int DM_ORIENTATION = 0x00000001;
340+
public static final int DM_PAPERSIZE = 0x00000002;
341+
public static final int DM_PAPERLENGTH = 0x00000004;
342+
public static final int DM_PAPERWIDTH = 0x00000008;
340343
public static final int DM_OUT_BUFFER = 2;
344+
public static final short DMPAPER_LETTER = 1;
345+
public static final short DMPAPER_LEGAL = 5;
346+
public static final short DMPAPER_EXECUTIVE = 7;
347+
public static final short DMPAPER_A3 = 8;
348+
public static final short DMPAPER_A4 = 9;
349+
public static final short DMPAPER_A5 = 11;
350+
public static final short DMPAPER_TABLOID = 3;
351+
public static final short DMPAPER_USER = 256;
341352
public static final short DMORIENT_PORTRAIT = 1;
342353
public static final short DMORIENT_LANDSCAPE = 2;
343354
public static final short DMDUP_SIMPLEX = 1;

bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java

Lines changed: 148 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ public class PDFDocument implements Drawable {
5757
boolean pageStarted = false;
5858
String filename;
5959

60+
/**
61+
* Width of the page in device-independent units
62+
*/
63+
double width;
64+
65+
/**
66+
* Height of the page in device-independent units
67+
*/
68+
double height;
69+
6070
/**
6171
* Width of the page in points (1/72 inch)
6272
*/
@@ -69,6 +79,74 @@ public class PDFDocument implements Drawable {
6979

7080
/** The name of the Microsoft Print to PDF printer */
7181
private static final String PDF_PRINTER_NAME = "Microsoft Print to PDF";
82+
83+
/** Helper class to represent a paper size with orientation */
84+
private static class PaperSize {
85+
int paperSizeConstant;
86+
int orientation;
87+
double widthInInches;
88+
double heightInInches;
89+
double wastedArea;
90+
91+
PaperSize(int paperSize, int orientation, double width, double height, double wasted) {
92+
this.paperSizeConstant = paperSize;
93+
this.orientation = orientation;
94+
this.widthInInches = width;
95+
this.heightInInches = height;
96+
this.wastedArea = wasted;
97+
}
98+
}
99+
100+
/**
101+
* Finds the best matching standard paper size for the given dimensions.
102+
* Tries both portrait and landscape orientations and selects the one that
103+
* minimizes wasted space while ensuring the content fits.
104+
*/
105+
private static PaperSize findBestPaperSize(double widthInInches, double heightInInches) {
106+
// Common paper sizes (width x height in inches, portrait orientation)
107+
int[][] standardSizes = {
108+
{OS.DMPAPER_LETTER, 850, 1100}, // 8.5 x 11
109+
{OS.DMPAPER_LEGAL, 850, 1400}, // 8.5 x 14
110+
{OS.DMPAPER_A4, 827, 1169}, // 8.27 x 11.69
111+
{OS.DMPAPER_TABLOID, 1100, 1700}, // 11 x 17
112+
{OS.DMPAPER_A3, 1169, 1654}, // 11.69 x 16.54
113+
{OS.DMPAPER_EXECUTIVE, 725, 1050}, // 7.25 x 10.5
114+
{OS.DMPAPER_A5, 583, 827}, // 5.83 x 8.27
115+
};
116+
117+
PaperSize bestMatch = null;
118+
double minWaste = Double.MAX_VALUE;
119+
120+
for (int[] size : standardSizes) {
121+
double paperWidth = size[1] / 100.0;
122+
double paperHeight = size[2] / 100.0;
123+
124+
// Try portrait orientation
125+
if (widthInInches <= paperWidth && heightInInches <= paperHeight) {
126+
double waste = (paperWidth * paperHeight) - (widthInInches * heightInInches);
127+
if (waste < minWaste) {
128+
minWaste = waste;
129+
bestMatch = new PaperSize(size[0], OS.DMORIENT_PORTRAIT, paperWidth, paperHeight, waste);
130+
}
131+
}
132+
133+
// Try landscape orientation (swap width and height)
134+
if (widthInInches <= paperHeight && heightInInches <= paperWidth) {
135+
double waste = (paperHeight * paperWidth) - (widthInInches * heightInInches);
136+
if (waste < minWaste) {
137+
minWaste = waste;
138+
bestMatch = new PaperSize(size[0], OS.DMORIENT_LANDSCAPE, paperHeight, paperWidth, waste);
139+
}
140+
}
141+
}
142+
143+
// Default to Letter if no match found
144+
if (bestMatch == null) {
145+
bestMatch = new PaperSize(OS.DMPAPER_LETTER, OS.DMORIENT_PORTRAIT, 8.5, 11.0, 0);
146+
}
147+
148+
return bestMatch;
149+
}
72150

73151
/**
74152
* Constructs a new PDFDocument with the specified filename and page dimensions.
@@ -77,8 +155,8 @@ public class PDFDocument implements Drawable {
77155
* </p>
78156
*
79157
* @param filename the path to the PDF file to create
80-
* @param widthInPoints the width of each page in points (1/72 inch)
81-
* @param heightInPoints the height of each page in points (1/72 inch)
158+
* @param width the width of each page in device-independent units
159+
* @param height the height of each page in device-independent units
82160
*
83161
* @exception IllegalArgumentException <ul>
84162
* <li>ERROR_NULL_ARGUMENT - if filename is null</li>
@@ -90,8 +168,8 @@ public class PDFDocument implements Drawable {
90168
*
91169
* @see #dispose()
92170
*/
93-
public PDFDocument(String filename, double widthInPoints, double heightInPoints) {
94-
this(null, filename, widthInPoints, heightInPoints);
171+
public PDFDocument(String filename, double width, double height) {
172+
this(null, filename, width, height);
95173
}
96174

97175
/**
@@ -103,8 +181,8 @@ public PDFDocument(String filename, double widthInPoints, double heightInPoints)
103181
*
104182
* @param device the device to associate with this PDFDocument
105183
* @param filename the path to the PDF file to create
106-
* @param widthInPoints the width of each page in points (1/72 inch)
107-
* @param heightInPoints the height of each page in points (1/72 inch)
184+
* @param width the width of each page in device-independent units
185+
* @param height the height of each page in device-independent units
108186
*
109187
* @exception IllegalArgumentException <ul>
110188
* <li>ERROR_NULL_ARGUMENT - if filename is null</li>
@@ -116,13 +194,13 @@ public PDFDocument(String filename, double widthInPoints, double heightInPoints)
116194
*
117195
* @see #dispose()
118196
*/
119-
public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) {
197+
public PDFDocument(Device device, String filename, double width, double height) {
120198
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
121-
if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
199+
if (width <= 0 || height <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
122200

123201
this.filename = filename;
124-
this.widthInPoints = widthInPoints;
125-
this.heightInPoints = heightInPoints;
202+
this.width = width;
203+
this.height = height;
126204

127205
// Get device from the current display if not provided
128206
if (device == null) {
@@ -135,6 +213,23 @@ public PDFDocument(Device device, String filename, double widthInPoints, double
135213
this.device = device;
136214
}
137215

216+
// Calculate physical size in inches from screen pixels
217+
int screenDpiX = 96;
218+
int screenDpiY = 96;
219+
if (this.device != null) {
220+
Point dpi = this.device.getDPI();
221+
screenDpiX = dpi.x;
222+
screenDpiY = dpi.y;
223+
}
224+
double widthInInches = width / screenDpiX;
225+
double heightInInches = height / screenDpiY;
226+
227+
// Microsoft Print to PDF doesn't support custom page sizes
228+
// Find the best matching standard paper size
229+
PaperSize bestMatch = findBestPaperSize(widthInInches, heightInInches);
230+
this.widthInPoints = bestMatch.widthInInches * 72.0;
231+
this.heightInPoints = bestMatch.heightInInches * 72.0;
232+
138233
// Create printer DC for "Microsoft Print to PDF"
139234
TCHAR driver = new TCHAR(0, "WINSPOOL", true);
140235
TCHAR deviceName = new TCHAR(0, PDF_PRINTER_NAME, true);
@@ -149,6 +244,12 @@ public PDFDocument(Device device, String filename, double widthInPoints, double
149244
if (lpInitData != 0) {
150245
int rc = OS.DocumentProperties(0, hPrinter[0], deviceName, lpInitData, 0, OS.DM_OUT_BUFFER);
151246
if (rc == OS.IDOK) {
247+
DEVMODE devmode = new DEVMODE();
248+
OS.MoveMemory(devmode, lpInitData, DEVMODE.sizeof);
249+
devmode.dmPaperSize = (short) bestMatch.paperSizeConstant;
250+
devmode.dmOrientation = (short) bestMatch.orientation;
251+
devmode.dmFields = OS.DM_PAPERSIZE | OS.DM_ORIENTATION;
252+
OS.MoveMemory(lpInitData, devmode, DEVMODE.sizeof);
152253
handle = OS.CreateDC(driver, deviceName, 0, lpInitData);
153254
}
154255
OS.HeapFree(hHeap, 0, lpInitData);
@@ -322,6 +423,43 @@ public long internal_new_GC(GCData data) {
322423
data.font = device.getSystemFont();
323424
}
324425
}
426+
427+
// Set up coordinate system scaling
428+
// Get screen DPI
429+
int screenDpiX = 96;
430+
int screenDpiY = 96;
431+
if (device != null) {
432+
Point dpi = device.getDPI();
433+
screenDpiX = dpi.x;
434+
screenDpiY = dpi.y;
435+
}
436+
437+
// Get PDF printer DPI
438+
int pdfDpiX = OS.GetDeviceCaps(handle, OS.LOGPIXELSX);
439+
int pdfDpiY = OS.GetDeviceCaps(handle, OS.LOGPIXELSY);
440+
441+
// Calculate content size in inches (what user wanted)
442+
double contentWidthInInches = width / screenDpiX;
443+
double contentHeightInInches = height / screenDpiY;
444+
445+
// Calculate scale factor to fit content to page
446+
// The page size is the physical paper size we selected
447+
double pageWidthInInches = widthInPoints / 72.0;
448+
double pageHeightInInches = heightInPoints / 72.0;
449+
double scaleToFitWidth = pageWidthInInches / contentWidthInInches;
450+
double scaleToFitHeight = pageHeightInInches / contentHeightInInches;
451+
452+
// Use the smaller scale to ensure both width and height fit
453+
double scaleToFit = Math.min(scaleToFitWidth, scaleToFitHeight);
454+
455+
// Combined scale: fit-to-page * DPI conversion
456+
float scaleX = (float)(scaleToFit * pdfDpiX / screenDpiX);
457+
float scaleY = (float)(scaleToFit * pdfDpiY / screenDpiY);
458+
459+
OS.SetGraphicsMode(handle, OS.GM_ADVANCED);
460+
float[] transform = new float[] {scaleX, 0, 0, scaleY, 0, 0};
461+
OS.SetWorldTransform(handle, transform);
462+
325463
isGCCreated = true;
326464
return handle;
327465
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,29 @@ public boolean print (GC gc) {
13501350
checkWidget ();
13511351
if (gc == null) error (SWT.ERROR_NULL_ARGUMENT);
13521352
if (gc.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
1353-
return false;
1353+
// Print only the client area (children) without shell decorations
1354+
forceResize ();
1355+
Control [] children = _getChildren ();
1356+
long gdipGraphics = gc.getGCData().gdipGraphics;
1357+
for (Control child : children) {
1358+
Rectangle bounds = child.getBounds();
1359+
if (gdipGraphics != 0) {
1360+
// For GDI+, translate the graphics object
1361+
org.eclipse.swt.internal.gdip.Gdip.Graphics_TranslateTransform(gdipGraphics, bounds.x, bounds.y, org.eclipse.swt.internal.gdip.Gdip.MatrixOrderPrepend);
1362+
child.print(gc);
1363+
org.eclipse.swt.internal.gdip.Gdip.Graphics_TranslateTransform(gdipGraphics, -bounds.x, -bounds.y, org.eclipse.swt.internal.gdip.Gdip.MatrixOrderPrepend);
1364+
} else {
1365+
// For GDI, modify the world transform to add translation
1366+
int state = OS.SaveDC(gc.handle);
1367+
// Create a translation transform matrix
1368+
float[] translateMatrix = new float[] {1, 0, 0, 1, bounds.x, bounds.y};
1369+
// Multiply (prepend) the translation to the existing transform
1370+
OS.ModifyWorldTransform(gc.handle, translateMatrix, OS.MWT_LEFTMULTIPLY);
1371+
child.print(gc);
1372+
OS.RestoreDC(gc.handle, state);
1373+
}
1374+
}
1375+
return true;
13541376
}
13551377

13561378
@Override

examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet388.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ public static void main(String[] args) {
210210
String tempDir = System.getProperty("java.io.tmpdir");
211211
String pdfPath = tempDir + "/swt_graphics_demo.pdf";
212212

213-
Rectangle shellBounds = shell.getBounds();
214-
PDFDocument pdf = new PDFDocument(pdfPath, shellBounds.width, shellBounds.height);
213+
Rectangle clientArea = shell.getClientArea();
214+
PDFDocument pdf = new PDFDocument(pdfPath, clientArea.width, clientArea.height);
215215
GC gc = new GC(pdf);
216216
shell.print(gc);
217217
gc.drawString("Exported to PDF...", 0, 0, true);

0 commit comments

Comments
 (0)