]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/TextStroke.java
G2DParentNode handles "undefined" child bounds separately
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / TextStroke.java
1 /*
2 Copyright 2006 Jerry Huxtable
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15  */
16
17 package org.simantics.scenegraph.utils;
18
19 import java.awt.Font;
20 import java.awt.Shape;
21 import java.awt.Stroke;
22 import java.awt.font.FontRenderContext;
23 import java.awt.font.GlyphVector;
24 import java.awt.geom.AffineTransform;
25 import java.awt.geom.FlatteningPathIterator;
26 import java.awt.geom.GeneralPath;
27 import java.awt.geom.PathIterator;
28 import java.awt.geom.Point2D;
29
30 /**
31  * Taken from http://www.jhlabs.com/java/java2d/strokes/
32  */
33 public class TextStroke implements Stroke {
34     private final String text;
35     private final Font font;
36     private boolean stretchToFit = false;
37     private boolean repeat = false;
38     private final AffineTransform t = new AffineTransform();
39
40     private static final float FLATNESS = 1;
41
42     public TextStroke( String text, Font font ) {
43         this( text, font, true, false );
44     }
45
46     public TextStroke( String text, Font font, boolean stretchToFit, boolean repeat ) {
47         this.text = text;
48         this.font = font;
49         this.stretchToFit = stretchToFit;
50         this.repeat = repeat;
51     }
52
53     @Override
54     public Shape createStrokedShape( Shape shape ) {
55         FontRenderContext frc = new FontRenderContext(null, true, true);
56         GlyphVector glyphVector = font.createGlyphVector(frc, text);
57
58         GeneralPath result = new GeneralPath();
59         PathIterator it = new FlatteningPathIterator( shape.getPathIterator( null ), FLATNESS );
60         float points[] = new float[6];
61         float moveX = 0, moveY = 0;
62         float lastX = 0, lastY = 0;
63         float thisX = 0, thisY = 0;
64         int type = 0;
65         @SuppressWarnings("unused")
66                 boolean first = false;
67         float next = 0;
68         int currentChar = 0;
69         int length = glyphVector.getNumGlyphs();
70
71         if ( length == 0 )
72             return result;
73
74         float factor = stretchToFit ? measurePathLength( shape )/(float)glyphVector.getLogicalBounds().getWidth() : 1.0f;
75         float nextAdvance = 0;
76
77         while ( currentChar < length && !it.isDone() ) {
78             type = it.currentSegment( points );
79             switch( type ){
80                 case PathIterator.SEG_MOVETO:
81                     moveX = lastX = points[0];
82                     moveY = lastY = points[1];
83                     result.moveTo( moveX, moveY );
84                     first = true;
85                     nextAdvance = glyphVector.getGlyphMetrics( currentChar ).getAdvance() * 0.5f;
86                     next = nextAdvance;
87                     break;
88
89                 case PathIterator.SEG_CLOSE:
90                     points[0] = moveX;
91                     points[1] = moveY;
92                     // Fall into....
93
94                 case PathIterator.SEG_LINETO:
95                     thisX = points[0];
96                     thisY = points[1];
97                     float dx = thisX-lastX;
98                     float dy = thisY-lastY;
99                     float distance = (float)Math.sqrt( dx*dx + dy*dy );
100                     if ( distance >= next ) {
101                         float r = 1.0f/distance;
102                         float angle = (float)Math.atan2( dy, dx );
103                         while ( currentChar < length && distance >= next ) {
104                             Shape glyph = glyphVector.getGlyphOutline( currentChar );
105                             Point2D p = glyphVector.getGlyphPosition(currentChar);
106                             float px = (float)p.getX();
107                             float py = (float)p.getY();
108                             float x = lastX + next*dx*r;
109                             float y = lastY + next*dy*r;
110                             float advance = nextAdvance;
111                             nextAdvance = currentChar < length-1 ? glyphVector.getGlyphMetrics(currentChar+1).getAdvance() * 0.5f : 0;
112                             t.setToTranslation( x, y );
113                             t.rotate( angle );
114                             t.translate( -px-advance, -py );
115                             result.append( t.createTransformedShape( glyph ), false );
116                             next += (advance+nextAdvance) * factor;
117                             currentChar++;
118                             if ( repeat )
119                                 currentChar %= length;
120                         }
121                     }
122                     next -= distance;
123                     first = false;
124                     lastX = thisX;
125                     lastY = thisY;
126                     break;
127             }
128             it.next();
129         }
130
131         return result;
132     }
133
134     public float measurePathLength( Shape shape ) {
135         PathIterator it = new FlatteningPathIterator( shape.getPathIterator( null ), FLATNESS );
136         float points[] = new float[6];
137         float moveX = 0, moveY = 0;
138         float lastX = 0, lastY = 0;
139         float thisX = 0, thisY = 0;
140         int type = 0;
141         float total = 0;
142
143         while ( !it.isDone() ) {
144             type = it.currentSegment( points );
145             switch( type ){
146                 case PathIterator.SEG_MOVETO:
147                     moveX = lastX = points[0];
148                     moveY = lastY = points[1];
149                     break;
150
151                 case PathIterator.SEG_CLOSE:
152                     points[0] = moveX;
153                     points[1] = moveY;
154                     // Fall into....
155
156                 case PathIterator.SEG_LINETO:
157                     thisX = points[0];
158                     thisY = points[1];
159                     float dx = thisX-lastX;
160                     float dy = thisY-lastY;
161                     total += (float)Math.sqrt( dx*dx + dy*dy );
162                     lastX = thisX;
163                     lastY = thisY;
164                     break;
165             }
166             it.next();
167         }
168
169         return total;
170     }
171
172 }