From e508bb7f2330eca43d710d28da7e31b06b2259d2 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 24 Jan 2024 16:59:19 +0000 Subject: [PATCH] Improved canvas HDPI management (introduced pixelDensityProperty) --- webfx-kit/webfx-kit-gwt/pom.xml | 6 ++ .../impl/gwt/GwtWebFxKitLauncherProvider.java | 33 ++++++- .../pom.xml | 6 ++ .../gwt/html/CanvasElementHelper.java | 49 ++++++++++ .../gwt/html/HtmlCanvasPeer.java | 14 +-- .../gwt/html/HtmlGraphicsContext.java | 89 ++++--------------- .../webfx/kit/launcher/WebFxKitLauncher.java | 16 ++++ .../spi/WebFxKitLauncherProvider.java | 22 +++++ 8 files changed, 151 insertions(+), 84 deletions(-) diff --git a/webfx-kit/webfx-kit-gwt/pom.xml b/webfx-kit/webfx-kit-gwt/pom.xml index 9311478e3..a9e555a16 100644 --- a/webfx-kit/webfx-kit-gwt/pom.xml +++ b/webfx-kit/webfx-kit-gwt/pom.xml @@ -49,6 +49,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-kit-util + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-console diff --git a/webfx-kit/webfx-kit-gwt/src/main/java/dev/webfx/kit/launcher/spi/impl/gwt/GwtWebFxKitLauncherProvider.java b/webfx-kit/webfx-kit-gwt/src/main/java/dev/webfx/kit/launcher/spi/impl/gwt/GwtWebFxKitLauncherProvider.java index f6fe2099f..087f82ecd 100644 --- a/webfx-kit/webfx-kit-gwt/src/main/java/dev/webfx/kit/launcher/spi/impl/gwt/GwtWebFxKitLauncherProvider.java +++ b/webfx-kit/webfx-kit-gwt/src/main/java/dev/webfx/kit/launcher/spi/impl/gwt/GwtWebFxKitLauncherProvider.java @@ -5,9 +5,12 @@ import dev.webfx.kit.launcher.spi.FastPixelReaderWriter; import dev.webfx.kit.launcher.spi.impl.base.WebFxKitLauncherProviderBase; import dev.webfx.kit.mapper.WebFxKitMapper; +import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html.CanvasElementHelper; +import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html.HtmlNodePeer; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.DragboardDataTransferHolder; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlFonts; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlUtil; +import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.platform.console.Console; import dev.webfx.platform.util.Strings; import dev.webfx.platform.util.collection.Collections; @@ -15,6 +18,8 @@ import elemental2.dom.*; import javafx.application.Application; import javafx.application.HostServices; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ObservableList; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; @@ -148,12 +153,33 @@ public GraphicsContext getGraphicsContext2D(Canvas canvas, boolean willReadFrequ return WebFxKitMapper.getGraphicsContext2D(canvas, willReadFrequently); } - private final HTMLCanvasElement canvas = HtmlUtil.createElement("canvas"); + // HDPI management + + @Override + public DoubleProperty canvasPixelDensityProperty(Canvas canvas) { + String key = "webfx-canvasPixelDensityProperty"; + DoubleProperty canvasPixelDensityProperty = (DoubleProperty) canvas.getProperties().get(key); + if (canvasPixelDensityProperty == null) { + canvas.getProperties().put(key, canvasPixelDensityProperty = new SimpleDoubleProperty(getDefaultCanvasPixelDensity())); + // Applying an immediate mapping between the JavaFX and HTML canvas, otherwise the default behaviour of the + // WebFX mapper (which is to postpone and process the mapping in the next animation frame) wouldn't work for + // canvas. The application will indeed probably draw in the canvas just after it is initialized (and sized). + // If we were to wait for the mapper to resize the canvas in the next animation frame, it would be too late. + HTMLCanvasElement canvasElement = (HTMLCanvasElement) ((HtmlNodePeer) canvas.getOrCreateAndBindNodePeer()).getElement(); + FXProperties.runNowAndOnPropertiesChange(() -> + CanvasElementHelper.resizeCanvasElement(canvasElement, canvas), + canvas.widthProperty(), canvas.heightProperty(), canvasPixelDensityProperty); + + } + return canvasPixelDensityProperty; + } + + private final HTMLCanvasElement MEASURE_CANVAS = HtmlUtil.createElement("canvas"); @Override public Bounds measureText(String text, Font font) { - JavaScriptObject textMetrics = getTextMetrics(canvas, text, HtmlFonts.getHtmlFontDefinition(font)); + JavaScriptObject textMetrics = getTextMetrics(MEASURE_CANVAS, text, HtmlFonts.getHtmlFontDefinition(font)); return new BoundingBox(0, 0, getJsonWidth(textMetrics), getJsonHeight(textMetrics)); } @@ -191,7 +217,8 @@ public ObservableList loadingFonts() { private native JavaScriptObject getTextMetrics(HTMLCanvasElement canvas, String text, String font)/*-{ var context = canvas.getContext("2d"); context.font = font; - return { width: context.measureText(text).width, height: parseFloat(context.font.match(/\d+/)) }; + var textMetrics = context.measureText(text); + return { width: textMetrics.width, height: textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent }; }-*/; private static native double getJsonWidth(JavaScriptObject json)/*-{ diff --git a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/pom.xml b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/pom.xml index 1719e1c02..7922a2b49 100644 --- a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/pom.xml +++ b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/pom.xml @@ -59,6 +59,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-kit-launcher + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-util diff --git a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/CanvasElementHelper.java b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/CanvasElementHelper.java index 3ec75b1ab..3db256c96 100644 --- a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/CanvasElementHelper.java +++ b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/CanvasElementHelper.java @@ -1,10 +1,13 @@ package dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html; +import dev.webfx.kit.launcher.WebFxKitLauncher; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.shared.HtmlSvgNodePeer; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlUtil; +import elemental2.dom.CSSProperties; import elemental2.dom.CanvasRenderingContext2D; import elemental2.dom.HTMLCanvasElement; import elemental2.dom.ImageData; +import javafx.scene.canvas.Canvas; import javafx.scene.image.Image; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; @@ -109,4 +112,50 @@ private static void setImagePeerCanvas(Image image, HTMLCanvasElement canvasElem image.setPeerCanvasDirty(dirty); } + // Utility methods to resize a Canvas element + + public static void resizeCanvasElement(HTMLCanvasElement canvasElement, Canvas canvas) { + double fxCanvasWidth = canvas.getWidth(), fxCanvasHeight = canvas.getHeight(); + // While HTML canvas and JavaFX canvas have an identical size in low-res screens, they differ in HDPI screens + // because JavaFX automatically apply the pixel conversion, while HTML doesn't. + double pixelDensity = WebFxKitLauncher.getCanvasPixelDensity(canvas); + int htmlWidth = (int) (fxCanvasWidth * pixelDensity); // So we apply the density factor to get the hi-res number of pixels. + int htmlHeight = (int) (fxCanvasHeight * pixelDensity); + // Note: the JavaFX canvas size might be 0 initially, but we set a minimal size of 1px for the HTML canvas, the + // reason is that transforms applied on zero-sized canvas are ignored on Chromium browsers (for example applying + // the pixelDensity scale on a zero-sized canvas doesn't change the canvas transform), which would make our + // canvas state snapshot technique below fail. + if (htmlWidth == 0) + htmlWidth = 1; + if (htmlHeight == 0) + htmlHeight = 1; + // It's very important to prevent changing the canvas size when not necessary, because resetting an HTML canvas + // size has these 2 serious consequences (even with identical value): + // 1) the canvas is erased + // 2) the context state is reset (including transforms, such as the initial pixel density on HDPI screens) + boolean htmlSizeHasChanged = canvasElement.width != htmlWidth || canvasElement.height != htmlHeight; + if (htmlSizeHasChanged) { + // Getting the 2D context but only if already created (we don't want to initialize a 2D context if the + // canvas will finally be used for WebGL in the application code - because the context type (2D or WebGL) + // can't be changed once initialized). The reason for getting the context is to eventually create a context + // snapshot (but there is no need if the 2D context was not initialized. + CanvasRenderingContext2D ctx = canvas.theContext == null ? null : Context2DHelper.getCanvasContext2D(canvasElement); + // We don't want to lose the context state when resizing the canvas, so we take a snapshot of it before + // resizing, so we can restore it after that. + Context2DStateSnapshot ctxStateSnapshot = ctx == null ? null : new Context2DStateSnapshot(ctx); + // Now we can change the canvas size, as we are prepared + canvasElement.width = htmlWidth; // => erases canvas & reset context sate + canvasElement.height = htmlHeight; // => erases canvas & reset context sate + // We restore the context state that we have stored in the snapshot (this includes the initial pixelDensity scale) + if (ctxStateSnapshot != null) + ctxStateSnapshot.reapply(); + // On HDPI screens, we must also set the CSS size, otherwise the CSS size will be taken from the canvas + // size by default, which is not what we want because the CSS size is expressed in low-res and not in HDPI + // pixels like the canvas size, so this would make the canvas appear much too big on the screen. + if (pixelDensity != 1) { // Scaling down the canvas size with CSS size on HDPI screens + canvasElement.style.width = CSSProperties.WidthUnionType.of(fxCanvasWidth + "px"); + canvasElement.style.height = CSSProperties.HeightUnionType.of(fxCanvasHeight + "px"); + } + } + } } diff --git a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlCanvasPeer.java b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlCanvasPeer.java index b53415002..1ee4b2e53 100644 --- a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlCanvasPeer.java +++ b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlCanvasPeer.java @@ -1,5 +1,6 @@ package dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html; +import dev.webfx.kit.launcher.WebFxKitLauncher; import dev.webfx.kit.mapper.peers.javafxgraphics.base.CanvasPeerBase; import dev.webfx.kit.mapper.peers.javafxgraphics.base.CanvasPeerMixin; import elemental2.dom.HTMLCanvasElement; @@ -11,8 +12,6 @@ import javafx.scene.transform.Scale; import javafx.scene.transform.Transform; -import static dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html.HtmlGraphicsContext.*; - /** * @author Bruno Salmon */ @@ -36,13 +35,13 @@ private HTMLCanvasElement getCanvasElement() { @Override public void updateWidth(Number width) { // Note: probably already updated by HtmlGraphicsContext - updateCanvasElementWidth(getCanvasElement(), width.doubleValue()); + CanvasElementHelper.resizeCanvasElement(getCanvasElement(), getNode()); } @Override public void updateHeight(Number height) { // Note: probably already updated by HtmlGraphicsContext - updateCanvasElementHeight(getCanvasElement(), height.doubleValue()); + CanvasElementHelper.resizeCanvasElement(getCanvasElement(), getNode()); } @Override @@ -56,6 +55,7 @@ public WritableImage snapshot(SnapshotParameters params, WritableImage image) { scaleY = scale.getY(); } } + N canvas = getNode(); int width, height; if (image != null) { width = (int) image.getWidth(); @@ -70,18 +70,18 @@ public WritableImage snapshot(SnapshotParameters params, WritableImage image) { // render the new version of this image (= this snapshot). image.setPeerCanvas(null); } else { - N canvas = getNode(); width = (int) (canvas.getWidth() * scaleX); height = (int) (canvas.getHeight() * scaleY); image = new WritableImage(width , height); } HTMLCanvasElement canvasToCopy = getCanvasElement(); + double pixelDensity = WebFxKitLauncher.getCanvasPixelDensity(canvas); // Making a rescaled copy of the canvas if necessary before capturing the image - if (scaleX != DPR || scaleY != DPR) { + if (scaleX != pixelDensity || scaleY != pixelDensity) { HTMLCanvasElement c = CanvasElementHelper.createCanvasElement(width, height); // Note: wrong Elemental2 signature. Correct signature = drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) - Context2DHelper.getCanvasContext2D(c).drawImage(canvasToCopy, 0, 0, width * DPR, height * DPR, 0, 0, width, height); + Context2DHelper.getCanvasContext2D(c).drawImage(canvasToCopy, 0, 0, width * pixelDensity, height * pixelDensity, 0, 0, width, height); canvasToCopy = c; } ImageData imageData = ImageDataHelper.captureCanvasImageData(canvasToCopy, width, height); diff --git a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlGraphicsContext.java b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlGraphicsContext.java index 308651fee..68dffb9dc 100644 --- a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlGraphicsContext.java +++ b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/html/HtmlGraphicsContext.java @@ -1,9 +1,9 @@ package dev.webfx.kit.mapper.peers.javafxgraphics.gwt.html; +import dev.webfx.kit.launcher.WebFxKitLauncher; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlFonts; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlPaints; import dev.webfx.kit.mapper.peers.javafxgraphics.gwt.util.HtmlUtil; -import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.platform.console.Console; import dev.webfx.platform.util.Objects; import elemental2.dom.*; @@ -25,38 +25,27 @@ import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import javafx.scene.transform.Affine; -import javafx.stage.Screen; /** * @author Bruno Salmon */ public class HtmlGraphicsContext implements GraphicsContext { - private final static boolean ENABLE_HDPI_CANVAS = true; - // DPR stands for Device Pixel Ratio (which is the HTML terminology, while JavaFX calls that output scale) - final static double DPR = ENABLE_HDPI_CANVAS ? Screen.getPrimary().getOutputScaleX() : 1; - private final Canvas canvas; private final CanvasRenderingContext2D ctx; private boolean proportionalFillLinearGradient; // Main constructor (directly bound to a JavaFX canvas) - public HtmlGraphicsContext(Canvas canvas, boolean willReadFrequently) { this.canvas = canvas; HTMLCanvasElement canvasElement = (HTMLCanvasElement) ((HtmlNodePeer) canvas.getOrCreateAndBindNodePeer()).getElement(); ctx = Context2DHelper.getCanvasContext2D(canvasElement, willReadFrequently); - // Applying an immediate mapping between the JavaFX and HTML canvas, otherwise the default behaviour of the - // WebFX mapper (which is to postpone and process the mapping in the next animation frame) wouldn't work for - // canvas. The application will indeed probably draw in the canvas just after it is initialized (and sized). - // If we were to wait for the mapper to resize the canvas in the next animation frame, it would be too late. - FXProperties.runNowAndOnPropertiesChange(() -> resizeCanvasElement(canvasElement), canvas.widthProperty(), canvas.heightProperty()); - // We apply a DPR scale on HDPI screens - ctx.scale(DPR, DPR); + // We apply a scale on HDPI screens + double pixelDensity = getPixelDensity(); // Note: this call will create the pixelDensityProperty and immediately size the canvas (see GwtWebFxKitLauncherProvider.canvasPixelDensityProperty()) + ctx.scale(pixelDensity, pixelDensity); } // Alternative constructors used internally for some operations (not directly bound to a JavaFX canvas) - HtmlGraphicsContext(HTMLCanvasElement canvasElement) { this(Context2DHelper.getCanvasContext2D(canvasElement)); } @@ -66,58 +55,9 @@ public HtmlGraphicsContext(Canvas canvas, boolean willReadFrequently) { this.ctx = ctx; } - private void resizeCanvasElement(HTMLCanvasElement canvasElement) { - updateCanvasElementWidth(canvasElement, canvas.getWidth()); - updateCanvasElementHeight(canvasElement, canvas.getHeight()); - } - - static void updateCanvasElementWidth(HTMLCanvasElement canvasElement, double fxCanvasWidth) { - updateCanvasElementSize(canvasElement, fxCanvasWidth, true); - } - - static void updateCanvasElementHeight(HTMLCanvasElement canvasElement, double fxCanvasHeight) { - updateCanvasElementSize(canvasElement, fxCanvasHeight, false); - } - - private static void updateCanvasElementSize(HTMLCanvasElement canvasElement, double fxCanvasSize, boolean isWidth) { - boolean isWebGL = canvasElement.getAttribute("webgl") != null; - CanvasRenderingContext2D ctx = isWebGL ? null : Context2DHelper.getCanvasContext2D(canvasElement); - // While HTML canvas and JavaFX canvas have an identical size in low-res screens, they differ in HDPI screens - // because JavaFX automatically apply the pixel conversion, while HTML doesn't. - int htmlSize = (int) (fxCanvasSize * DPR); // So we apply the DPR factor to get the hi-res number of pixels. - // Note: the JavaFX canvas size might be 0 initially, but we set a minimal size of 1px for the HTML canvas, the - // reason is that transforms applied on zero-sized canvas are ignored on Chromium browsers (for example applying - // the DPR scale on a zero-sized canvas doesn't change the canvas transform), which would make our canvas state - // snapshot technique below fail. - if (htmlSize == 0) - htmlSize = 1; - // It's very important to prevent changing the canvas size when not necessary, because resetting an HTML canvas - // size has these 2 serious consequences (even with identical value): - // 1) the canvas is erased - // 2) the context state is reset (including transforms, such as the initial DPR scale on HDPI screens) - boolean htmlSizeHasChanged = isWidth ? canvasElement.width != htmlSize : canvasElement.height != htmlSize; - if (htmlSizeHasChanged) { - // We don't want to lose the context state when resizing the canvas, so we take a snapshot of it before - // resizing, so we can restore it after that. - Context2DStateSnapshot ctxStateSnapshot = ctx == null ? null : new Context2DStateSnapshot(ctx); - // Now we can change the canvas size, as we are prepared - if (isWidth) - canvasElement.width = htmlSize; // => erases canvas & reset context sate - else - canvasElement.height = htmlSize; // => erases canvas & reset context sate - // We restore the context state that we have stored in the snapshot (this includes the initial DPR scale) - if (ctxStateSnapshot != null) - ctxStateSnapshot.reapply(); - // On HDPI screens, we must also set the CSS size, otherwise the CSS size will be taken from the canvas - // size by default, which is not what we want because the CSS size is expressed in low-res and not in HDPI - // pixels like the canvas size, so this would make the canvas appear much too big on the screen. - if (DPR != 1) { // Scaling down the canvas size with CSS size on HDPI screens - if (isWidth) - canvasElement.style.width = CSSProperties.WidthUnionType.of(fxCanvasSize + "px"); - else - canvasElement.style.height = CSSProperties.HeightUnionType.of(fxCanvasSize + "px"); - } - } + // HDPI management + private double getPixelDensity() { + return WebFxKitLauncher.getCanvasPixelDensity(canvas); } @Override @@ -164,11 +104,12 @@ public void transform(double mxx, double myx, double mxy, double myy, double mxt @Override public void setTransform(double mxx, double myx, double mxy, double myy, double mxt, double myt) { - if (DPR == 1) + double dpr = getPixelDensity(); + if (dpr == 1) ctx.setTransform(mxx, myx, mxy, myy, mxt, myt); else { ctx.setTransform(1, 0, 0, 1, 0, 0); // Same as ctx.resetTransform() (but not provided by Elemental2) - ctx.scale(DPR, DPR); + ctx.scale(dpr, dpr); ctx.transform(mxx, myx, mxy, myy, mxt, myt); } } @@ -285,11 +226,11 @@ private CanvasPattern toCanvasPattern(ImagePattern imagePattern) { private static boolean isImageLoadedWithoutError(HTMLImageElement imageElement) { return imageElement.complete // indicates that the image loading has finished (but not if it was successful or not) - // There is no specific HTML attribute to report if there was an error (such as HTTP 404 image not found), - // there is only an onerror handler, but it's too late to use here. So we use naturalWidth for the test, - // because when the image is successfully loaded the browser sets the naturalWidth value (assuming the - // image is not zero-sized), while the browser leaves that value to 0 on error. - && imageElement.naturalWidth != 0; + // There is no specific HTML attribute to report if there was an error (such as HTTP 404 image not found), + // there is only an onerror handler, but it's too late to use here. So we use naturalWidth for the test, + // because when the image is successfully loaded the browser sets the naturalWidth value (assuming the + // image is not zero-sized), while the browser leaves that value to 0 on error. + && imageElement.naturalWidth != 0; } private void applyProportionalFillLinearGradiant(double x, double y, double width, double height) { diff --git a/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/WebFxKitLauncher.java b/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/WebFxKitLauncher.java index 7b71a65da..697465f18 100644 --- a/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/WebFxKitLauncher.java +++ b/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/WebFxKitLauncher.java @@ -90,6 +90,22 @@ public static GraphicsContext getGraphicsContext2D(Canvas canvas, boolean willRe return getProvider().getGraphicsContext2D(canvas, willReadFrequently); } + // Canvas HDPI management. + // Pixel density initially equals to Screen.outputScale, but then can be changed (ex: 1 to get a low-res canvas). + + public static void setCanvasPixelDensity(Canvas canvas, double pixelDensity) { + getProvider().setCanvasPixelDensity(canvas, pixelDensity); + } + + public static double getCanvasPixelDensity(Canvas canvas) { + return canvas == null ? getDefaultCanvasPixelDensity() : getProvider().getCanvasPixelDensity(canvas); + } + + public static double getDefaultCanvasPixelDensity() { + return getProvider().getDefaultCanvasPixelDensity(); + } + + public static Bounds measureText(String text, Font font) { return getProvider().measureText(text, font); } diff --git a/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/spi/WebFxKitLauncherProvider.java b/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/spi/WebFxKitLauncherProvider.java index c25aec88a..d052f25e2 100644 --- a/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/spi/WebFxKitLauncherProvider.java +++ b/webfx-kit/webfx-kit-launcher/src/main/java/dev/webfx/kit/launcher/spi/WebFxKitLauncherProvider.java @@ -2,6 +2,7 @@ import javafx.application.Application; import javafx.application.HostServices; +import javafx.beans.property.DoubleProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Bounds; @@ -66,6 +67,27 @@ default GraphicsContext getGraphicsContext2D(Canvas canvas, boolean willReadFreq return canvas.getGraphicsContext2D(); } + default DoubleProperty canvasPixelDensityProperty(Canvas canvas) { + return null; + } + + default void setCanvasPixelDensity(Canvas canvas, double pixelDensity) { + DoubleProperty pixelDensityProperty = canvasPixelDensityProperty(canvas); + if (pixelDensityProperty != null) + pixelDensityProperty.set(pixelDensity); + } + + default double getCanvasPixelDensity(Canvas canvas) { + DoubleProperty pixelDensityProperty = canvasPixelDensityProperty(canvas); + if (pixelDensityProperty != null) + return pixelDensityProperty.doubleValue(); + return getDefaultCanvasPixelDensity(); + } + + default double getDefaultCanvasPixelDensity() { + return Screen.getPrimary().getOutputScaleX(); + } + Bounds measureText(String text, Font font); double measureBaselineOffset(Font font);