1 /*
   2  * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package test.com.sun.marlin;
  27 
  28 import java.util.Locale;
  29 import java.util.concurrent.CountDownLatch;
  30 import java.util.concurrent.TimeUnit;
  31 import java.util.logging.Handler;
  32 import java.util.logging.Level;
  33 import java.util.logging.LogRecord;
  34 import java.util.logging.Logger;
  35 
  36 import javafx.animation.KeyFrame;
  37 import javafx.animation.KeyValue;
  38 import javafx.animation.Timeline;
  39 import javafx.application.Application;
  40 import javafx.application.Platform;
  41 import javafx.beans.property.DoubleProperty;
  42 import javafx.beans.property.SimpleDoubleProperty;
  43 import javafx.geometry.Bounds;
  44 import javafx.scene.Group;
  45 import javafx.scene.Scene;
  46 import javafx.scene.paint.Color;
  47 import javafx.scene.shape.SVGPath;
  48 import javafx.stage.Stage;
  49 import javafx.util.Duration;
  50 
  51 import junit.framework.AssertionFailedError;
  52 import org.junit.AfterClass;
  53 import org.junit.Assert;
  54 import static org.junit.Assert.assertEquals;
  55 import org.junit.BeforeClass;
  56 import org.junit.Test;
  57 
  58 import static test.util.Util.TIMEOUT;
  59 
  60 /**
  61  * @test
  62  * @bug 8170140
  63  * @summary Check the rendering anomaly with MarlinFX renderer
  64  */
  65 public class QPathTest {
  66 
  67     private final static double SCALE = 2.0;
  68 
  69     private final static long MAX_DURATION = 3000 * 1000 * 1000L; // 3s
  70 
  71     // Used to launch the application before running any test
  72     private static final CountDownLatch launchLatch = new CountDownLatch(1);
  73 
  74     // Singleton Application instance
  75     static MyApp myApp;
  76 
  77     static boolean doChecksFailed = false;
  78 
  79     static {
  80         Locale.setDefault(Locale.US);
  81 
  82         // initialize j.u.l Looger:
  83         final Logger log = Logger.getLogger("prism.marlin");
  84         log.addHandler(new Handler() {
  85             @Override
  86             public void publish(LogRecord record) {
  87                 final Throwable th = record.getThrown();
  88                 // detect any Throwable:
  89                 if (th != null) {
  90                     System.out.println("Test failed:\n" + record.getMessage());
  91                     th.printStackTrace(System.out);
  92 
  93                     doChecksFailed = true;
  94 
  95                     throw new RuntimeException("Test failed: ", th);
  96                 }
  97             }
  98 
  99             @Override
 100             public void flush() {
 101             }
 102 
 103             @Override
 104             public void close() {
 105             }
 106         });
 107 
 108         // enable Marlin logging & internal checks:
 109         System.setProperty("prism.marlinrasterizer", "true");
 110         System.setProperty("prism.marlin.log", "true");
 111         System.setProperty("prism.marlin.useLogger", "true");
 112         System.setProperty("prism.marlin.doChecks", "true");
 113     }
 114 
 115     private CountDownLatch latch = new CountDownLatch(1);
 116 
 117     // Application class. An instance is created and initialized before running
 118     // the first test, and it lives through the execution of all tests.
 119     public static class MyApp extends Application {
 120 
 121         Stage stage = null;
 122 
 123         public MyApp() {
 124             super();
 125         }
 126 
 127         @Override
 128         public void init() {
 129             QPathTest.myApp = this;
 130         }
 131 
 132         @Override
 133         public void start(Stage primaryStage) throws Exception {
 134             this.stage = primaryStage;
 135             launchLatch.countDown();
 136         }
 137     }
 138 
 139     @BeforeClass
 140     public static void setupOnce() {
 141         // Start the Application
 142         new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start();
 143 
 144         try {
 145             if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 146                 throw new AssertionFailedError("Timeout waiting for Application to launch");
 147             }
 148         } catch (InterruptedException ex) {
 149             AssertionFailedError err = new AssertionFailedError("Unexpected exception");
 150             err.initCause(ex);
 151             throw err;
 152         }
 153 
 154         assertEquals(0, launchLatch.getCount());
 155     }
 156 
 157     @AfterClass
 158     public static void teardownOnce() {
 159         Platform.exit();
 160     }
 161 
 162     @Test(timeout = 10000)
 163     public void TestBug() {
 164         Platform.runLater(() -> {
 165             SVGPath path = new SVGPath();
 166             String svgpath = readPath();
 167             path.setContent(svgpath);
 168 
 169             Scene scene = new Scene(new Group(path), 400, 400, Color.WHITE);
 170             myApp.stage.setScene(scene);
 171             myApp.stage.show();
 172 
 173             DoubleProperty rscale = new SimpleDoubleProperty(SCALE);
 174             myApp.stage.renderScaleXProperty().bind(rscale);
 175             myApp.stage.renderScaleYProperty().bind(rscale);
 176 
 177             double scw = scene.getWidth();
 178             double sch = scene.getHeight();
 179             Bounds pathbounds = path.getBoundsInParent();
 180             double pathXoff = -pathbounds.getMinX();
 181             double pathYoff = -pathbounds.getMinY();
 182             double bounceW = scw - pathbounds.getWidth();
 183             double bounceH = sch - pathbounds.getHeight();
 184             double Xrate = (1.0 + Math.random()) / 2.0;
 185             double Yrate = (1.0 + Math.random()) / 2.0;
 186 
 187             DoubleProperty prop = new SimpleDoubleProperty();
 188 
 189             final long start = System.nanoTime();
 190 
 191             prop.addListener((Observable) -> {
 192                 if (doChecksFailed || System.nanoTime() - start > MAX_DURATION) {
 193                     latch.countDown();
 194                     myApp.stage.close();
 195                 }
 196                 double v = prop.doubleValue();
 197                 double x = Math.abs((((v * Xrate) % 2.0) - 1.0) * bounceW);
 198                 double y = Math.abs((((v * Yrate) % 2.0) - 1.0) * bounceH);
 199                 path.setTranslateX(pathXoff + x);
 200                 path.setTranslateY(pathYoff + y);
 201                 path.setContent(null);
 202                 path.setContent(svgpath);
 203             });
 204             int bignum = 1000000;
 205             KeyValue kv = new KeyValue(prop, bignum);
 206             KeyFrame kf = new KeyFrame(Duration.seconds(bignum), kv);
 207             Timeline t = new Timeline(kf);
 208             t.setCycleCount(Timeline.INDEFINITE);
 209             t.play();
 210         });
 211         try {
 212             latch.await();
 213         } catch (InterruptedException ie) {
 214             Logger.getLogger(QPathTest.class.getName()).log(Level.SEVERE, "interrupted", ie);
 215         }
 216         Assert.assertFalse("DoChecks detected a problem.", doChecksFailed);
 217     }
 218 
 219     static String readPath() {
 220         return "M54.589844,86.230469 C27.929688,86.230469 10.546875,107.714844 10.546875,140.722656 C10.546875,173.925781 "
 221                 + "27.734375,195.214844 54.589844,195.214844 C69.433594,195.214844 80.761719,189.062500 87.500000,177.539063 "
 222                 + "L89.062500,177.539063 L89.062500,228.515625 L106.054688,228.515625 L106.054688,88.085938 L89.843750,88.085938 "
 223                 + "L89.843750,105.664063 L88.281250,105.664063 C82.031250,93.847656 68.945313,86.230469 54.589844,86.230469 Z "
 224                 + "M58.398438,180.078125 C39.257813,180.078125 27.929688,165.429688 27.929688,140.722656 C27.929688,116.113281 "
 225                 + "39.355469,101.367188 58.496094,101.367188 C77.539063,101.367188 89.550781,116.601563 89.550781,140.722656 "
 226                 + "C89.550781,164.941406 77.636719,180.078125 58.398438,180.078125 Z ";
 227     }
 228 }