source: trunk/src/org/expeditee/gio/javafx/JavaFXTextLayoutManager.java@ 1097

Last change on this file since 1097 was 1097, checked in by davidb, 6 years ago

Newly structured files from Corey's work on logic/graphics separation

File size: 8.5 KB
Line 
1package org.expeditee.gio.javafx;
2
3import java.util.HashMap;
4import java.util.LinkedList;
5import java.util.List;
6
7import org.expeditee.core.Font;
8import org.expeditee.core.Line;
9import org.expeditee.core.Point;
10import org.expeditee.core.TextHitInfo;
11import org.expeditee.core.TextLayout;
12import org.expeditee.core.bounds.AxisAlignedBoxBounds;
13import org.expeditee.gio.TextLayoutManager;
14
15import javafx.geometry.Bounds;
16import javafx.scene.text.Text;
17import javafx.scene.text.TextAlignment;
18
19public class JavaFXTextLayoutManager extends TextLayoutManager {
20
21 /** Singleton instance. */
22 private static JavaFXTextLayoutManager _instance;
23
24 /** Singleton instantiator. */
25 public static JavaFXTextLayoutManager getInstance()
26 {
27 if (_instance == null) _instance = new JavaFXTextLayoutManager();
28
29 return _instance;
30 }
31
32 /** Mapping from Expeditee text-layout objects to JavaFX equivalents. */
33 private HashMap<Long, javafx.scene.text.Text> _layoutMap;
34
35 private JavaFXTextLayoutManager()
36 {
37 _layoutMap = new HashMap<Long, javafx.scene.text.Text>();
38 }
39
40 private synchronized void register(TextLayout layout, javafx.scene.text.Text jfxLayout)
41 {
42 if (layout == null || jfxLayout == null) return;
43
44 _layoutMap.put(layout.getHandle(), jfxLayout);
45 }
46
47 public synchronized javafx.scene.text.Text getInternalLayout(TextLayout layout)
48 {
49 if (!isTextLayoutValid(layout)) return null;
50
51 return _layoutMap.get(layout.getHandle());
52 }
53
54 public synchronized boolean isTextLayoutValid(TextLayout layout)
55 {
56 if (layout == null) return false;
57
58 return _layoutMap.containsKey(layout.getHandle());
59 }
60
61 @Override
62 public float getAdvance(TextLayout layout)
63 {
64 if (!isTextLayoutValid(layout)) return Float.NaN;
65
66 Text text = getInternalLayout(layout);
67
68 Bounds textBounds = text.getBoundsInLocal();
69
70 return (float) textBounds.getWidth();
71 }
72
73 @Override
74 public int getCharacterCount(TextLayout layout)
75 {
76 if (!isTextLayoutValid(layout)) return 0;
77
78 Text text = getInternalLayout(layout);
79
80 return text.getText().length();
81 }
82
83 @Override
84 public TextHitInfo getNextLeftHit(TextLayout layout, int offset)
85 {
86 // TODO: Complete. cts16
87 return new TextHitInfo(offset, false);
88 }
89
90 @Override
91 public TextHitInfo getNextLeftHit(TextLayout layout, TextHitInfo hit)
92 {
93 // TODO: Complete. cts16
94 return hit;
95 }
96
97 @Override
98 public TextHitInfo getNextRightHit(TextLayout layout, int offset)
99 {
100 // TODO: Complete. cts16
101 return new TextHitInfo(offset, false);
102 }
103
104 @Override
105 public TextHitInfo getNextRightHit(TextLayout layout, TextHitInfo hit)
106 {
107 // TODO: Complete. cts16
108 return hit;
109 }
110
111 @Override
112 public float[] getCaretInfo(TextLayout layout, TextHitInfo hit)
113 {
114 // TODO: Complete. cts16
115 float[] ret = new float[2];
116 ret[0] = 0;
117 ret[1] = 1;
118 return ret;
119 }
120
121 @Override
122 public TextHitInfo hitTestChar(TextLayout layout, float x, float y)
123 {
124 // TODO: Complete. cts16
125 return new TextHitInfo(0, false);
126 }
127
128 @Override
129 public AxisAlignedBoxBounds getPixelBounds(TextLayout layout, float x, float y)
130 {
131 if (!isTextLayoutValid(layout)) return null;
132
133 Text text = getInternalLayout(layout);
134
135 Bounds textBounds = text.getBoundsInLocal();
136
137 return new AxisAlignedBoxBounds((int) x, (int) y, (int) textBounds.getWidth(), (int) textBounds.getHeight());
138 }
139
140 @Override
141 public AxisAlignedBoxBounds getLogicalHighlightShape(TextLayout layout, int firstEndpoint, int secondEndpoint)
142 {
143 // TODO: Complete. cts16
144 return getPixelBounds(layout, 0, -layout.getAscent());
145 }
146
147 @Override
148 public float getAscent(TextLayout layout)
149 {
150 if (!isTextLayoutValid(layout)) return Float.NaN;
151
152 Text text = getInternalLayout(layout);
153
154 return (int) text.getBaselineOffset();
155 }
156
157 @Override
158 public float getDescent(TextLayout layout)
159 {
160 if (!isTextLayoutValid(layout)) return Float.NaN;
161
162 Text text = getInternalLayout(layout);
163
164 return (int) (text.getBoundsInLocal().getHeight() - text.getBaselineOffset());
165 }
166
167 @Override
168 public float getLeading(TextLayout layout)
169 {
170 // TODO Auto-generated method stub
171 return 0;
172 }
173
174 // TODO: Remove magic constant of -1. cts16
175 @Override
176 public int getStringWidth(Font font, String string)
177 {
178 if (font == null || string == null) return -1;
179
180 // Make sure we have a JavaFX font to use
181 javafx.scene.text.Font jfxFont;
182 JavaFXFontManager fontManager = JavaFXMiscManager.getIfUsingJavaFXFontManager();
183 if (fontManager == null) return -1;
184 jfxFont = fontManager.getInternalFont(font);
185 if (jfxFont == null) {
186 JavaFXGraphicsManager g = JavaFXMiscManager.getIfUsingJavaFXGraphicsManager();
187 if (g == null) return -1;
188 jfxFont = fontManager.getInternalFont(fontManager.getDefaultFont());
189 }
190 if (jfxFont == null) return -1;
191
192 // Create a text object to layout the text
193 javafx.scene.text.Text text = new javafx.scene.text.Text(string);
194 text.setFont(jfxFont);
195
196 // Return the width of the text
197 return (int) text.getBoundsInLocal().getWidth();
198 }
199
200 @Override
201 public void releaseLayout(TextLayout layout)
202 {
203 if (!isTextLayoutValid(layout)) return;
204
205 _layoutMap.remove(layout.getHandle());
206 }
207
208 @Override
209 public List<TextLayout> layoutString(String string, Font font, Point start, Line[] lineBreakers, int widthLimit,
210 int lineSpacing, boolean dontBreakWords, boolean fullJustify)
211 {
212 if (string == null || font == null || start == null) return null;
213
214 // Make sure we have a JavaFX font to use
215 javafx.scene.text.Font jfxFont;
216 JavaFXFontManager fontManager = JavaFXMiscManager.getIfUsingJavaFXFontManager();
217 if (fontManager == null) return null;
218 jfxFont = fontManager.getInternalFont(font);
219 if (jfxFont == null) return null;
220
221 // Temporary list to accumulate the Texts in to
222 List<javafx.scene.text.Text> layouts = new LinkedList<javafx.scene.text.Text>();
223 List<Integer> offsets = new LinkedList<Integer>();
224 offsets.add(0);
225 List<Point> positions = new LinkedList<Point>();
226 positions.add(new Point(start));
227
228 int startIndex = 0;
229 while (startIndex < string.length()) {
230
231 int width = widthLimit;
232 if (lineBreakers != null) width = Math.min(width, (int) getLineWidth(start, lineBreakers));
233
234 int endIndex = string.length();
235 for (int i = startIndex + 1; i < string.length(); i++) {
236 if (string.charAt(i) == '\n') {
237 endIndex = i;
238 break;
239 }
240 }
241
242 javafx.scene.text.Text text = findTextNoWiderThan(string.substring(startIndex, endIndex), jfxFont, width, dontBreakWords);
243
244 // If it's impossible to layout any more text without breaking a word, just do it
245 if (text == null || text.getText().length() == 0 && width == widthLimit) {
246 text = findTextNoWiderThan(string.substring(startIndex, endIndex), jfxFont, width, false);
247 // If still impossible, give up
248 if (text == null || text.getText().length() == 0) break;
249 }
250
251 endIndex = startIndex + text.getText().length();
252 text.setX(start.x);
253 text.setY(start.y);
254
255 if (fullJustify && endIndex < string.length()) {
256 text = getJustifiedLayout(text, width);
257 }
258
259 layouts.add(text);
260 offsets.add(endIndex);
261 startIndex = endIndex;
262 if (startIndex < string.length() && string.charAt(startIndex) == '\n') startIndex += 1;
263 int lineDrop = (int) (text.getBoundsInLocal().getHeight());
264 if (lineSpacing >= 0) {
265 lineDrop += lineSpacing;
266 }
267 start.y += lineDrop;
268 positions.add(new Point(start));
269 }
270
271 // Convert the accumulated list into an internal-type array
272 List<TextLayout> ret = new LinkedList<TextLayout>();
273 for (int i = 0; i < layouts.size(); i++) {
274 javafx.scene.text.Text text = layouts.get(i);
275 int startCharIndex = offsets.get(i);
276 int endCharIndex = offsets.get(i + 1) - 1;
277 String line = string.substring(startCharIndex, endCharIndex + 1);
278 TextLayout layout = TextLayout.get(line, font, startCharIndex, endCharIndex);
279 register(layout, text);
280 ret.add(layout);
281 }
282
283 return ret;
284 }
285
286 private javafx.scene.text.Text findTextNoWiderThan(String string, javafx.scene.text.Font font, int maxWidth, boolean dontBreakWords)
287 {
288 if (string == null || font == null || maxWidth <= 0) return null;
289
290 javafx.scene.text.Text text = new javafx.scene.text.Text(string);
291 text.setFont(font);
292
293 // TODO: Change to binary search. cts16
294 while (text.getBoundsInLocal().getWidth() > maxWidth && string.length() > 0) {
295 string = string.substring(0, string.length() - 1);
296 if (dontBreakWords && !string.endsWith(" ")) continue;
297 text.setText(string);
298 }
299
300 return text;
301 }
302
303 private javafx.scene.text.Text getJustifiedLayout(javafx.scene.text.Text text, int width)
304 {
305 if (text == null) return null;
306
307 text.setTextAlignment(TextAlignment.JUSTIFY);
308 text.setWrappingWidth(width);
309
310 return text;
311 }
312
313}
Note: See TracBrowser for help on using the repository browser.