2013-06-25 8 views
15

私はjavafx 2.2または少なくともjavafx 8でこのようなことをしています。Text javadoccss referenceを検索しました。ベジェ曲線に沿ってテキストを書き込む方法は?

enter image description here

WebViewに表示し、SVGでこの効果を行うことが可能です。しかし、私のアプリケーションは、この効果を持つ多くのテキストを表示する必要があります。 WebViewは、このエフェクトを使用してテキストを描画するには重すぎます。

私はoracle technology networkで同じ質問をしました。

+0

これがあなたに役立つかどうかはわかりません。私はunity3dのベジエ曲線に沿ってグラフィックスを動かさなければなりませんでした。私たちはsvgトレースパスを使いました。これは私たちが図書館を作るために使ったものです。 http://www.w3.org/TR/SVG/paths.html – MichelleJS

+0

@MichelleJSありがとうございますが、SVGはjavafxでは実際にサポートされていません。 – gontard

+0

これはおそらくあなたを助けるかもしれません:https://forums.oracle.com/thread/1712335 – Sebastian

答えて

23

Bézier Curveに沿ってプロットされたテキストを取得するPathTransitionの乱用です。

このプログラムでは、コントロールポイントをドラッグしてカーブを定義し、そのカーブに沿ってテキストをプロットすることができます。テキスト内の文字は等間隔に配置されているため、カーブの全長が「通常の」スペーシングでテキストの幅に非常に近く、カーニングなどの調整が行われない場合に最適です。

ショー以下のサンプル:glow効果と

  1. 湾曲したテキスト。
  2. 効果が適用されていない曲線テキストです。
  3. 効果なしのテキストを曲線パスで定義するために使用されるコントロール操作ポイントがプロットされています。

Curved Text With Glow Curved Text Curve Manipulator

ソリューションはStackOverflowの質問に対する回答に基づいて迅速なハックでした:CubicCurve JavaFX。私はより良い解決策がより多くの努力、時間とスキルで見つけられると確信しています。

プログラムはトランジションに基づいているため、テキストをアニメーション化してカーブに沿ってアニメーションしたり、オーバーフロー時に右から左に折り返したりすることができます(marquee textまたは株式チッカー)。

グロウ、シャドウなどの標準的なJavaFXエフェクトやフォントの変更を適用して、質問のpainttshopプロテキストからシャドーエフェクトのようなものを取得することができます。グロー効果は、回転した文字の周りのギザギザのエッジを微妙に柔らかくするので、ここで適用すると良い効果です。

また、このソリューションのPathTransitionは、パスの入力として任意の形状を取ることができるため、テキストは3次曲線だけでなく、他の種類のパスに従うことができます。

import javafx.animation.*; 
import javafx.application.Application; 
import javafx.beans.property.DoubleProperty; 
import javafx.collections.*; 
import javafx.event.*; 
import javafx.scene.*; 
import javafx.scene.control.ToggleButton; 
import javafx.scene.effect.Glow; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.*; 
import javafx.scene.text.Text; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

/** 
* Example of drawing text along a cubic curve. 
* Drag the anchors around to change the curve. 
*/ 
public class BezierTextPlotter extends Application { 
    private static final String CURVED_TEXT = "Bézier Curve"; 

    public static void main(String[] args) throws Exception { 
     launch(args); 
    } 

    @Override 
    public void start(final Stage stage) throws Exception { 
     final CubicCurve curve = createStartingCurve(); 

     Line controlLine1 = new BoundLine(curve.controlX1Property(), curve.controlY1Property(), curve.startXProperty(), curve.startYProperty()); 
     Line controlLine2 = new BoundLine(curve.controlX2Property(), curve.controlY2Property(), curve.endXProperty(), curve.endYProperty()); 

     Anchor start = new Anchor(Color.PALEGREEN, curve.startXProperty(), curve.startYProperty()); 
     Anchor control1 = new Anchor(Color.GOLD, curve.controlX1Property(), curve.controlY1Property()); 
     Anchor control2 = new Anchor(Color.GOLDENROD, curve.controlX2Property(), curve.controlY2Property()); 
     Anchor end = new Anchor(Color.TOMATO, curve.endXProperty(), curve.endYProperty()); 

     final Text text = new Text(CURVED_TEXT); 
     text.setStyle("-fx-font-size: 40px"); 
     text.setEffect(new Glow()); 
     final ObservableList<Text> parts = FXCollections.observableArrayList(); 
     final ObservableList<PathTransition> transitions = FXCollections.observableArrayList(); 
     for (char character : text.textProperty().get().toCharArray()) { 
      Text part = new Text(character + ""); 
      part.setEffect(text.getEffect()); 
      part.setStyle(text.getStyle()); 
      parts.add(part); 
      part.setVisible(false); 

      transitions.add(createPathTransition(curve, part)); 
     } 

     final ObservableList<Node> controls = FXCollections.observableArrayList(); 
     controls.setAll(controlLine1, controlLine2, curve, start, control1, control2, end); 

     final ToggleButton plot = new ToggleButton("Plot Text"); 
     plot.setOnAction(new PlotHandler(plot, parts, transitions, controls)); 

     Group content = new Group(controlLine1, controlLine2, curve, start, control1, control2, end, plot); 
     content.getChildren().addAll(parts); 

     stage.setTitle("Cubic Curve Manipulation Sample"); 
     stage.setScene(new Scene(content, 400, 400, Color.ALICEBLUE)); 
     stage.show(); 
    } 

    private PathTransition createPathTransition(CubicCurve curve, Text text) { 
     final PathTransition transition = new PathTransition(Duration.seconds(10), curve, text); 

     transition.setAutoReverse(false); 
     transition.setCycleCount(PathTransition.INDEFINITE); 
     transition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT); 
     transition.setInterpolator(Interpolator.LINEAR); 

     return transition; 
    } 

    private CubicCurve createStartingCurve() { 
     CubicCurve curve = new CubicCurve(); 
     curve.setStartX(50); 
     curve.setStartY(200); 
     curve.setControlX1(150); 
     curve.setControlY1(300); 
     curve.setControlX2(250); 
     curve.setControlY2(50); 
     curve.setEndX(350); 
     curve.setEndY(150); 
     curve.setStroke(Color.FORESTGREEN); 
     curve.setStrokeWidth(4); 
     curve.setStrokeLineCap(StrokeLineCap.ROUND); 
     curve.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6)); 
     return curve; 
    } 

    class BoundLine extends Line { 
     BoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) { 
      startXProperty().bind(startX); 
      startYProperty().bind(startY); 
      endXProperty().bind(endX); 
      endYProperty().bind(endY); 
      setStrokeWidth(2); 
      setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5)); 
      setStrokeLineCap(StrokeLineCap.BUTT); 
      getStrokeDashArray().setAll(10.0, 5.0); 
     } 
    } 

    // a draggable anchor displayed around a point. 
    class Anchor extends Circle { 
     Anchor(Color color, DoubleProperty x, DoubleProperty y) { 
      super(x.get(), y.get(), 10); 
      setFill(color.deriveColor(1, 1, 1, 0.5)); 
      setStroke(color); 
      setStrokeWidth(2); 
      setStrokeType(StrokeType.OUTSIDE); 

      x.bind(centerXProperty()); 
      y.bind(centerYProperty()); 
      enableDrag(); 
     } 

     // make a node movable by dragging it around with the mouse. 
     private void enableDrag() { 
      final Delta dragDelta = new Delta(); 
      setOnMousePressed(new EventHandler<MouseEvent>() { 
       @Override 
       public void handle(MouseEvent mouseEvent) { 
        // record a delta distance for the drag and drop operation. 
        dragDelta.x = getCenterX() - mouseEvent.getX(); 
        dragDelta.y = getCenterY() - mouseEvent.getY(); 
        getScene().setCursor(Cursor.MOVE); 
       } 
      }); 
      setOnMouseReleased(new EventHandler<MouseEvent>() { 
       @Override 
       public void handle(MouseEvent mouseEvent) { 
        getScene().setCursor(Cursor.HAND); 
       } 
      }); 
      setOnMouseDragged(new EventHandler<MouseEvent>() { 
       @Override 
       public void handle(MouseEvent mouseEvent) { 
        double newX = mouseEvent.getX() + dragDelta.x; 
        if (newX > 0 && newX < getScene().getWidth()) { 
         setCenterX(newX); 
        } 
        double newY = mouseEvent.getY() + dragDelta.y; 
        if (newY > 0 && newY < getScene().getHeight()) { 
         setCenterY(newY); 
        } 
       } 
      }); 
      setOnMouseEntered(new EventHandler<MouseEvent>() { 
       @Override 
       public void handle(MouseEvent mouseEvent) { 
        if (!mouseEvent.isPrimaryButtonDown()) { 
         getScene().setCursor(Cursor.HAND); 
        } 
       } 
      }); 
      setOnMouseExited(new EventHandler<MouseEvent>() { 
       @Override 
       public void handle(MouseEvent mouseEvent) { 
        if (!mouseEvent.isPrimaryButtonDown()) { 
         getScene().setCursor(Cursor.DEFAULT); 
        } 
       } 
      }); 
     } 

     // records relative x and y co-ordinates. 
     private class Delta { 
      double x, y; 
     } 
    } 

    // plots text along a path defined by provided bezier control points. 
    private static class PlotHandler implements EventHandler<ActionEvent> { 
     private final ToggleButton plot; 
     private final ObservableList<Text> parts; 
     private final ObservableList<PathTransition> transitions; 
     private final ObservableList<Node> controls; 

     public PlotHandler(ToggleButton plot, ObservableList<Text> parts, ObservableList<PathTransition> transitions, ObservableList<Node> controls) { 
      this.plot = plot; 
      this.parts = parts; 
      this.transitions = transitions; 
      this.controls = controls; 
     } 

     @Override 
     public void handle(ActionEvent actionEvent) { 
      if (plot.isSelected()) { 
       for (int i = 0; i < parts.size(); i++) { 
        parts.get(i).setVisible(true); 
        final Transition transition = transitions.get(i); 
        transition.stop(); 
        transition.jumpTo(Duration.seconds(10).multiply((i + 0.5) * 1.0/parts.size())); 
        // just play a single animation frame to display the curved text, then stop 
        AnimationTimer timer = new AnimationTimer() { 
         int frameCounter = 0; 

         @Override 
         public void handle(long l) { 
          frameCounter++; 
          if (frameCounter == 1) { 
           transition.stop(); 
           stop(); 
          } 
         } 
        }; 
        timer.start(); 
        transition.play(); 
       } 
       plot.setText("Show Controls"); 
      } else { 
       plot.setText("Plot Text"); 
      } 

      for (Node control : controls) { 
       control.setVisible(!plot.isSelected()); 
      } 

      for (Node part : parts) { 
       part.setVisible(plot.isSelected()); 
      } 
     } 
    } 
} 

解決策のもう1つの可能性は、各テキスト文字を測定し、PathTransitionを使用せずにテキストの位置と回転を補間する数学を行うことです。しかし、PathTransitionはすでにそこにあり、私のためにうまくいきました(おそらくテキストの進歩のためのカーブの距離測定は私にとにかく挑戦するかもしれません)。追加の質問へ

回答は

あなたはあなたのコードを適応させることによってjavafx.scene.effect.Effectを実現することが可能であると思いますか? (それだけでこれを行うには、既存のPathTransitionを採用していたように)私の答えが用意されていませんベジエ曲線に沿ってテキストを表示するための数学を実行する必要になる効果を実装

号。

さらに、独自のカスタムエフェクトを実装するためのJavaFX 2.2には公開APIはありません。

類似のものを得るためにおそらく使用される可能性のあるDisplacementMapエフェクトが存在します。しかし、DisplacementMapエフェクト(およびおそらくテキストレイアウトを調整するエフェクト)を使用すると、テキストが歪む可能性が高くなります。

IMOでは、ベジエ曲線に沿ったテキストの描画は、エフェクト関連よりもレイアウトに関連しています。エフェクトを使用して移動するのではなく、レイアウトと回転を調整することをお勧めします。

また、JFXフレームワークで正しく統合するためのより良い方法があるかもしれませんか?

あなたはペインをサブクラス化しFlowPaneに似ていますが、パスではなく、直線に沿ってノードをレイアウトするカスタムPathLayoutを作成することができます。レイアウトされるノードは、各文字のTextノードによって構成されます。これは、私の答えで行ったのと同様です。しかし、それでもなお、正確にテキストをレンダリングしていないのは、等間隔の文字(kerningなど)を考慮したいからです。したがって、忠実度と正確さのためには、独自の低レベルテキストレイアウトアルゴリズムを実装する必要があります。 PathTransitionsを使用してこの回答で提供された「十分に良い」ソリューションが十分な品質でないことが判明した場合は、私の場合は、私はその努力に尽くします。

+0

素敵な回答ありがとう!あなたのコードを適応させてjavafx.scene.effect.Effectを実装することは可能だと思いますか?あるいは、JFXフレームワークでそれを適切に統合するためのより良い方法があるかもしれませんか? – gontard

+0

@gontard私はあなたの追加質問に答えるために私の答えを編集しました。 – jewelsea

+0

これらの精度と非常に完全な答えをありがとう。私は、AnimationPathHelperまたはPathIteratorに基づいてカスタムペインを作成すると思います。時間をとってくれてありがとう。 – gontard

7

WebViewといくつかのhtmlを使ってsvgを表示できます。次に例を示します。

import javafx.application.Application; 
import javafx.scene.Scene; 
import javafx.scene.layout.StackPane; 
import javafx.scene.web.WebView; 
import javafx.stage.Stage; 

public class CurvedText extends Application { 

    public static void main(String[] args) { 
    launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception { 
    StackPane root = new StackPane(); 
    WebView view = new WebView(); 
    view.getEngine().loadContent("<!DOCTYPE html>\n" + 
      "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + 
      " <body>\n" + 
      "<embed width=\"100\" height=\"100\" type=\"image/svg+xml\" src=\"path.svg\">\n" + 
      " <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" + 
      "<defs>\n" + 
      " <path id=\"textPath\" d=\"M10 50 C10 0 90 0 90 50\"/>\n" + 
      "</defs>\n"+ 
      "<text fill=\"red\">\n" + 
      " <textPath xlink:href=\"#textPath\">Text on a Path</textPath>\n" + 
      "</text>" + 
      "</svg>\n" + 
      "</embed>" + 
      " </body>\n" + 
      "</html>"); 
    root.getChildren().add(view); 
    Scene scene = new Scene(root, 500, 500); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
    } 
} 

結果:

enter image description here

それは私の経験では、ラベルのように振る舞う必要がある場合のJavaFXのWebViewは少し微妙な振る舞いので、これは何の最適解されていないが、それがされ何かで始める。

EDIT

あなたは直接のWebViewを使用したくないので、あなたがHTMLでシーンをレンダリングして、ImageViewのを生成することのスナップショットを取るためのWebViewの単一のインスタンスを使用することができます。この例を参照してください:

ここ
import javafx.animation.AnimationTimer; 
import javafx.application.Application; 
import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.concurrent.Worker; 
import javafx.scene.Scene; 
import javafx.scene.image.ImageView; 
import javafx.scene.image.WritableImage; 
import javafx.scene.layout.HBox; 
import javafx.scene.web.WebView; 
import javafx.stage.Stage; 

public class CurvedText extends Application { 

    public static void main(String[] args) { 
    launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception { 
    final HBox root = new HBox(); 
    final WebView view = new WebView(); 
    view.getEngine().loadContent("<!DOCTYPE html>\n" + 
      "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + 
      " <body>\n" + 
      "<embed width=\"100\" height=\"100\" type=\"image/svg+xml\" src=\"path.svg\">\n" + 
      " <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" + 
      "<defs>\n" + 
      " <path id=\"textPath\" d=\"M10 50 C10 0 90 0 90 50\"/>\n" + 
      "</defs>\n"+ 
      "<text fill=\"red\">\n" + 
      " <textPath xlink:href=\"#textPath\">Text on a Path</textPath>\n" + 
      "</text>" + 
      "</svg>\n" + 
      "</embed>" + 
      " </body>\n" + 
      "</html>"); 
    root.getChildren().add(view); 
    view.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() { 
     @Override 
     public void changed(ObservableValue<? extends Worker.State> arg0, Worker.State oldState, Worker.State newState) { 
     if (newState == Worker.State.SUCCEEDED) { 
      // workaround for https://javafx-jira.kenai.com/browse/RT-23265 
      AnimationTimer waitForViewToBeRendered = new AnimationTimer(){ 
      private int frames = 0; 
      @Override 
      public void handle(long now) { 
       if (frames++ > 3){ 
       WritableImage snapshot = view.snapshot(null, null); 
       ImageView imageView = new ImageView(snapshot); 
       root.getChildren().add(imageView); 
       this.stop(); 
       } 
      } 
      }; 
      waitForViewToBeRendered.start(); 
     } 
     } 
    }); 
    Scene scene = new Scene(root, 500, 500); 
    primaryStage.setScene(scene); 
    primaryStage.show(); 
    } 
} 
+0

ご協力ありがとうございます。WebViewを使用すると、アプリケーションでこのエフェクトを含む多くのテキストを表示する必要があるため、使用することはできません。 WebViewは、このエフェクトを使用してテキストを描画するには重すぎます。 – gontard

+0

@gontard私の編集を参照してください – Sebastian

関連する問題