diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so
index 562b8abc621..1807907f2f8 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:aea877ec9eff47e0fff89de92ef3383f62e188590e7a037b981631c92aaea752
-size 39168
+oid sha256:ff2855d483fc997f2692a0f9d6e3775eb90743db49e53a576c42a7d60797eb34
+size 43408
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so
index 0c6248ab26c..f1368fe08e2 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:616e9e63473d7281ac7b995c423cf44f8665fbcbbba0b1048170a0cf7eb8541b
-size 14112
+oid sha256:6fba450120f753344e4a52ea6c731118d4272c325c16363ce7e65a22aa74b68d
+size 14408
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so
index f9c8fe6b96d..e92f8871b20 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f79f0ab0bbecd950624dc6c37e242073aa284cec62ffa310b336912b111991b5
-size 47952
+oid sha256:1b257d1d881ab0eb178c88af34d7a5c4c9ce3988d6506ff4a7440f8b2cbc3622
+size 48096
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so
index 78ca320c441..4899c5f1c66 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:998ff6f410ef05b30b8e57a05cfea2514730febba0e0114348a284d36965976c
-size 14352
+oid sha256:1d7ed632ecc66cccaeaacf3cddd59737c74055aceed22a2213b822614737cfed
+size 14496
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so
index ebeb0a2263b..1f5578e6b76 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:da7a80e2c4da658f6e3e1495dbcc39a96d3a1a903ce826b9d187d4b5fc0f7c39
-size 610344
+oid sha256:8e34c00dbf368e9f35bed68e5ad54b9c969690f1c26b2df76fecda4d8a3e501f
+size 626888
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so
index 64c3c7cdf07..af46a94be46 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:560a259b92a7a14f2146722d56fc67681d88680d56100fb0fc8297608637d115
-size 466912
+oid sha256:fec14f3cac401f6aa77087dd74e99db1321b0bbedf7aa1adaaefbacd0bff476b
+size 491600
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so
index 3ad7b8a7098..4d52c2cc367 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d9c9892b192a09fb79c68a499149d00522584dd0f12aebdaa787da6ca7a76b49
-size 412632
+oid sha256:56527bb18757065129d84b096bc306b7376838e26d6195f0a020549bb5f527a2
+size 437360
diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so
index 63d41181fe4..79dd2d57aae 100755
--- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so
+++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4972r1.so
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:caa1a8258c09b99cfa3076f0207975223268d872dd5c87f8af492c8f631870ab
-size 71640
+oid sha256:d3a998c181c39cc7c25b28127fe3fa06c629e71f0447018265817038f0cde262
+size 71792
diff --git a/build_gtk.sh b/build_gtk.sh
new file mode 100755
index 00000000000..ea1dfb7db60
--- /dev/null
+++ b/build_gtk.sh
@@ -0,0 +1,6 @@
+cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt/bundles/org.eclipse.swt && java -Dws=gtk -Darch=x86_64 build-scripts/CollectSources.java -nativeSources '/home/runner/build/gtk'
+cd /home/runner/build/gtk && SWT_JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.9-10/x64 MODEL=x86_64 OUTPUT_DIR=/home/runner/build/tmp ./build.sh install clean
+cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt
+cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt/bundles/org.eclipse.swt && java -Dws=gtk -Darch=x86_64 build-scripts/CollectSources.java -nativeSources '/home/runner/build/gtk'
+cd /home/runner/build/gtk && SWT_JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.9-10/x64 MODEL=x86_64 OUTPUT_DIR=/home/runner/build/tmp ./build.sh install clean
+cd /home/runner/work/eclipse.platform.swt/eclipse.platform.swt
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..accf0da79be 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
@@ -1471,3 +1471,27 @@ JNIEXPORT void JNICALL Cairo_NATIVE(memmove___3DJJ)
}
#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)((cairo_surface_t * (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
+
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..86f31348171 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
@@ -87,6 +87,7 @@ typedef enum {
cairo_1pattern_1set_1filter_FUNC,
cairo_1pattern_1set_1matrix_FUNC,
cairo_1pdf_1surface_1set_1size_FUNC,
+ cairo_1pdf_1surface_1create_FUNC,
cairo_1pop_1group_1to_1source_FUNC,
cairo_1ps_1surface_1set_1size_FUNC,
cairo_1push_1group_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..552151d3ab0 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
@@ -1596,6 +1596,52 @@ JNIEXPORT void JNICALL OS_NATIVE(CGImageRelease)
}
#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, lparg1, (CFDictionaryRef)arg2);
+fail:
+ if (arg1 && lparg1) setCGRectFields(env, arg1, lparg1);
+ OS_NATIVE_EXIT(env, that, CGPDFContextCreateWithURL_FUNC);
+ return rc;
+}
+#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_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_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_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..8fec5b090ea 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
@@ -117,6 +117,10 @@ typedef enum {
CGImageGetHeight_FUNC,
CGImageGetWidth_FUNC,
CGImageRelease_FUNC,
+ CGPDFContextCreateWithURL_FUNC,
+ CGPDFContextBeginPage_FUNC,
+ CGPDFContextEndPage_FUNC,
+ CGPDFContextClose_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..d5c87d68bf7 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 flags=struct
+ * @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 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..3a14d1294f1
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/cocoa/org/eclipse/swt/printing/PDFDocument.java
@@ -0,0 +1,389 @@
+/*******************************************************************************
+ * 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.131
+ */
+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+ * 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+ * 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+ * 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
+ * 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.
+ *
+ * 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.
+ *
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..704d29880a7 --- /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 usingnew 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.131
+ */
+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+ * 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+ * 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+ * 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
+ * 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.
+ *
+ * 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.
+ *
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..0f3e2a1a0c9 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Printing/win32/org/eclipse/swt/printing/PDFDocument.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * 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 usingnew 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.131
+ */
+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 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";
+
+ /**
+ * 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+ * 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+ * 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+ * 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
+ * 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.
+ *
+ * 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.
+ *
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/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java index 7cb58ea5ebf..1b1e3115362 100644 --- a/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java +++ b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/AllGTKTests.java @@ -19,7 +19,8 @@ @Suite @SelectClasses({ - Test_GtkConverter.class + Test_GtkConverter.class, + Test_PDFDocument.class }) public class AllGTKTests { diff --git a/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java new file mode 100644 index 00000000000..9e73073f849 --- /dev/null +++ b/tests/org.eclipse.swt.tests.gtk/JUnit Tests/org/eclipse/swt/tests/gtk/Test_PDFDocument.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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.gtk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.printing.PDFDocument; +import org.eclipse.swt.widgets.Display; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +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_PDFDocument { + + private static Display display; + + @TempDir + File tempDir; + + private PDFDocument pdfDocument; + + @BeforeAll + public static void setUpBeforeClass() { + display = Display.getDefault(); + } + + @AfterAll + public static void tearDownAfterClass() { + if (display != null && !display.isDisposed()) { + display.dispose(); + } + } + + @AfterEach + public void tearDown() { + if (pdfDocument != null && !pdfDocument.isDisposed()) { + pdfDocument.dispose(); + } + } + + @Test + public void test_Constructor_NullFilename() { + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(null, 612, 792)); + } + + @Test + public void test_Constructor_InvalidDimensions() { + String filename = new File(tempDir, "test.pdf").getAbsolutePath(); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 0, 792)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 612, 0)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, -1, 792)); + assertThrows(IllegalArgumentException.class, () -> new PDFDocument(filename, 612, -1)); + } + + @Test + public void test_Constructor_ValidParameters() { + String filename = new File(tempDir, "test_valid.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(filename, 612, 792); + assertNotNull(pdfDocument); + assertFalse(pdfDocument.isDisposed()); + } + + @Test + public void test_ConstructorWithDevice() { + String filename = new File(tempDir, "test_device.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertNotNull(pdfDocument); + assertFalse(pdfDocument.isDisposed()); + } + + @Test + public void test_getWidth() { + String filename = new File(tempDir, "test_width.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertEquals(612.0, pdfDocument.getWidth(), 0.001); + } + + @Test + public void test_getHeight() { + String filename = new File(tempDir, "test_height.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertEquals(792.0, pdfDocument.getHeight(), 0.001); + } + + @Test + public void test_isAutoScalable() { + String filename = new File(tempDir, "test_autoscale.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertFalse(pdfDocument.isAutoScalable()); + } + + @Test + public void test_dispose() { + String filename = new File(tempDir, "test_dispose.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + assertFalse(pdfDocument.isDisposed()); + pdfDocument.dispose(); + assertTrue(pdfDocument.isDisposed()); + } + + @Test + public void test_createGC() { + String filename = new File(tempDir, "test_gc.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + assertNotNull(gc); + gc.dispose(); + } + + @Test + public void test_drawOnGC() { + String filename = new File(tempDir, "test_draw.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + + // Test drawing operations - should not throw exceptions + gc.drawRectangle(10, 10, 100, 80); + gc.drawText("Hello, PDF!", 50, 50); + gc.drawLine(0, 0, 100, 100); + + gc.dispose(); + pdfDocument.dispose(); + + // Verify file was created + File file = new File(filename); + assertTrue(file.exists(), "PDF file should have been created"); + assertTrue(file.length() > 0, "PDF file should not be empty"); + } + + @Test + public void test_newPage() { + String filename = new File(tempDir, "test_newpage.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + GC gc = new GC(pdfDocument); + + gc.drawText("Page 1", 50, 50); + pdfDocument.newPage(); + gc.drawText("Page 2", 50, 50); + + gc.dispose(); + pdfDocument.dispose(); + + // Verify file was created + File file = new File(filename); + assertTrue(file.exists(), "PDF file should have been created"); + } + + @Test + public void test_newPageWithDifferentSize() { + String filename = new File(tempDir, "test_newpage_size.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); // Letter size + + assertEquals(612.0, pdfDocument.getWidth(), 0.001); + assertEquals(792.0, pdfDocument.getHeight(), 0.001); + + GC gc = new GC(pdfDocument); + gc.drawText("Page 1 - Letter", 50, 50); + + pdfDocument.newPage(842, 595); // A4 Landscape + + assertEquals(842.0, pdfDocument.getWidth(), 0.001); + assertEquals(595.0, pdfDocument.getHeight(), 0.001); + + gc.drawText("Page 2 - A4 Landscape", 50, 50); + + gc.dispose(); + pdfDocument.dispose(); + } + + @Test + public void test_operationsAfterDispose() { + String filename = new File(tempDir, "test_disposed.pdf").getAbsolutePath(); + pdfDocument = new PDFDocument(display, filename, 612, 792); + pdfDocument.dispose(); + + assertThrows(Exception.class, () -> pdfDocument.getWidth()); + assertThrows(Exception.class, () -> pdfDocument.getHeight()); + assertThrows(Exception.class, () -> pdfDocument.newPage()); + } +}