JavaCAD
TextExtrude.java
Go to the documentation of this file.
1 package eu.mihosoft.vrl.v3d;
2 
3 import java.util.ArrayList;
4 import java.util.List;
5 
6 import javafx.scene.shape.ClosePath;
7 import javafx.scene.shape.CubicCurveTo;
8 import javafx.scene.shape.LineTo;
9 import javafx.scene.shape.MoveTo;
10 import javafx.scene.shape.Path;
11 import javafx.scene.shape.PathElement;
12 import javafx.scene.shape.QuadCurveTo;
13 import javafx.scene.shape.Rectangle;
14 import javafx.scene.shape.Shape;
15 import javafx.scene.text.Font;
16 import javafx.scene.text.Text;
17 import java.util.stream.Collectors;
18 import java.util.stream.IntStream;
19 import javafx.beans.property.DoubleProperty;
20 import javafx.beans.property.SimpleDoubleProperty;
21 import javafx.scene.paint.Color;
22 
23 // TODO: Auto-generated Javadoc
28 @SuppressWarnings("restriction")
29 public class TextExtrude {
30  private static final String default_font = "FreeSerif";
31  private final static int POINTS_CURVE = 10;
32 
33  private final String text;
34  private List<Vector3d> points;
35  private Vector3d p0;
36  private final List<LineSegment> polis = new ArrayList<>();
37  ArrayList<CSG> sections = new ArrayList<CSG>();
38  ArrayList<CSG> holes = new ArrayList<CSG>();
39  private double dir;
40 
41  class LineSegment {
42 
43  /*
44  * Given one single character in terms of Path, LineSegment stores a
45  * list of points that define the exterior of one of its polygons
46  * (!isHole). It can contain reference to one or several holes inside
47  * this polygon. Or it can define the perimeter of a hole (isHole), with
48  * no more holes inside.
49  */
50 
51  private boolean hole;
52  private List<Vector3d> points;
53  private Path path;
54  private Vector3d origen;
55  private List<LineSegment> holes = new ArrayList<>();
56  private String letter;
57 
58  public LineSegment(String text) {
59  letter = text;
60  }
61 
62  public String getLetter() {
63  return letter;
64  }
65 
66  public void setLetter(String letter) {
67  this.letter = letter;
68  }
69 
70  public boolean isHole() {
71  return hole;
72  }
73 
74  public void setHole(boolean isHole) {
75  this.hole = isHole;
76  }
77 
78  public List<Vector3d> getPoints() {
79  return points;
80  }
81 
82  public void setPoints(List<Vector3d> points) {
83  this.points = points;
84  }
85 
86  public Path getPath() {
87  return path;
88  }
89 
90  public void setPath(Path path) {
91  this.path = path;
92  }
93 
94  public Vector3d getOrigen() {
95  return origen;
96  }
97 
98  public void setOrigen(Vector3d origen) {
99  this.origen = origen;
100  }
101 
102  public List<LineSegment> getHoles() {
103  return holes;
104  }
105 
106  public void setHoles(List<LineSegment> holes) {
107  this.holes = holes;
108  }
109 
110  public void addHole(LineSegment hole) {
111  holes.add(hole);
112  }
113 
114  @Override
115  public String toString() {
116  return "Poly{" + "points=" + points + ", path=" + path + ", origen=" + origen + ", holes=" + holes + '}';
117  }
118  }
119 
120  private TextExtrude(String text, Font font, double dir) {
121  if(dir<=0)
122  throw new NumberFormatException("length can not be negative");
123  this.dir = dir;
124  points = new ArrayList<>();
125  this.text=text;
126  Text textNode = new Text(text);
127  textNode.setFont(font);
128 
129  // Convert Text to Path
130  Path subtract = (Path) (Shape.subtract(textNode, new Rectangle(0, 0)));
131  // Convert Path elements into lists of points defining the perimeter
132  // (exterior or interior)
133  subtract.getElements().forEach(this::getPoints);
134 
135  // Group exterior polygons with their interior polygons
136 // polis.stream().filter(LineSegment::isHole).forEach(hole -> {
137 // polis.stream().filter(poly -> !poly.isHole())
138 // .filter(poly -> !((Path) Shape.intersect(poly.getPath(), hole.getPath())).getElements().isEmpty())
139 // .filter(poly -> poly.getPath().contains(new Point2D(hole.getOrigen().x, hole.getOrigen().y)))
140 // .forEach(poly -> poly.addHole(hole));
141 // });
142  //polis.removeIf(LineSegment::isHole);
143 
144  for (int i = 0; i < sections.size(); i++) {
145  for (CSG h : holes) {
146  try {
147  if (sections.get(i).touching(h)) {
148  // println "Hole found "
149  CSG nl = sections.get(i).difference(h);
150 
151  sections.set(i, nl);
152  }
153  } catch (Exception e) {
154 
155  }
156  }
157  }
158  }
159 
173  @SuppressWarnings("restriction")
174  public static ArrayList<CSG> text(double dir, String text, Font font) {
175 
176  TextExtrude te = new TextExtrude(text, font, dir);
177 
178  return te.sections;
179  }
180 
181  public List<LineSegment> getLineSegment() {
182  return polis;
183  }
184 
185  public List<Vector3d> getOffset(){
186  return polis.stream().sorted((p1,p2)->(int)(p1.getOrigen().x-p2.getOrigen().x))
187  .map(LineSegment::getOrigen).collect(Collectors.toList());
188  }
189 
190  private void getPoints(PathElement elem){
191  if(elem instanceof MoveTo){
192  loadPoints();
193  p0=new Vector3d((float)((MoveTo)elem).getX(),(float)((MoveTo)elem).getY(),0f);
194  points.add(p0);
195  } else if(elem instanceof LineTo){
196  points.add(new Vector3d((float)((LineTo)elem).getX(),(float)((LineTo)elem).getY(),0f));
197  } else if(elem instanceof CubicCurveTo){
198  Vector3d ini = (points.size()>0?points.get(points.size()-1):p0);
199  IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->points.add(evalCubicBezier((CubicCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
200  } else if(elem instanceof QuadCurveTo){
201  Vector3d ini = (points.size()>0?points.get(points.size()-1):p0);
202  IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->points.add(evalQuadBezier((QuadCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
203  } else if(elem instanceof ClosePath){
204  points.add(p0);
205  // Every closed path is a polygon (exterior or interior==hole)
206  // the text, the list of points and a new path between them are
207  // stored in a LineSegment: a continuous line that can change direction
208  if(Math.abs(getArea())>0.001){
209  LineSegment line = new LineSegment(text);
210  line.setHole(isHole());
211  line.setPoints(points);
212  line.setPath(generatePath());
213  line.setOrigen(p0);
214  polis.add(line);
215  }
216  loadPoints();
217 
218  }
219  }
220 
221  private void loadPoints(){
222  if(points.size()>4){
223  points.remove(points.size() - 1);
224  //points.remove(points.size() - 1);
225  boolean hole = Extrude.isCCW(Polygon.fromPoints(points));
226  CSG newLetter = Extrude.points(new Vector3d(0, 0, dir), points);
227 
228  if (!hole)
229  sections.add(newLetter);
230  else
231  holes.add(newLetter);
232  }
233  points=new ArrayList<>();
234  }
235 
236  private Vector3d evalCubicBezier(CubicCurveTo c, Vector3d ini, double t){
237  Vector3d p=new Vector3d((float)(Math.pow(1-t,3)*ini.x+
238  3*t*Math.pow(1-t,2)*c.getControlX1()+
239  3*(1-t)*t*t*c.getControlX2()+
240  Math.pow(t, 3)*c.getX()),
241  (float)(Math.pow(1-t,3)*ini.y+
242  3*t*Math.pow(1-t, 2)*c.getControlY1()+
243  3*(1-t)*t*t*c.getControlY2()+
244  Math.pow(t, 3)*c.getY()),
245  0f);
246  return p;
247  }
248 
249  private Vector3d evalQuadBezier(QuadCurveTo c, Vector3d ini, double t){
250  Vector3d p=new Vector3d((float)(Math.pow(1-t,2)*ini.x+
251  2*(1-t)*t*c.getControlX()+
252  Math.pow(t, 2)*c.getX()),
253  (float)(Math.pow(1-t,2)*ini.y+
254  2*(1-t)*t*c.getControlY()+
255  Math.pow(t, 2)*c.getY()),
256  0f);
257  return p;
258  }
259 
260  private double getArea(){
261  DoubleProperty res=new SimpleDoubleProperty();
262  IntStream.range(0, points.size()-1)
263  .forEach(i->res.set(res.get()+points.get(i).cross(points.get(i+1)).z));
264  // System.out.println("path: "+res.doubleValue()/2);
265 
266  return res.doubleValue()/2d;
267  }
268 
269  private boolean isHole(){
270  // area>0 -> the path is a hole, clockwise (y up)
271  // area<0 -> the path is a polygon, counterclockwise (y up)
272  return getArea()>0;
273  }
274 
275  private Path generatePath(){
276  Path path = new Path(new MoveTo(points.get(0).x,points.get(0).y));
277  points.stream().skip(1).forEach(p->path.getElements().add(new LineTo(p.x,p.y)));
278  path.getElements().add(new ClosePath());
279  path.setStroke(Color.GREEN);
280  // Path must be filled to allow Shape.intersect
281  path.setFill(Color.RED);
282  return path;
283  }
284 }
CSG difference(List< CSG > csgs)
Definition: CSG.java:1061
static boolean isCCW(Polygon polygon)
Definition: Extrude.java:258
static CSG points(Vector3d dir, List< Vector3d > points)
Definition: Extrude.java:199
static Polygon fromPoints(List< Vector3d > points, PropertyStorage shared)
Definition: Polygon.java:386
void getPoints(PathElement elem)
Vector3d evalQuadBezier(QuadCurveTo c, Vector3d ini, double t)
List< LineSegment > getLineSegment()
TextExtrude(String text, Font font, double dir)
Vector3d evalCubicBezier(CubicCurveTo c, Vector3d ini, double t)
static Vector3d y(double y)
Definition: Vector3d.java:484
static Vector3d x(double x)
Definition: Vector3d.java:474