From 262fa3ba47efd65d7f6860cc061f0070e6f22d0f Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 20 Jul 2019 21:47:49 -0400 Subject: [PATCH 1/3] ovelay renderer: more gracefully handle exceptions If an overlay throws an exception it should not prevent other overlays in the layer from being rendererd. --- .../client/ui/overlay/OverlayRenderer.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index a9f2e9f017..9e5820470a 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -37,6 +37,7 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.SwingUtilities; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.MenuAction; @@ -56,6 +57,7 @@ import net.runelite.client.ui.JagexColors; import net.runelite.client.util.ColorUtil; @Singleton +@Slf4j public class OverlayRenderer extends MouseAdapter implements KeyListener { private static final int BORDER = 5; @@ -458,8 +460,23 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener } subGraphics.translate(point.x, point.y); - final Dimension dimension = MoreObjects.firstNonNull(overlay.render(subGraphics), new Dimension()); - subGraphics.dispose(); + + final Dimension overlayDimension; + try + { + overlayDimension = overlay.render(subGraphics); + } + catch (Exception ex) + { + log.warn("Error during overlay rendering", ex); + return; + } + finally + { + subGraphics.dispose(); + } + + final Dimension dimension = MoreObjects.firstNonNull(overlayDimension, new Dimension()); overlay.setBounds(new Rectangle(point, dimension)); } From 34243f727c49f5ccd27095016bb56804e5edf29c Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 20 Jul 2019 21:52:33 -0400 Subject: [PATCH 2/3] tooltip overlay: move tooltip clearing to a finally block Tooltips must always be cleared after each frame, and the overlay is responsible for this. This fixes tooltips erroneously stacking up in the event of an exception in the overlay --- .../client/ui/overlay/tooltip/TooltipOverlay.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java index 9436da087f..da1600450e 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java @@ -66,6 +66,19 @@ public class TooltipOverlay extends Overlay return null; } + try + { + return renderTooltips(graphics, tooltips); + } + finally + { + // Tooltips must always be cleared each frame + tooltipManager.clear(); + } + } + + private Dimension renderTooltips(Graphics2D graphics, List tooltips) + { final Rectangle clientCanvasBounds = new Rectangle(client.getRealDimensions()); final net.runelite.api.Point mouseCanvasPosition = client.getMouseCanvasPosition(); final Point mousePosition = new Point(mouseCanvasPosition.getX(), mouseCanvasPosition.getY() + OFFSET); @@ -113,7 +126,6 @@ public class TooltipOverlay extends Overlay newBounds.width = Math.max(newBounds.width, dimension.width); } - tooltipManager.clear(); return newBounds.getSize(); } } From 877df92d11b01f003d3ba7d5986e4f0b31e4f2a8 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 20 Jul 2019 21:37:30 -0400 Subject: [PATCH 3/3] tooltip component: fix exception from malformed tags --- .../overlay/components/TooltipComponent.java | 14 ++++-- .../components/TooltipComponentTest.java | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/ui/overlay/components/TooltipComponentTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java index 4d606b40ca..2e4b943063 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java @@ -24,6 +24,7 @@ */ package net.runelite.client.ui.overlay.components; +import com.google.common.annotations.VisibleForTesting; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; @@ -96,6 +97,7 @@ public class TooltipComponent implements RenderableEntity char[] chars = line.toCharArray(); int begin = 0; + boolean inTag = false; for (int j = 0; j < chars.length; j++) { if (chars[j] == '<') @@ -110,8 +112,9 @@ public class TooltipComponent implements RenderableEntity lineX += metrics.stringWidth(text); begin = j; + inTag = true; } - else if (chars[j] == '>') + else if (chars[j] == '>' && inTag) { String subLine = line.substring(begin + 1, j); @@ -148,6 +151,7 @@ public class TooltipComponent implements RenderableEntity } begin = j + 1; + inTag = false; } } @@ -162,12 +166,14 @@ public class TooltipComponent implements RenderableEntity return new Dimension(tooltipWidth + OFFSET * 2, tooltipHeight + OFFSET * 2); } - private static int calculateTextWidth(FontMetrics metrics, String line) + @VisibleForTesting + static int calculateTextWidth(FontMetrics metrics, String line) { char[] chars = line.toCharArray(); int textWidth = 0; int begin = 0; + boolean inTag = false; for (int j = 0; j < chars.length; j++) { if (chars[j] == '<') @@ -175,8 +181,9 @@ public class TooltipComponent implements RenderableEntity textWidth += metrics.stringWidth(line.substring(begin, j)); begin = j; + inTag = true; } - else if (chars[j] == '>') + else if (chars[j] == '>' && inTag) { String subLine = line.substring(begin + 1, j); @@ -190,6 +197,7 @@ public class TooltipComponent implements RenderableEntity } begin = j + 1; + inTag = false; } } diff --git a/runelite-client/src/test/java/net/runelite/client/ui/overlay/components/TooltipComponentTest.java b/runelite-client/src/test/java/net/runelite/client/ui/overlay/components/TooltipComponentTest.java new file mode 100644 index 0000000000..f50bf45530 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/ui/overlay/components/TooltipComponentTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.overlay.components; + +import java.awt.FontMetrics; +import static net.runelite.client.ui.overlay.components.TooltipComponent.calculateTextWidth; +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TooltipComponentTest +{ + @Test + public void testCalculateTextWidth() + { + FontMetrics fontMetics = mock(FontMetrics.class); + when(fontMetics.stringWidth(anyString())).thenAnswer((invocation) -> ((String) invocation.getArguments()[0]).length()); + + assertEquals(11, calculateTextWidth(fontMetics, "line1>line2")); + } +} \ No newline at end of file