diff --git a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF index b2c427bf1af..142f59fae71 100644 --- a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface;singleton:=true -Bundle-Version: 3.38.100.qualifier +Bundle-Version: 3.39.0 Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: org.eclipse.jface, @@ -34,7 +34,7 @@ Export-Package: org.eclipse.jface, org.eclipse.jface.window, org.eclipse.jface.wizard, org.eclipse.jface.wizard.images -Require-Bundle: org.eclipse.swt;bundle-version="[3.126.0,4.0.0)";visibility:=reexport, +Require-Bundle: org.eclipse.swt;bundle-version="[3.132.0,3.134.0)";visibility:=reexport, org.eclipse.core.commands;bundle-version="[3.4.0,4.0.0)";visibility:=reexport, org.eclipse.equinox.common;bundle-version="[3.18.0,4.0.0)", org.eclipse.equinox.bidi;bundle-version="[0.10.0,2.0.0)";resolution:=optional diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java new file mode 100644 index 00000000000..4d5c176cf32 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.resource; + +import java.net.URL; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.swt.graphics.Point; + +class URLHintProvider implements Supplier { + + private static final Pattern QUERY_PATTERN = Pattern.compile("&size=(\\d+)x(\\d+)"); //$NON-NLS-1$ + private static final Pattern PATH_PATTERN = Pattern.compile("/(\\d+)x(\\d+)/"); //$NON-NLS-1$ + + private URL url; + + public URLHintProvider(URL url) { + this.url = url; + } + + @Override + public Point get() { + String query = url.getQuery(); + Matcher matcher; + if (query != null && !query.isEmpty()) { + matcher = QUERY_PATTERN.matcher("&" + query); //$NON-NLS-1$ + } else { + String path = url.getPath(); + matcher = PATH_PATTERN.matcher(path); + } + if (matcher.find()) { + return new Point(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))); + } + return null; + } + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java index 646ca5cb90f..66a3ea04a49 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java @@ -25,6 +25,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,6 +43,7 @@ import org.eclipse.swt.graphics.ImageDataProvider; import org.eclipse.swt.graphics.ImageFileNameProvider; import org.eclipse.swt.graphics.ImageLoader; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.internal.DPIUtil.ElementAtZoom; import org.eclipse.swt.internal.NativeImageLoader; import org.eclipse.swt.internal.image.FileFormat; @@ -133,7 +135,7 @@ private static R getZoomedImageSource(URL url, String urlString, int zoom, F private static ImageData getImageData(URL url, int fileZoom, int targetZoom) { try (InputStream in = getStream(url)) { if (in != null) { - return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom); + return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom, new URLHintProvider(url)); } } catch (SWTException e) { if (e.code != SWT.ERROR_INVALID_IMAGE) { @@ -147,7 +149,13 @@ private static ImageData getImageData(URL url, int fileZoom, int targetZoom) { } @SuppressWarnings("restriction") - private static ImageData loadImageFromStream(InputStream stream, int fileZoom, int targetZoom) { + private static ImageData loadImageFromStream(InputStream stream, int fileZoom, int targetZoom, + Supplier hintProvider) { + Point hintSize = hintProvider.get(); + if (hintSize != null) { + return NativeImageLoader.load(stream, new ImageLoader(), hintSize.x * targetZoom / fileZoom, + hintSize.y * targetZoom / fileZoom); + } return NativeImageLoader.load(new ElementAtZoom<>(stream, fileZoom), new ImageLoader(), targetZoom).get(0) .element(); } @@ -171,6 +179,7 @@ private static InputStream getStream(URL url) { } return url.openStream(); } catch (IOException e) { + e.printStackTrace(); if (InternalPolicy.DEBUG_LOG_URL_IMAGE_DESCRIPTOR_MISSING_2x) { String path = url.getPath(); if (path.endsWith("@2x.png") || path.endsWith("@1.5x.png")) { //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/examples/org.eclipse.jface.snippets/.classpath b/examples/org.eclipse.jface.snippets/.classpath index d47c0b22004..59a38942740 100644 --- a/examples/org.eclipse.jface.snippets/.classpath +++ b/examples/org.eclipse.jface.snippets/.classpath @@ -1,6 +1,6 @@ - + diff --git a/examples/org.eclipse.jface.snippets/.settings/org.eclipse.jdt.core.prefs b/examples/org.eclipse.jface.snippets/.settings/org.eclipse.jdt.core.prefs index 19621f56db4..99fa2aea988 100644 --- a/examples/org.eclipse.jface.snippets/.settings/org.eclipse.jdt.core.prefs +++ b/examples/org.eclipse.jface.snippets/.settings/org.eclipse.jdt.core.prefs @@ -8,9 +8,9 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annota org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.compliance=21 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -118,7 +118,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=17 +org.eclipse.jdt.core.compiler.source=21 org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 diff --git a/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/resources/Snippet083SVGImageSizeHints.java b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/resources/Snippet083SVGImageSizeHints.java new file mode 100644 index 00000000000..cfc5a79ae94 --- /dev/null +++ b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/resources/Snippet083SVGImageSizeHints.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.snippets.resources; + +import java.net.URL; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** + * A snippet to demonstrate SVG image size hints using path-based and + * query-parameter-based size detection. + * + *

+ * This demonstrates two ways to control SVG rendering size: + *

    + *
  1. Path-based hints: Place SVG in folders like /icons/16x16/ or + * /icons/32x32/
  2. + *
  3. Query parameter hints: Add ?size=WIDTHxHEIGHT to the URL
  4. + *
+ *

+ * + *

+ * This allows using a single SVG file at different sizes without creating + * multiple scaled versions or restricting the SVG design size. + *

+ */ +public class Snippet083SVGImageSizeHints { + + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setText("SVG Image Size Hints Demo"); + GridLayoutFactory.fillDefaults().numColumns(2).margins(10, 10).spacing(10, 10).applyTo(shell); + + addSection(shell, "Path-Based Size Hints", """ + SVGs placed in folders with size patterns (e.g., /icons/16x16/, /icons/32x32/) + are automatically rendered at that size. + + Example: bundle://plugin.id/icons/16x16/icon.svg + """); + + addSection(shell, "Query Parameter Size Hints", """ + You can specify size using query parameters for maximum flexibility. + This allows using the same SVG at different sizes. + + Example: bundle://plugin.id/icons/icon.svg?size=16x16 + Example: bundle://plugin.id/icons/icon.svg?size=128x128 + """); + + addSection(shell, "Zoom Support", """ + Both methods work with high-DPI displays. The hint specifies the base size, + and JFace automatically scales for 150% and 200% zoom levels. + + 16x16 at 100% zoom → 16x16 pixels + 16x16 at 150% zoom → 24x24 pixels + 16x16 at 200% zoom → 32x32 pixels + """); + + // Demonstrate with actual images from test resources if available + try { + URL svgUrl = Snippet083SVGImageSizeHints.class + .getResource("/org/eclipse/jface/snippets/resources/test-demo.svg"); + if (svgUrl != null) { + addImageDemo(shell, "Default (no hint)", svgUrl.toString(), display); + addImageDemo(shell, "With ?size=32x32", svgUrl.toString() + "?size=32x32", display); + addImageDemo(shell, "With ?size=64x64", svgUrl.toString() + "?size=64x64", display); + } + } catch (Exception e) { + // Demo images not available, that's okay + Label note = new Label(shell, SWT.WRAP); + note.setText("Note: Visual demo requires test SVG files. See URLHintProviderTest for examples."); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(note); + } + + addSection(shell, "Use Cases", """ + 1. Toolbar icons: Use ?size=16x16 for consistent toolbar sizing + 2. Wizard images: Use ?size=128x128 for large wizard graphics + 3. View icons: Place in /icons/16x16/ folder structure + 4. Multi-resolution: One SVG serves all sizes without duplication + """); + + addSection(shell, "Code Example", """ + // Using query parameter + URL iconUrl = new URL("bundle://my.plugin/icons/search.svg?size=16x16"); + ImageDescriptor desc = ImageDescriptor.createFromURL(iconUrl); + Image icon = desc.createImage(); + + // Using path-based hint + URL iconUrl = FileLocator.find(bundle, new Path("icons/16x16/search.svg")); + ImageDescriptor desc = ImageDescriptor.createFromURL(iconUrl); + """); + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } + + private static void addSection(Shell shell, String title, String text) { + Label titleLabel = new Label(shell, SWT.BOLD); + titleLabel.setText(title); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(titleLabel); + + Label textLabel = new Label(shell, SWT.WRAP); + textLabel.setText(text.trim()); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).hint(600, SWT.DEFAULT).applyTo(textLabel); + + Label separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(separator); + } + + private static void addImageDemo(Shell shell, String description, String urlString, Display display) { + try { + URL url = new URL(urlString); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + Image image = descriptor.createImage(display); + + Label label = new Label(shell, SWT.NONE); + label.setText(description + ":"); + GridDataFactory.fillDefaults().applyTo(label); + + Canvas canvas = new Canvas(shell, SWT.BORDER); + canvas.addPaintListener(e -> { + if (!image.isDisposed()) { + e.gc.drawImage(image, 0, 0); + } + }); + GridDataFactory.swtDefaults().hint(image.getBounds().width + 2, image.getBounds().height + 2) + .applyTo(canvas); + + canvas.addDisposeListener(e -> { + if (!image.isDisposed()) { + image.dispose(); + } + }); + } catch (Exception e) { + Label error = new Label(shell, SWT.NONE); + error.setText(description + ": Error loading image"); + GridDataFactory.fillDefaults().span(2, 1).applyTo(error); + } + } +} diff --git a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF index 5d7848897ab..6a368779768 100644 --- a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF @@ -15,5 +15,5 @@ Export-Package: org.eclipse.jface.snippets.dialogs, org.eclipse.jface.snippets.viewers, org.eclipse.jface.snippets.window, org.eclipse.jface.snippets.wizard -Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-RequiredExecutionEnvironment: JavaSE-21 Automatic-Module-Name: org.eclipse.jface.snippets diff --git a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF index b8dc172d3e3..8cadbfa2c6c 100644 --- a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jface.tests -Bundle-Version: 1.4.1100.qualifier +Bundle-Version: 1.4.1200.qualifier Automatic-Module-Name: org.eclipse.jface.tests Bundle-RequiredExecutionEnvironment: JavaSE-17 Require-Bundle: org.junit;bundle-version="4.12.0", diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg new file mode 100644 index 00000000000..61178e6910f --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg @@ -0,0 +1,6 @@ + + + + + S + diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg new file mode 100644 index 00000000000..61178e6910f --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg @@ -0,0 +1,6 @@ + + + + + S + diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg new file mode 100644 index 00000000000..4c8ecc392a1 --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg @@ -0,0 +1,6 @@ + + + + + S + diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java index 7761d2aa846..d4fb86cacd9 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java @@ -21,7 +21,8 @@ @Suite @SelectClasses({ ImageRegistryTest.class, ResourceManagerTest.class, FileImageDescriptorTest.class, - UrlImageDescriptorTest.class, DecorationOverlayIconTest.class, DeferredImageDescriptorTest.class }) + UrlImageDescriptorTest.class, URLHintProviderTest.class, DecorationOverlayIconTest.class, + DeferredImageDescriptorTest.class }) public class AllImagesTests { public static void main(String[] args) { diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java new file mode 100644 index 00000000000..ae5b207e386 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.tests.images; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.net.URL; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.ImageData; +import org.junit.jupiter.api.Test; + +/** + * Tests for URLHintProvider functionality that detects desired image sizes from + * URL paths and query parameters. + * + *

+ * Note: SVG size hint support requires SWT 3.132+ with native SVG loading + * capabilities. Tests verify that hints are detected and passed to the loader, + * but actual rendering depends on platform SVG support. + *

+ */ +public class URLHintProviderTest { + + @Test + public void testPathBasedHintDetection16x16() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + // If SVG size hints are supported, should be 16x16, otherwise native 128x128 + assertEquals(16, imageData.width, "Width should be 16 based on path hint"); + assertEquals(16, imageData.height, "Height should be 16 based on path hint"); + } + + @Test + public void testPathBasedHintDetection32x32() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/32x32/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 based on path hint"); + assertEquals(32, imageData.height, "Height should be 32 based on path hint"); + } + + @Test + public void testPathBasedHintDetectionZoom200() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(200); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 (16*2) at 200% zoom"); + assertEquals(32, imageData.height, "Height should be 32 (16*2) at 200% zoom"); + } + + @Test + public void testPathBasedHintDetectionZoom150() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(150); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(24, imageData.width, "Width should be 24 (16*1.5) at 150% zoom"); + assertEquals(24, imageData.height, "Height should be 24 (16*1.5) at 150% zoom"); + } + + @Test + public void testQueryParameterHintDetection() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + // Create URL with query parameter - using jar: protocol from class loader + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=16x16"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(16, imageData.width, "Width should be 16 based on query parameter"); + assertEquals(16, imageData.height, "Height should be 16 based on query parameter"); + } + + @Test + public void testQueryParameterHintDetection64x64() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=64x64"); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(64, imageData.width, "Width should be 64 based on query parameter"); + assertEquals(64, imageData.height, "Height should be 64 based on query parameter"); + } + + @Test + public void testQueryParameterWithZoom200() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=16x16"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(200); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 (16*2) at 200% zoom"); + assertEquals(32, imageData.height, "Height should be 32 (16*2) at 200% zoom"); + } + + @Test + public void testQueryParameterRectangularSize() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=48x32"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(48, imageData.width, "Width should be 48 based on query parameter"); + assertEquals(32, imageData.height, "Height should be 32 based on query parameter"); + } + + @Test + public void testNoHintDefaultSize() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + // Without hint, SVG should render at its native viewBox size (128x128) + assertEquals(128, imageData.width, "Width should be 128 (native SVG size)"); + assertEquals(128, imageData.height, "Height should be 128 (native SVG size)"); + } + + @Test + public void testQueryParameterPrecedenceOverPath() throws Exception { + // Query parameter should take precedence over path hint + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=64x64"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(64, imageData.width, "Width should be 64 from query parameter, not 16 from path"); + assertEquals(64, imageData.height, "Height should be 64 from query parameter, not 16 from path"); + } + + @Test + public void testQueryParameterWithMultipleParams() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + // Test with multiple query parameters + URL url = new URL(urlString + "?foo=bar&size=24x24&other=value"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(24, imageData.width, "Width should be 24 from query parameter"); + assertEquals(24, imageData.height, "Height should be 24 from query parameter"); + } + +}