diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/.settings/.api_filters b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/.settings/.api_filters
index 10facecefd3..ca5e705a7eb 100644
--- a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/.settings/.api_filters
@@ -196,12 +196,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/.settings/.api_filters b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/.settings/.api_filters
index b60addbbb8e..3dcd19b8e19 100644
--- a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/.settings/.api_filters
@@ -196,12 +196,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.aarch64/.settings/.api_filters
index 74653d35cf0..74680b18800 100644
--- a/binaries/org.eclipse.swt.gtk.linux.aarch64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.gtk.linux.aarch64/.settings/.api_filters
@@ -204,12 +204,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.gtk.linux.loongarch64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.loongarch64/.settings/.api_filters
index 93bb4727d41..3a8f35fd3d1 100644
--- a/binaries/org.eclipse.swt.gtk.linux.loongarch64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.gtk.linux.loongarch64/.settings/.api_filters
@@ -204,12 +204,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.ppc64le/.settings/.api_filters
index 5b182ba15ae..fd5936a3709 100644
--- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/.settings/.api_filters
@@ -204,12 +204,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.riscv64/.settings/.api_filters
index 7e7b49ce111..d6e1e5dcb9e 100644
--- a/binaries/org.eclipse.swt.gtk.linux.riscv64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.gtk.linux.riscv64/.settings/.api_filters
@@ -204,12 +204,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters
index ff22ad7af3f..a9011033bff 100644
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters
@@ -204,12 +204,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/.settings/.api_filters b/binaries/org.eclipse.swt.win32.win32.aarch64/.settings/.api_filters
index e32b4d5cd2e..f532b07786b 100644
--- a/binaries/org.eclipse.swt.win32.win32.aarch64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.win32.win32.aarch64/.settings/.api_filters
@@ -315,12 +315,4 @@
-
-
-
-
-
-
-
-
diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/.settings/.api_filters b/binaries/org.eclipse.swt.win32.win32.x86_64/.settings/.api_filters
index dfb4da06adb..5460dfa4033 100644
--- a/binaries/org.eclipse.swt.win32.win32.x86_64/.settings/.api_filters
+++ b/binaries/org.eclipse.swt.win32.win32.x86_64/.settings/.api_filters
@@ -315,12 +315,4 @@
-
-
-
-
-
-
-
-
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c
index 514e744c809..2bd801b9181 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo.c
@@ -15,7 +15,7 @@
*
* IBM
* - Binding to permit interfacing between Cairo and SWT
- * - Copyright (C) 2005, 2022 IBM Corp. All Rights Reserved.
+ * - Copyright (C) 2005, 2025 IBM Corp. All Rights Reserved.
*
* ***** END LICENSE BLOCK ***** */
@@ -704,6 +704,30 @@ JNIEXPORT void JNICALL Cairo_NATIVE(cairo_1pattern_1set_1matrix)
}
#endif
+#ifndef NO_cairo_1pdf_1surface_1create
+JNIEXPORT jlong JNICALL Cairo_NATIVE(cairo_1pdf_1surface_1create)
+ (JNIEnv *env, jclass that, jbyteArray arg0, jdouble arg1, jdouble arg2)
+{
+ jbyte *lparg0=NULL;
+ jlong rc = 0;
+ Cairo_NATIVE_ENTER(env, that, cairo_1pdf_1surface_1create_FUNC);
+ if (arg0) if ((lparg0 = (*env)->GetByteArrayElements(env, arg0, NULL)) == NULL) goto fail;
+/*
+ rc = (jlong)cairo_pdf_surface_create((const char *)lparg0, arg1, arg2);
+*/
+ {
+ Cairo_LOAD_FUNCTION(fp, cairo_pdf_surface_create)
+ if (fp) {
+ rc = (jlong)((jlong (CALLING_CONVENTION*)(const char *, jdouble, jdouble))fp)((const char *)lparg0, arg1, arg2);
+ }
+ }
+fail:
+ if (arg0 && lparg0) (*env)->ReleaseByteArrayElements(env, arg0, lparg0, 0);
+ Cairo_NATIVE_EXIT(env, that, cairo_1pdf_1surface_1create_FUNC);
+ return rc;
+}
+#endif
+
#ifndef NO_cairo_1pdf_1surface_1set_1size
JNIEXPORT void JNICALL Cairo_NATIVE(cairo_1pdf_1surface_1set_1size)
(JNIEnv *env, jclass that, jlong arg0, jdouble arg1, jdouble arg2)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h
index 3d314e25886..99408ca3154 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_custom.h
@@ -30,6 +30,7 @@
#define cairo_ps_surface_set_size_LIB LIB_CAIRO
#define cairo_surface_set_device_scale_LIB LIB_CAIRO
#define cairo_surface_get_device_scale_LIB LIB_CAIRO
+#define cairo_pdf_surface_create_LIB LIB_CAIRO
#ifdef CAIRO_HAS_XLIB_SURFACE
#define cairo_xlib_surface_get_height_LIB LIB_CAIRO
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h
index 23572bbca3b..9b79992a843 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/library/cairo_stats.h
@@ -15,7 +15,7 @@
*
* IBM
* - Binding to permit interfacing between Cairo and SWT
- * - Copyright (C) 2005, 2023 IBM Corp. All Rights Reserved.
+ * - Copyright (C) 2005, 2025 IBM Corp. All Rights Reserved.
*
* ***** END LICENSE BLOCK ***** */
@@ -86,6 +86,7 @@ typedef enum {
cairo_1pattern_1set_1extend_FUNC,
cairo_1pattern_1set_1filter_FUNC,
cairo_1pattern_1set_1matrix_FUNC,
+ cairo_1pdf_1surface_1create_FUNC,
cairo_1pdf_1surface_1set_1size_FUNC,
cairo_1pop_1group_1to_1source_FUNC,
cairo_1ps_1surface_1set_1size_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java
index 19ee94d0dec..bacc4d50b1d 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cairo/org/eclipse/swt/internal/cairo/Cairo.java
@@ -418,4 +418,13 @@ public class Cairo extends Platform {
*/
public static final native void memmove(double[] dest, long src, long size);
+/** Surface type constant for SVG */
+public static final int CAIRO_SURFACE_TYPE_SVG = 4;
+
+/**
+ * @method flags=dynamic
+ * @param filename cast=(const char *)
+ */
+public static final native long cairo_pdf_surface_create(byte[] filename, double width_in_points, double height_in_points);
+
}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c
index 0dd0bd6dd11..b29812ac759 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2022 IBM Corporation and others.
+ * Copyright (c) 2000, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -7,9 +7,6 @@
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
*******************************************************************************/
/* Note: This file was auto-generated by org.eclipse.swt.tools.internal.JNIGenerator */
@@ -1596,6 +1593,52 @@ JNIEXPORT void JNICALL OS_NATIVE(CGImageRelease)
}
#endif
+#ifndef NO_CGPDFContextBeginPage
+JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextBeginPage)
+ (JNIEnv *env, jclass that, jlong arg0, jlong arg1)
+{
+ OS_NATIVE_ENTER(env, that, CGPDFContextBeginPage_FUNC);
+ CGPDFContextBeginPage((CGContextRef)arg0, (CFDictionaryRef)arg1);
+ OS_NATIVE_EXIT(env, that, CGPDFContextBeginPage_FUNC);
+}
+#endif
+
+#ifndef NO_CGPDFContextClose
+JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextClose)
+ (JNIEnv *env, jclass that, jlong arg0)
+{
+ OS_NATIVE_ENTER(env, that, CGPDFContextClose_FUNC);
+ CGPDFContextClose((CGContextRef)arg0);
+ OS_NATIVE_EXIT(env, that, CGPDFContextClose_FUNC);
+}
+#endif
+
+#ifndef NO_CGPDFContextCreateWithURL
+JNIEXPORT jlong JNICALL OS_NATIVE(CGPDFContextCreateWithURL)
+ (JNIEnv *env, jclass that, jlong arg0, jobject arg1, jlong arg2)
+{
+ CGRect _arg1, *lparg1=NULL;
+ jlong rc = 0;
+ OS_NATIVE_ENTER(env, that, CGPDFContextCreateWithURL_FUNC);
+ if (arg1) if ((lparg1 = getCGRectFields(env, arg1, &_arg1)) == NULL) goto fail;
+ rc = (jlong)CGPDFContextCreateWithURL((CFURLRef)arg0, (const CGRect *)lparg1, (CFDictionaryRef)arg2);
+fail:
+ if (arg1 && lparg1) setCGRectFields(env, arg1, lparg1);
+ OS_NATIVE_EXIT(env, that, CGPDFContextCreateWithURL_FUNC);
+ return rc;
+}
+#endif
+
+#ifndef NO_CGPDFContextEndPage
+JNIEXPORT void JNICALL OS_NATIVE(CGPDFContextEndPage)
+ (JNIEnv *env, jclass that, jlong arg0)
+{
+ OS_NATIVE_ENTER(env, that, CGPDFContextEndPage_FUNC);
+ CGPDFContextEndPage((CGContextRef)arg0);
+ OS_NATIVE_EXIT(env, that, CGPDFContextEndPage_FUNC);
+}
+#endif
+
#ifndef NO_CGPathAddCurveToPoint
JNIEXPORT void JNICALL OS_NATIVE(CGPathAddCurveToPoint)
(JNIEnv *env, jclass that, jlong arg0, jlong arg1, jdouble arg2, jdouble arg3, jdouble arg4, jdouble arg5, jdouble arg6, jdouble arg7)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h
index 3391ecb8cd1..f574e737cbc 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os_stats.h
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2023 IBM Corporation and others.
+ * Copyright (c) 2000, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -7,9 +7,6 @@
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
*******************************************************************************/
/* Note: This file was auto-generated by org.eclipse.swt.tools.internal.JNIGenerator */
@@ -117,6 +114,10 @@ typedef enum {
CGImageGetHeight_FUNC,
CGImageGetWidth_FUNC,
CGImageRelease_FUNC,
+ CGPDFContextBeginPage_FUNC,
+ CGPDFContextClose_FUNC,
+ CGPDFContextCreateWithURL_FUNC,
+ CGPDFContextEndPage_FUNC,
CGPathAddCurveToPoint_FUNC,
CGPathAddLineToPoint_FUNC,
CGPathApply_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java
index e125e46194e..fffa4837edc 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java
@@ -3198,6 +3198,25 @@ public static Selector getSelector (long value) {
* @param image cast=(CGImageRef)
*/
public static final native void CGImageRelease(long image);
+/**
+ * @param url cast=(CFURLRef)
+ * @param mediaBox cast=(const CGRect *)
+ * @param auxiliaryInfo cast=(CFDictionaryRef)
+ */
+public static final native long CGPDFContextCreateWithURL(long url, CGRect mediaBox, long auxiliaryInfo);
+/**
+ * @param context cast=(CGContextRef)
+ * @param pageInfo cast=(CFDictionaryRef)
+ */
+public static final native void CGPDFContextBeginPage(long context, long pageInfo);
+/**
+ * @param context cast=(CGContextRef)
+ */
+public static final native void CGPDFContextEndPage(long context);
+/**
+ * @param context cast=(CGContextRef)
+ */
+public static final native void CGPDFContextClose(long context);
/**
* @param path cast=(CGMutablePathRef)
* @param m cast=(CGAffineTransform*)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java
index 66b285598a3..798c7bf9ec4 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java
@@ -337,7 +337,18 @@ public class OS extends C {
public static final int DM_COPIES = 0x00000100;
public static final int DM_DUPLEX = 0x00001000;
public static final int DM_ORIENTATION = 0x00000001;
+ public static final int DM_PAPERSIZE = 0x00000002;
+ public static final int DM_PAPERLENGTH = 0x00000004;
+ public static final int DM_PAPERWIDTH = 0x00000008;
public static final int DM_OUT_BUFFER = 2;
+ public static final short DMPAPER_LETTER = 1;
+ public static final short DMPAPER_LEGAL = 5;
+ public static final short DMPAPER_EXECUTIVE = 7;
+ public static final short DMPAPER_A3 = 8;
+ public static final short DMPAPER_A4 = 9;
+ public static final short DMPAPER_A5 = 11;
+ public static final short DMPAPER_TABLOID = 3;
+ public static final short DMPAPER_USER = 256;
public static final short DMORIENT_PORTRAIT = 1;
public static final short DMORIENT_LANDSCAPE = 2;
public static final short DMDUP_SIMPLEX = 1;
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java
new file mode 100644
index 00000000000..ce7d51ae1d1
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java
@@ -0,0 +1,393 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Eclipse Platform Contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse Platform Contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.printing;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.internal.cocoa.*;
+
+/**
+ * Instances of this class are used to create PDF documents.
+ * Applications create a GC on a PDFDocument using new GC(pdfDocument)
+ * and then draw on the GC using the usual graphics calls.
+ *
+ * A PDFDocument object may be constructed by providing
+ * a filename and the page dimensions. After drawing is complete,
+ * the document must be disposed to finalize the PDF file.
+ *
+ * Application code must explicitly invoke the PDFDocument.dispose()
+ * method to release the operating system resources managed by each instance
+ * when those instances are no longer required.
+ *
+ *
+ * The following example demonstrates how to use PDFDocument:
+ *
+ *
+ * PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ * GC gc = new GC(pdf);
+ * gc.drawText("Hello, PDF!", 100, 100);
+ * gc.dispose();
+ * pdf.dispose();
+ *
+ *
+ * @see GC
+ * @since 3.133
+ */
+public class PDFDocument implements Drawable {
+ Device device;
+ long pdfContext;
+ NSGraphicsContext graphicsContext;
+ boolean isGCCreated = false;
+ boolean disposed = false;
+ boolean pageStarted = false;
+
+ /**
+ * Width of the page in points (1/72 inch)
+ */
+ double widthInPoints;
+
+ /**
+ * Height of the page in points (1/72 inch)
+ */
+ double heightInPoints;
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param filename the path to the PDF file to create
+ * @param widthInPoints the width of each page in points (1/72 inch)
+ * @param heightInPoints the height of each page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF context could not be created
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(String filename, double widthInPoints, double heightInPoints) {
+ this(null, filename, widthInPoints, heightInPoints);
+ }
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions,
+ * associated with the given device.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param device the device to associate with this PDFDocument
+ * @param filename the path to the PDF file to create
+ * @param widthInPoints the width of each page in points (1/72 inch)
+ * @param heightInPoints the height of each page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF context could not be created
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) {
+ if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ NSAutoreleasePool pool = null;
+ if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
+ try {
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+
+ // Get device from the current display if not provided
+ if (device == null) {
+ try {
+ this.device = org.eclipse.swt.widgets.Display.getDefault();
+ } catch (Exception e) {
+ this.device = null;
+ }
+ } else {
+ this.device = device;
+ }
+
+ // Create CFURL from the filename
+ NSString path = NSString.stringWith(filename);
+ NSURL fileURL = NSURL.fileURLWithPath(path);
+
+ // Create the PDF context with the media box
+ CGRect mediaBox = createMediaBox();
+
+ // Use CGPDFContextCreateWithURL
+ pdfContext = OS.CGPDFContextCreateWithURL(fileURL.id, mediaBox, 0);
+ if (pdfContext == 0) SWT.error(SWT.ERROR_NO_HANDLES);
+
+ // Create an NSGraphicsContext from the CGContext
+ graphicsContext = NSGraphicsContext.graphicsContextWithGraphicsPort(pdfContext, false);
+ if (graphicsContext == null) {
+ OS.CGContextRelease(pdfContext);
+ pdfContext = 0;
+ SWT.error(SWT.ERROR_NO_HANDLES);
+ }
+ graphicsContext.retain();
+ } finally {
+ if (pool != null) pool.release();
+ }
+ }
+
+ /**
+ * Creates a CGRect for the current page dimensions
+ */
+ private CGRect createMediaBox() {
+ CGRect mediaBox = new CGRect();
+ mediaBox.origin.x = 0;
+ mediaBox.origin.y = 0;
+ mediaBox.size.width = widthInPoints;
+ mediaBox.size.height = heightInPoints;
+ return mediaBox;
+ }
+
+ /**
+ * Ensures the first page has been started
+ */
+ private void ensurePageStarted() {
+ if (!pageStarted) {
+ OS.CGPDFContextBeginPage(pdfContext, 0);
+ pageStarted = true;
+ }
+ }
+
+ /**
+ * Starts a new page in the PDF document.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page. The new page will have
+ * the same dimensions as the initial page.
+ *
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ NSAutoreleasePool pool = null;
+ if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
+ try {
+ if (pageStarted) {
+ OS.CGPDFContextEndPage(pdfContext);
+ }
+ OS.CGPDFContextBeginPage(pdfContext, 0);
+ pageStarted = true;
+ } finally {
+ if (pool != null) pool.release();
+ }
+ }
+
+ /**
+ * Starts a new page in the PDF document with the specified dimensions.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page.
+ *
+ *
+ * @param widthInPoints the width of the new page in points (1/72 inch)
+ * @param heightInPoints the height of the new page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage(double widthInPoints, double heightInPoints) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+ newPage();
+ }
+
+ /**
+ * Returns the width of the current page in points.
+ *
+ * @return the width in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getWidth() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return widthInPoints;
+ }
+
+ /**
+ * Returns the height of the current page in points.
+ *
+ * @return the height in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getHeight() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return heightInPoints;
+ }
+
+ /**
+ * Invokes platform specific functionality to allocate a new GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param data the platform specific GC data
+ * @return the platform specific GC handle
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public long internal_new_GC(GCData data) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ NSAutoreleasePool pool = null;
+ if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
+ try {
+ ensurePageStarted();
+
+ // Set up current graphics context
+ NSGraphicsContext.static_saveGraphicsState();
+ NSGraphicsContext.setCurrentContext(graphicsContext);
+
+ if (data != null) {
+ int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
+ if ((data.style & mask) == 0) {
+ data.style |= SWT.LEFT_TO_RIGHT;
+ }
+ data.device = device;
+ data.flippedContext = graphicsContext;
+ data.restoreContext = true;
+ NSSize size = new NSSize();
+ size.width = widthInPoints;
+ size.height = heightInPoints;
+ data.size = size;
+ if (device != null) {
+ data.background = device.getSystemColor(SWT.COLOR_WHITE).handle;
+ data.foreground = device.getSystemColor(SWT.COLOR_BLACK).handle;
+ data.font = device.getSystemFont();
+ }
+ }
+ isGCCreated = true;
+ return graphicsContext.id;
+ } finally {
+ if (pool != null) pool.release();
+ }
+ }
+
+ /**
+ * Invokes platform specific functionality to dispose a GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param hDC the platform specific GC handle
+ * @param data the platform specific GC data
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public void internal_dispose_GC(long hDC, GCData data) {
+ NSAutoreleasePool pool = null;
+ if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
+ try {
+ // Only restore the graphics state if it hasn't been restored yet by uncheckGC()
+ if (data != null && data.restoreContext) {
+ NSGraphicsContext.static_restoreGraphicsState();
+ data.restoreContext = false;
+ }
+ if (data != null) isGCCreated = false;
+ } finally {
+ if (pool != null) pool.release();
+ }
+ }
+
+ /**
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public boolean isAutoScalable() {
+ return false;
+ }
+
+ /**
+ * Returns true if the PDFDocument has been disposed,
+ * and false otherwise.
+ *
+ * @return true when the PDFDocument is disposed and false otherwise
+ */
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ /**
+ * Disposes of the operating system resources associated with
+ * the PDFDocument. Applications must dispose of all PDFDocuments
+ * that they allocate.
+ *
+ * This method finalizes the PDF file and writes it to disk.
+ *
+ */
+ public void dispose() {
+ if (disposed) return;
+ disposed = true;
+
+ NSAutoreleasePool pool = null;
+ if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
+ try {
+ if (pdfContext != 0) {
+ if (pageStarted) {
+ OS.CGPDFContextEndPage(pdfContext);
+ }
+ OS.CGPDFContextClose(pdfContext);
+ OS.CGContextRelease(pdfContext);
+ pdfContext = 0;
+ }
+ if (graphicsContext != null) {
+ graphicsContext.release();
+ graphicsContext = null;
+ }
+ } finally {
+ if (pool != null) pool.release();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java
new file mode 100644
index 00000000000..9c76c25187b
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/gtk/org/eclipse/swt/printing/PDFDocument.java
@@ -0,0 +1,336 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Eclipse Platform Contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse Platform Contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.printing;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.internal.*;
+import org.eclipse.swt.internal.cairo.*;
+
+/**
+ * Instances of this class are used to create PDF documents.
+ * Applications create a GC on a PDFDocument using new GC(pdfDocument)
+ * and then draw on the GC using the usual graphics calls.
+ *
+ * A PDFDocument object may be constructed by providing
+ * a filename and the page dimensions. After drawing is complete,
+ * the document must be disposed to finalize the PDF file.
+ *
+ * Application code must explicitly invoke the PDFDocument.dispose()
+ * method to release the operating system resources managed by each instance
+ * when those instances are no longer required.
+ *
+ *
+ * The following example demonstrates how to use PDFDocument:
+ *
+ *
+ * PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ * GC gc = new GC(pdf);
+ * gc.drawText("Hello, PDF!", 100, 100);
+ * gc.dispose();
+ * pdf.dispose();
+ *
+ *
+ * @see GC
+ * @since 3.133
+ */
+public class PDFDocument implements Drawable {
+ Device device;
+ long surface;
+ long cairo;
+ boolean isGCCreated = false;
+ boolean disposed = false;
+
+ /**
+ * Width of the page in points (1/72 inch)
+ */
+ double widthInPoints;
+
+ /**
+ * Height of the page in points (1/72 inch)
+ */
+ double heightInPoints;
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param filename the path to the PDF file to create
+ * @param widthInPoints the width of each page in points (1/72 inch)
+ * @param heightInPoints the height of each page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF surface could not be created
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(String filename, double widthInPoints, double heightInPoints) {
+ if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+
+ byte[] filenameBytes = Converter.wcsToMbcs(filename, true);
+ surface = Cairo.cairo_pdf_surface_create(filenameBytes, widthInPoints, heightInPoints);
+ if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES);
+
+ cairo = Cairo.cairo_create(surface);
+ if (cairo == 0) {
+ Cairo.cairo_surface_destroy(surface);
+ surface = 0;
+ SWT.error(SWT.ERROR_NO_HANDLES);
+ }
+
+ // Get device from the current display or create a temporary one
+ try {
+ device = org.eclipse.swt.widgets.Display.getDefault();
+ } catch (Exception e) {
+ device = null;
+ }
+ }
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions,
+ * associated with the given device.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param device the device to associate with this PDFDocument
+ * @param filename the path to the PDF file to create
+ * @param widthInPoints the width of each page in points (1/72 inch)
+ * @param heightInPoints the height of each page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF surface could not be created
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(Device device, String filename, double widthInPoints, double heightInPoints) {
+ if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ this.device = device;
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+
+ byte[] filenameBytes = Converter.wcsToMbcs(filename, true);
+ surface = Cairo.cairo_pdf_surface_create(filenameBytes, widthInPoints, heightInPoints);
+ if (surface == 0) SWT.error(SWT.ERROR_NO_HANDLES);
+
+ cairo = Cairo.cairo_create(surface);
+ if (cairo == 0) {
+ Cairo.cairo_surface_destroy(surface);
+ surface = 0;
+ SWT.error(SWT.ERROR_NO_HANDLES);
+ }
+ }
+
+ /**
+ * Starts a new page in the PDF document.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page. The new page will have
+ * the same dimensions as the initial page.
+ *
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ Cairo.cairo_show_page(cairo);
+ }
+
+ /**
+ * Starts a new page in the PDF document with the specified dimensions.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page.
+ *
+ *
+ * @param widthInPoints the width of the new page in points (1/72 inch)
+ * @param heightInPoints the height of the new page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage(double widthInPoints, double heightInPoints) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ Cairo.cairo_show_page(cairo);
+ Cairo.cairo_pdf_surface_set_size(surface, widthInPoints, heightInPoints);
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+ }
+
+ /**
+ * Returns the width of the current page in points.
+ *
+ * @return the width in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getWidth() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return widthInPoints;
+ }
+
+ /**
+ * Returns the height of the current page in points.
+ *
+ * @return the height in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getHeight() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return heightInPoints;
+ }
+
+ /**
+ * Invokes platform specific functionality to allocate a new GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param data the platform specific GC data
+ * @return the platform specific GC handle
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public long internal_new_GC(GCData data) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ if (data != null) {
+ int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
+ if ((data.style & mask) == 0) {
+ data.style |= SWT.LEFT_TO_RIGHT;
+ }
+ data.device = device;
+ data.cairo = cairo;
+ data.width = (int) widthInPoints;
+ data.height = (int) heightInPoints;
+ if (device != null) {
+ data.foregroundRGBA = device.getSystemColor(SWT.COLOR_BLACK).handle;
+ data.backgroundRGBA = device.getSystemColor(SWT.COLOR_WHITE).handle;
+ data.font = device.getSystemFont();
+ } else {
+ // Fallback: create default colors manually using GdkRGBA values
+ data.foregroundRGBA = new org.eclipse.swt.internal.gtk.GdkRGBA();
+ data.foregroundRGBA.red = 0;
+ data.foregroundRGBA.green = 0;
+ data.foregroundRGBA.blue = 0;
+ data.foregroundRGBA.alpha = 1;
+ data.backgroundRGBA = new org.eclipse.swt.internal.gtk.GdkRGBA();
+ data.backgroundRGBA.red = 1;
+ data.backgroundRGBA.green = 1;
+ data.backgroundRGBA.blue = 1;
+ data.backgroundRGBA.alpha = 1;
+ }
+ }
+ isGCCreated = true;
+ return cairo;
+ }
+
+ /**
+ * Invokes platform specific functionality to dispose a GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param hDC the platform specific GC handle
+ * @param data the platform specific GC data
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public void internal_dispose_GC(long hDC, GCData data) {
+ if (data != null) isGCCreated = false;
+ }
+
+ /**
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public boolean isAutoScalable() {
+ return false;
+ }
+
+ /**
+ * Returns true if the PDFDocument has been disposed,
+ * and false otherwise.
+ *
+ * @return true when the PDFDocument is disposed and false otherwise
+ */
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ /**
+ * Disposes of the operating system resources associated with
+ * the PDFDocument. Applications must dispose of all PDFDocuments
+ * that they allocate.
+ *
+ * This method finalizes the PDF file and writes it to disk.
+ *
+ */
+ public void dispose() {
+ if (disposed) return;
+ disposed = true;
+
+ if (cairo != 0) {
+ Cairo.cairo_destroy(cairo);
+ cairo = 0;
+ }
+ if (surface != 0) {
+ Cairo.cairo_surface_finish(surface);
+ Cairo.cairo_surface_destroy(surface);
+ surface = 0;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java b/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java
new file mode 100644
index 00000000000..db15aa658f9
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java
@@ -0,0 +1,526 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Eclipse Platform Contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse Platform Contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.printing;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.internal.win32.*;
+
+/**
+ * Instances of this class are used to create PDF documents.
+ * Applications create a GC on a PDFDocument using new GC(pdfDocument)
+ * and then draw on the GC using the usual graphics calls.
+ *
+ * A PDFDocument object may be constructed by providing
+ * a filename and the page dimensions. After drawing is complete,
+ * the document must be disposed to finalize the PDF file.
+ *
+ * Application code must explicitly invoke the PDFDocument.dispose()
+ * method to release the operating system resources managed by each instance
+ * when those instances are no longer required.
+ *
+ *
+ * Note: On Windows, this class uses the built-in "Microsoft Print to PDF"
+ * printer which is available on Windows 10 and later.
+ *
+ *
+ * The following example demonstrates how to use PDFDocument:
+ *
+ *
+ * PDFDocument pdf = new PDFDocument("output.pdf", 612, 792); // Letter size in points
+ * GC gc = new GC(pdf);
+ * gc.drawText("Hello, PDF!", 100, 100);
+ * gc.dispose();
+ * pdf.dispose();
+ *
+ *
+ * @see GC
+ * @since 3.133
+ */
+public class PDFDocument implements Drawable {
+ Device device;
+ long handle;
+ boolean isGCCreated = false;
+ boolean disposed = false;
+ boolean jobStarted = false;
+ boolean pageStarted = false;
+ String filename;
+
+ /**
+ * Width of the page in device-independent units
+ */
+ double width;
+
+ /**
+ * Height of the page in device-independent units
+ */
+ double height;
+
+ /**
+ * Width of the page in points (1/72 inch)
+ */
+ double widthInPoints;
+
+ /**
+ * Height of the page in points (1/72 inch)
+ */
+ double heightInPoints;
+
+ /** The name of the Microsoft Print to PDF printer */
+ private static final String PDF_PRINTER_NAME = "Microsoft Print to PDF";
+
+ /** Helper class to represent a paper size with orientation */
+ private static class PaperSize {
+ int paperSizeConstant;
+ int orientation;
+ double widthInInches;
+ double heightInInches;
+
+ PaperSize(int paperSize, int orientation, double width, double height) {
+ this.paperSizeConstant = paperSize;
+ this.orientation = orientation;
+ this.widthInInches = width;
+ this.heightInInches = height;
+ }
+ }
+
+ /**
+ * Finds the best matching standard paper size for the given dimensions.
+ * Tries both portrait and landscape orientations and selects the one that
+ * minimizes wasted space while ensuring the content fits.
+ */
+ private static PaperSize findBestPaperSize(double widthInInches, double heightInInches) {
+ // Common paper sizes (width x height in inches, portrait orientation)
+ int[][] standardSizes = {
+ {OS.DMPAPER_LETTER, 850, 1100}, // 8.5 x 11
+ {OS.DMPAPER_LEGAL, 850, 1400}, // 8.5 x 14
+ {OS.DMPAPER_A4, 827, 1169}, // 8.27 x 11.69
+ {OS.DMPAPER_TABLOID, 1100, 1700}, // 11 x 17
+ {OS.DMPAPER_A3, 1169, 1654}, // 11.69 x 16.54
+ {OS.DMPAPER_EXECUTIVE, 725, 1050}, // 7.25 x 10.5
+ {OS.DMPAPER_A5, 583, 827}, // 5.83 x 8.27
+ };
+
+ PaperSize bestMatch = null;
+ double minWaste = Double.MAX_VALUE;
+
+ for (int[] size : standardSizes) {
+ double paperWidth = size[1] / 100.0;
+ double paperHeight = size[2] / 100.0;
+
+ // Try portrait orientation
+ if (widthInInches <= paperWidth && heightInInches <= paperHeight) {
+ double waste = (paperWidth * paperHeight) - (widthInInches * heightInInches);
+ if (waste < minWaste) {
+ minWaste = waste;
+ bestMatch = new PaperSize(size[0], OS.DMORIENT_PORTRAIT, paperWidth, paperHeight);
+ }
+ }
+
+ // Try landscape orientation (swap width and height)
+ if (widthInInches <= paperHeight && heightInInches <= paperWidth) {
+ double waste = (paperHeight * paperWidth) - (widthInInches * heightInInches);
+ if (waste < minWaste) {
+ minWaste = waste;
+ bestMatch = new PaperSize(size[0], OS.DMORIENT_LANDSCAPE, paperHeight, paperWidth);
+ }
+ }
+ }
+
+ // Default to Letter if no match found
+ if (bestMatch == null) {
+ bestMatch = new PaperSize(OS.DMPAPER_LETTER, OS.DMORIENT_PORTRAIT, 8.5, 11.0);
+ }
+
+ return bestMatch;
+ }
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param filename the path to the PDF file to create
+ * @param width the width of each page in device-independent units
+ * @param height the height of each page in device-independent units
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF printer is not available
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(String filename, double width, double height) {
+ this(null, filename, width, height);
+ }
+
+ /**
+ * Constructs a new PDFDocument with the specified filename and page dimensions,
+ * associated with the given device.
+ *
+ * You must dispose the PDFDocument when it is no longer required.
+ *
+ *
+ * @param device the device to associate with this PDFDocument
+ * @param filename the path to the PDF file to create
+ * @param width the width of each page in device-independent units
+ * @param height the height of each page in device-independent units
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_NULL_ARGUMENT - if filename is null
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTError
+ * - ERROR_NO_HANDLES - if the PDF printer is not available
+ *
+ *
+ * @see #dispose()
+ */
+ public PDFDocument(Device device, String filename, double width, double height) {
+ if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (width <= 0 || height <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ this.filename = filename;
+ this.width = width;
+ this.height = height;
+
+ // Get device from the current display if not provided
+ if (device == null) {
+ try {
+ this.device = org.eclipse.swt.widgets.Display.getDefault();
+ } catch (SWTException e) {
+ this.device = null;
+ }
+ } else {
+ this.device = device;
+ }
+
+ // Calculate physical size in inches from screen pixels
+ int screenDpiX = 96;
+ int screenDpiY = 96;
+ if (this.device != null) {
+ Point dpi = this.device.getDPI();
+ screenDpiX = dpi.x;
+ screenDpiY = dpi.y;
+ }
+ double widthInInches = width / screenDpiX;
+ double heightInInches = height / screenDpiY;
+
+ // Microsoft Print to PDF doesn't support custom page sizes
+ // Find the best matching standard paper size
+ PaperSize bestMatch = findBestPaperSize(widthInInches, heightInInches);
+ this.widthInPoints = bestMatch.widthInInches * 72.0;
+ this.heightInPoints = bestMatch.heightInInches * 72.0;
+
+ // Create printer DC for "Microsoft Print to PDF"
+ TCHAR driver = new TCHAR(0, "WINSPOOL", true);
+ TCHAR deviceName = new TCHAR(0, PDF_PRINTER_NAME, true);
+
+ // Get printer settings
+ long[] hPrinter = new long[1];
+ if (OS.OpenPrinter(deviceName, hPrinter, 0)) {
+ int dwNeeded = OS.DocumentProperties(0, hPrinter[0], deviceName, 0, 0, 0);
+ if (dwNeeded >= 0) {
+ long hHeap = OS.GetProcessHeap();
+ long lpInitData = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, dwNeeded);
+ if (lpInitData != 0) {
+ int rc = OS.DocumentProperties(0, hPrinter[0], deviceName, lpInitData, 0, OS.DM_OUT_BUFFER);
+ if (rc == OS.IDOK) {
+ DEVMODE devmode = new DEVMODE();
+ OS.MoveMemory(devmode, lpInitData, DEVMODE.sizeof);
+ devmode.dmPaperSize = (short) bestMatch.paperSizeConstant;
+ devmode.dmOrientation = (short) bestMatch.orientation;
+ devmode.dmFields = OS.DM_PAPERSIZE | OS.DM_ORIENTATION;
+ OS.MoveMemory(lpInitData, devmode, DEVMODE.sizeof);
+ handle = OS.CreateDC(driver, deviceName, 0, lpInitData);
+ }
+ OS.HeapFree(hHeap, 0, lpInitData);
+ }
+ }
+ OS.ClosePrinter(hPrinter[0]);
+ }
+
+ if (handle == 0) {
+ SWT.error(SWT.ERROR_NO_HANDLES);
+ }
+ }
+
+ /**
+ * Ensures the print job has been started
+ */
+ private void ensureJobStarted() {
+ if (!jobStarted) {
+ DOCINFO di = new DOCINFO();
+ di.cbSize = DOCINFO.sizeof;
+ long hHeap = OS.GetProcessHeap();
+
+ // Set output filename
+ TCHAR buffer = new TCHAR(0, filename, true);
+ int byteCount = buffer.length() * TCHAR.sizeof;
+ long lpszOutput = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
+ OS.MoveMemory(lpszOutput, buffer, byteCount);
+ di.lpszOutput = lpszOutput;
+
+ // Set document name
+ TCHAR docName = new TCHAR(0, "SWT PDF Document", true);
+ int docByteCount = docName.length() * TCHAR.sizeof;
+ long lpszDocName = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, docByteCount);
+ OS.MoveMemory(lpszDocName, docName, docByteCount);
+ di.lpszDocName = lpszDocName;
+
+ int rc = OS.StartDoc(handle, di);
+
+ OS.HeapFree(hHeap, 0, lpszOutput);
+ OS.HeapFree(hHeap, 0, lpszDocName);
+
+ if (rc <= 0) {
+ SWT.error(SWT.ERROR_NO_HANDLES);
+ }
+ jobStarted = true;
+ }
+ }
+
+ /**
+ * Ensures the current page has been started
+ */
+ private void ensurePageStarted() {
+ ensureJobStarted();
+ if (!pageStarted) {
+ OS.StartPage(handle);
+ pageStarted = true;
+ }
+ }
+
+ /**
+ * Starts a new page in the PDF document.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page. The new page will have
+ * the same dimensions as the initial page.
+ *
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (pageStarted) {
+ OS.EndPage(handle);
+ pageStarted = false;
+ }
+ ensurePageStarted();
+ }
+
+ /**
+ * Starts a new page in the PDF document with the specified dimensions.
+ *
+ * This method should be called after completing the content of one page
+ * and before starting to draw on the next page.
+ *
+ *
+ * Note: On Windows, changing page dimensions after the document
+ * has been started may not be fully supported by all printer drivers.
+ *
+ *
+ * @param widthInPoints the width of the new page in points (1/72 inch)
+ * @param heightInPoints the height of the new page in points (1/72 inch)
+ *
+ * @exception IllegalArgumentException
+ * - ERROR_INVALID_ARGUMENT - if width or height is not positive
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public void newPage(double widthInPoints, double heightInPoints) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (widthInPoints <= 0 || heightInPoints <= 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ this.widthInPoints = widthInPoints;
+ this.heightInPoints = heightInPoints;
+ newPage();
+ }
+
+ /**
+ * Returns the width of the current page in points.
+ *
+ * @return the width in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getWidth() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return widthInPoints;
+ }
+
+ /**
+ * Returns the height of the current page in points.
+ *
+ * @return the height in points (1/72 inch)
+ *
+ * @exception SWTException
+ * - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+ *
+ */
+ public double getHeight() {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return heightInPoints;
+ }
+
+ /**
+ * Invokes platform specific functionality to allocate a new GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param data the platform specific GC data
+ * @return the platform specific GC handle
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public long internal_new_GC(GCData data) {
+ if (disposed) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ if (isGCCreated) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ ensurePageStarted();
+
+ if (data != null) {
+ int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
+ if ((data.style & mask) != 0) {
+ data.layout = (data.style & SWT.RIGHT_TO_LEFT) != 0 ? OS.LAYOUT_RTL : 0;
+ } else {
+ data.style |= SWT.LEFT_TO_RIGHT;
+ }
+ data.device = device;
+ data.nativeZoom = 100;
+ if (device != null) {
+ data.font = device.getSystemFont();
+ }
+ }
+
+ // Set up coordinate system scaling
+ // Get screen DPI
+ int screenDpiX = 96;
+ int screenDpiY = 96;
+ if (device != null) {
+ Point dpi = device.getDPI();
+ screenDpiX = dpi.x;
+ screenDpiY = dpi.y;
+ }
+
+ // Get PDF printer DPI
+ int pdfDpiX = OS.GetDeviceCaps(handle, OS.LOGPIXELSX);
+ int pdfDpiY = OS.GetDeviceCaps(handle, OS.LOGPIXELSY);
+
+ // Calculate content size in inches (what user wanted)
+ double contentWidthInInches = width / screenDpiX;
+ double contentHeightInInches = height / screenDpiY;
+
+ // Calculate scale factor to fit content to page
+ // The page size is the physical paper size we selected
+ double pageWidthInInches = widthInPoints / 72.0;
+ double pageHeightInInches = heightInPoints / 72.0;
+ double scaleToFitWidth = pageWidthInInches / contentWidthInInches;
+ double scaleToFitHeight = pageHeightInInches / contentHeightInInches;
+
+ // Use the smaller scale to ensure both width and height fit
+ double scaleToFit = Math.min(scaleToFitWidth, scaleToFitHeight);
+
+ // Combined scale: fit-to-page * DPI conversion
+ float scaleX = (float)(scaleToFit * pdfDpiX / screenDpiX);
+ float scaleY = (float)(scaleToFit * pdfDpiY / screenDpiY);
+
+ OS.SetGraphicsMode(handle, OS.GM_ADVANCED);
+ float[] transform = new float[] {scaleX, 0, 0, scaleY, 0, 0};
+ OS.SetWorldTransform(handle, transform);
+
+ isGCCreated = true;
+ return handle;
+ }
+
+ /**
+ * Invokes platform specific functionality to dispose a GC handle.
+ *
+ * IMPORTANT: This method is not part of the public
+ * API for PDFDocument. It is marked public only so that it
+ * can be shared within the packages provided by SWT. It is not
+ * available on all platforms, and should never be called from
+ * application code.
+ *
+ *
+ * @param hDC the platform specific GC handle
+ * @param data the platform specific GC data
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public void internal_dispose_GC(long hDC, GCData data) {
+ if (data != null) isGCCreated = false;
+ }
+
+ /**
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ @Override
+ public boolean isAutoScalable() {
+ return false;
+ }
+
+ /**
+ * Returns true if the PDFDocument has been disposed,
+ * and false otherwise.
+ *
+ * @return true when the PDFDocument is disposed and false otherwise
+ */
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ /**
+ * Disposes of the operating system resources associated with
+ * the PDFDocument. Applications must dispose of all PDFDocuments
+ * that they allocate.
+ *
+ * This method finalizes the PDF file and writes it to disk.
+ *
+ */
+ public void dispose() {
+ if (disposed) return;
+ disposed = true;
+
+ if (handle != 0) {
+ if (pageStarted) {
+ OS.EndPage(handle);
+ }
+ if (jobStarted) {
+ OS.EndDoc(handle);
+ }
+ OS.DeleteDC(handle);
+ handle = 0;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java
index 5c7cc8e9936..45943a568e8 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java
@@ -1404,7 +1404,23 @@ public boolean print (GC gc) {
checkWidget ();
if (gc == null) error (SWT.ERROR_NULL_ARGUMENT);
if (gc.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
- return false;
+ // Print only the client area (children) without shell decorations
+ Control [] children = _getChildren ();
+ for (Control child : children) {
+ Rectangle bounds = child.getBounds();
+ // Save the graphics state before transforming
+ NSGraphicsContext.static_saveGraphicsState();
+ NSGraphicsContext.setCurrentContext(gc.handle);
+ // Create and apply translation transform for child's position
+ NSAffineTransform transform = NSAffineTransform.transform();
+ transform.translateXBy(bounds.x, bounds.y);
+ transform.concat();
+ // Print the child control
+ child.print(gc);
+ // Restore the graphics state
+ NSGraphicsContext.static_restoreGraphicsState();
+ }
+ return true;
}
@Override
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java
index 84c21d92193..f1005c72996 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java
@@ -1350,7 +1350,29 @@ public boolean print (GC gc) {
checkWidget ();
if (gc == null) error (SWT.ERROR_NULL_ARGUMENT);
if (gc.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
- return false;
+ // Print only the client area (children) without shell decorations
+ forceResize ();
+ Control [] children = _getChildren ();
+ long gdipGraphics = gc.getGCData().gdipGraphics;
+ for (Control child : children) {
+ Rectangle bounds = child.getBounds();
+ if (gdipGraphics != 0) {
+ // For GDI+, translate the graphics object
+ org.eclipse.swt.internal.gdip.Gdip.Graphics_TranslateTransform(gdipGraphics, bounds.x, bounds.y, org.eclipse.swt.internal.gdip.Gdip.MatrixOrderPrepend);
+ child.print(gc);
+ org.eclipse.swt.internal.gdip.Gdip.Graphics_TranslateTransform(gdipGraphics, -bounds.x, -bounds.y, org.eclipse.swt.internal.gdip.Gdip.MatrixOrderPrepend);
+ } else {
+ // For GDI, modify the world transform to add translation
+ int state = OS.SaveDC(gc.handle);
+ // Create a translation transform matrix
+ float[] translateMatrix = new float[] {1, 0, 0, 1, bounds.x, bounds.y};
+ // Multiply (prepend) the translation to the existing transform
+ OS.ModifyWorldTransform(gc.handle, translateMatrix, OS.MWT_LEFTMULTIPLY);
+ child.print(gc);
+ OS.RestoreDC(gc.handle, state);
+ }
+ }
+ return true;
}
@Override
diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet388.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet388.java
new file mode 100644
index 00000000000..889100d17c6
--- /dev/null
+++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet388.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Eclipse Platform Contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse Platform Contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.snippets;
+
+/*
+ * PDFDocument example snippet: create a shell with graphics and export to PDF
+ *
+ * For a list of all SWT example snippets see
+ * http://www.eclipse.org/swt/snippets/
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.printing.*;
+import org.eclipse.swt.program.*;
+import org.eclipse.swt.widgets.*;
+
+public class Snippet388 {
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display);
+ shell.setText("PDF Export Demo");
+ shell.setLayout(new BorderLayout());
+
+ Label titleLabel = new Label(shell, SWT.CENTER);
+ titleLabel.setText("SWT Graphics Demo");
+ titleLabel.setLayoutData(new BorderData(SWT.TOP));
+
+ Canvas canvas = new Canvas(shell, SWT.BORDER);
+ canvas.setLayoutData(new BorderData(SWT.CENTER));
+ canvas.addListener(SWT.Paint, e -> {
+ GC gc = e.gc;
+ Color red = display.getSystemColor(SWT.COLOR_RED);
+ Color blue = display.getSystemColor(SWT.COLOR_BLUE);
+ Color green = display.getSystemColor(SWT.COLOR_GREEN);
+ Color yellow = display.getSystemColor(SWT.COLOR_YELLOW);
+ Color black = display.getSystemColor(SWT.COLOR_BLACK);
+ Color darkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY);
+
+ int shapeSpacing = 200;
+ int shapeY = 20;
+ int shapeWidth = 100;
+ int shapeHeight = 80;
+ int textY = shapeY + shapeHeight + 5;
+
+ int x1 = 50;
+ gc.setBackground(red);
+ gc.fillRectangle(x1, shapeY, shapeWidth, shapeHeight);
+
+ int x2 = x1 + shapeSpacing;
+ gc.setForeground(blue);
+ gc.setLineWidth(3);
+ gc.drawRectangle(x2, shapeY, shapeWidth, shapeHeight);
+
+ int x3 = x2 + shapeSpacing;
+ gc.setBackground(green);
+ gc.fillOval(x3, shapeY, shapeWidth, shapeHeight);
+
+ int x4 = x3 + shapeSpacing;
+ gc.setForeground(yellow);
+ gc.setLineWidth(2);
+ gc.drawOval(x4, shapeY, shapeWidth, shapeHeight);
+
+ gc.setForeground(black);
+ gc.setLineWidth(4);
+ gc.drawLine(20, 150, x4 + shapeWidth, 150);
+
+ gc.setBackground(blue);
+ gc.fillPolygon(new int[] { 50, 170, 100, 220, 150, 170 });
+
+ gc.setForeground(red);
+ gc.setLineWidth(2);
+ gc.drawPolygon(new int[] { 250, 170, 300, 220, 350, 170, 300, 200 });
+
+ gc.setForeground(darkGray);
+ String[] labels = { "Filled Rectangle", "Outlined Rectangle", "Filled Oval", "Outlined Oval" };
+ int[] shapeXPositions = { x1, x2, x3, x4 };
+ for (int i = 0; i < labels.length; i++) {
+ Point textExtent = gc.stringExtent(labels[i]);
+ int centeredX = shapeXPositions[i] + (shapeWidth - textExtent.x) / 2;
+ gc.drawString(labels[i], centeredX, textY, true);
+ }
+
+ int row2Y = 240;
+
+ Path path = new Path(display);
+ try {
+ path.moveTo(x1, row2Y + 40);
+ path.lineTo(x1 + 30, row2Y);
+ path.quadTo(x1 + 50, row2Y + 20, x1 + 70, row2Y);
+ path.cubicTo(x1 + 90, row2Y, x1 + 100, row2Y + 60, x1 + 50, row2Y + 70);
+ path.close();
+ gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
+ gc.fillPath(path);
+ gc.setForeground(black);
+ gc.setLineWidth(2);
+ gc.drawPath(path);
+ } finally {
+ path.dispose();
+ }
+
+ Pattern gradient1 = new Pattern(display, x2, row2Y, x2 + shapeWidth, row2Y + shapeHeight,
+ display.getSystemColor(SWT.COLOR_MAGENTA), display.getSystemColor(SWT.COLOR_WHITE));
+ try {
+ gc.setBackgroundPattern(gradient1);
+ gc.fillRoundRectangle(x2, row2Y, shapeWidth, shapeHeight, 20, 20);
+ } finally {
+ gradient1.dispose();
+ }
+
+ Pattern gradient2 = new Pattern(display, x3 + shapeWidth / 2, row2Y + shapeHeight / 2,
+ x3 + shapeWidth / 2, row2Y + shapeHeight / 2, red, 0, yellow, 50);
+ try {
+ gc.setBackgroundPattern(gradient2);
+ gc.fillOval(x3, row2Y, shapeWidth, shapeHeight);
+ } finally {
+ gradient2.dispose();
+ }
+
+ Transform transform = new Transform(display);
+ try {
+ transform.translate(x4 + shapeWidth / 2, row2Y + shapeHeight / 2);
+ transform.rotate(45);
+ gc.setTransform(transform);
+ gc.setBackground(green);
+ gc.fillRectangle(-40, -40, 80, 80);
+ gc.setForeground(black);
+ gc.setLineWidth(2);
+ gc.drawRectangle(-40, -40, 80, 80);
+ gc.setTransform(null);
+ } finally {
+ transform.dispose();
+ }
+
+ gc.setForeground(darkGray);
+ String[] row2Labels = { "Path with Curves", "Linear Gradient", "Radial Gradient", "45° Rotation" };
+ int row2TextY = row2Y + shapeHeight + 5;
+ for (int i = 0; i < row2Labels.length; i++) {
+ Point textExtent = gc.stringExtent(row2Labels[i]);
+ int centeredX = shapeXPositions[i] + (shapeWidth - textExtent.x) / 2;
+ gc.drawString(row2Labels[i], centeredX, row2TextY, true);
+ }
+
+ int row3Y = 360;
+
+ gc.setAlpha(128);
+ gc.setBackground(blue);
+ gc.fillOval(x1, row3Y, 60, 60);
+ gc.setBackground(red);
+ gc.fillOval(x1 + 40, row3Y + 20, 60, 60);
+ gc.setAlpha(255);
+
+ gc.setLineStyle(SWT.LINE_DASH);
+ gc.setForeground(blue);
+ gc.setLineWidth(3);
+ gc.drawRoundRectangle(x2, row3Y, shapeWidth, shapeHeight, 15, 15);
+ gc.setLineStyle(SWT.LINE_DOT);
+ gc.setForeground(red);
+ gc.drawRectangle(x2 + 10, row3Y + 10, shapeWidth - 20, shapeHeight - 20);
+ gc.setLineStyle(SWT.LINE_SOLID);
+
+ gc.setAntialias(SWT.ON);
+ gc.setForeground(green);
+ gc.setLineWidth(3);
+ for (int i = 0; i < 5; i++) {
+ int offset = i * 20;
+ gc.drawLine(x3 + offset, row3Y, x3 + shapeWidth, row3Y + shapeHeight - offset);
+ }
+ gc.setAntialias(SWT.OFF);
+
+ Font largeFont = new Font(display, "Arial", 24, SWT.BOLD);
+ try {
+ gc.setFont(largeFont);
+ gc.setForeground(display.getSystemColor(SWT.COLOR_DARK_BLUE));
+ String text = "ABC";
+ Point extent = gc.stringExtent(text);
+ gc.drawString(text, x4 + (shapeWidth - extent.x) / 2, row3Y + (shapeHeight - extent.y) / 2, true);
+ } finally {
+ largeFont.dispose();
+ }
+
+ gc.setForeground(darkGray);
+ String[] row3Labels = { "Alpha Blending", "Line Styles", "Antialiasing", "Custom Font" };
+ int row3TextY = row3Y + shapeHeight + 5;
+ for (int i = 0; i < row3Labels.length; i++) {
+ Point textExtent = gc.stringExtent(row3Labels[i]);
+ int centeredX = shapeXPositions[i] + (shapeWidth - textExtent.x) / 2;
+ gc.drawString(row3Labels[i], centeredX, row3TextY, true);
+ }
+ });
+
+ Button exportButton = new Button(shell, SWT.PUSH);
+ exportButton.setText("Export to PDF");
+ exportButton.setLayoutData(new BorderData(SWT.BOTTOM));
+ exportButton.addListener(SWT.Selection, e -> {
+ try {
+ String tempDir = System.getProperty("java.io.tmpdir");
+ String pdfPath = tempDir + "/swt_graphics_demo.pdf";
+
+ Rectangle clientArea = shell.getClientArea();
+ PDFDocument pdf = new PDFDocument(pdfPath, clientArea.width, clientArea.height);
+ GC gc = new GC(pdf);
+ shell.print(gc);
+ gc.drawString("Exported to PDF...", 0, 0, true);
+ gc.dispose();
+ pdf.dispose();
+ System.out.println("PDF has been exported to:\n" + pdfPath + "\n\nOpening...");
+ Program.launch(pdfPath);
+
+ } catch (Throwable ex) {
+ MessageBox errorBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
+ errorBox.setText("Error");
+ errorBox.setMessage("Failed to export PDF: " + ex.getMessage());
+ errorBox.open();
+ ex.printStackTrace();
+ }
+ });
+
+ shell.setSize(800, 600);
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ }
+}
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
index db49ac96590..69e8bb534c8 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java
@@ -71,6 +71,7 @@
Test_org_eclipse_swt_layout_BorderLayout.class, //
Test_org_eclipse_swt_layout_FormAttachment.class, //
Test_org_eclipse_swt_layout_GridData.class, //
+ Test_org_eclipse_swt_printing_PDFDocument.class, //
Test_org_eclipse_swt_printing_PrintDialog.class, //
Test_org_eclipse_swt_printing_Printer.class, //
Test_org_eclipse_swt_printing_PrinterData.class, //
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_printing_PDFDocument.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_printing_PDFDocument.java
new file mode 100644
index 00000000000..5df7dc42ada
--- /dev/null
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_printing_PDFDocument.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Eclipse Platform Contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eclipse Platform Contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.tests.junit;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.DataFormatException;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.printing.PDFDocument;
+import org.eclipse.swt.widgets.Display;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Automated Test Suite for class org.eclipse.swt.printing.PDFDocument
+ *
+ * @see org.eclipse.swt.printing.PDFDocument
+ */
+public class Test_org_eclipse_swt_printing_PDFDocument {
+
+ Display display;
+ PDFDocument pdfDocument;
+ GC gc;
+ File tempFile;
+
+ @TempDir
+ Path tempDir;
+
+ @BeforeEach
+ public void setUp() {
+ display = Display.getDefault();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (gc != null && !gc.isDisposed()) {
+ gc.dispose();
+ }
+ if (pdfDocument != null && !pdfDocument.isDisposed()) {
+ pdfDocument.dispose();
+ }
+ if (tempFile != null && tempFile.exists()) {
+ tempFile.delete();
+ }
+ }
+
+ @Test
+ public void test_createPDFDocumentWithHelloWorld() throws IOException, DataFormatException {
+ // Create a temporary file for the PDF
+ tempFile = Files.createTempFile(tempDir, "test", ".pdf").toFile();
+ String filename = tempFile.getAbsolutePath();
+
+ // Create PDF document with standard letter size (612 x 792 points)
+ pdfDocument = new PDFDocument(filename, 612, 792);
+ assertNotNull(pdfDocument, "PDFDocument should be created");
+
+ // Create a GC on the PDF document
+ gc = new GC(pdfDocument);
+ assertNotNull(gc, "GC should be created on PDFDocument");
+
+ // Draw "hello world" text
+ gc.drawText("hello world", 100, 100);
+
+ // Dispose of resources to finalize the PDF
+ gc.dispose();
+ gc = null;
+ pdfDocument.dispose();
+ pdfDocument = null;
+
+ // Verify the PDF file was created and is not empty
+ assertTrue(tempFile.exists(), "PDF file should exist");
+ assertTrue(tempFile.length() > 0, "PDF file should not be empty");
+
+ // Verify PDF magic bytes and content
+ byte[] fileContent = Files.readAllBytes(tempFile.toPath());
+ assertTrue(fileContent.length >= 5, "PDF file should have at least 5 bytes for header");
+
+ // Check for PDF magic bytes: %PDF-
+ String headerString = new String(fileContent, 0, Math.min(5, fileContent.length));
+ assertTrue(headerString.startsWith("%PDF-"),
+ "PDF file should start with %PDF- magic bytes, but got: " + headerString);
+ }
+
+}