1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.jeuclid.elements.support.text;
20
21 import java.awt.Font;
22 import java.awt.Graphics2D;
23 import java.awt.font.FontRenderContext;
24 import java.awt.font.TextAttribute;
25 import java.awt.font.TextLayout;
26 import java.awt.geom.Rectangle2D;
27 import java.text.AttributedCharacterIterator;
28 import java.text.AttributedString;
29 import java.text.CharacterIterator;
30 import java.util.ArrayList;
31 import java.util.Iterator;
32 import java.util.List;
33
34 import javax.annotation.Nullable;
35
36 import net.sourceforge.jeuclid.LayoutContext;
37 import net.sourceforge.jeuclid.context.Parameter;
38 import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
39 import net.sourceforge.jeuclid.elements.JEuclidElement;
40 import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
41 import net.sourceforge.jeuclid.elements.support.attributes.MathVariant;
42
43 import org.w3c.dom.Element;
44 import org.w3c.dom.Node;
45 import org.w3c.dom.NodeList;
46
47
48
49
50
51
52
53
54
55 public final class StringUtil {
56
57
58
59
60
61 public static final boolean OSX = System.getProperty("mrj.version") != null;
62
63 static final CharacterMapping CMAP = CharacterMapping.getInstance();
64
65 private StringUtil() {
66
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public static AttributedString convertStringtoAttributedString(
85 final String inputString, final MathVariant baseVariant,
86 final float fontSize, final LayoutContext context) {
87 if (inputString == null) {
88 return new AttributedString("");
89 }
90 final StringBuilder builder = new StringBuilder();
91 final List<Font> fonts = new ArrayList<Font>();
92 final String plainString = CharConverter.convertLate(inputString);
93
94 for (int i = 0; i < plainString.length(); i++) {
95 if (!Character.isLowSurrogate(plainString.charAt(i))) {
96
97 final CodePointAndVariant cpav1 = new CodePointAndVariant(
98 plainString.codePointAt(i), baseVariant);
99 final Object[] codeAndFont = StringUtil.mapCpavToCpaf(cpav1,
100 fontSize, context);
101 final int codePoint = (Integer) codeAndFont[0];
102 final Font font = (Font) codeAndFont[1];
103
104 builder.appendCodePoint(codePoint);
105 fonts.add(font);
106 if (Character.isSupplementaryCodePoint(codePoint)) {
107 fonts.add(font);
108 }
109 }
110 }
111
112 final AttributedString aString = new AttributedString(builder
113 .toString());
114
115 final int len = builder.length();
116
117 for (int i = 0; i < len; i++) {
118 final char currentChar = builder.charAt(i);
119 if (!Character.isLowSurrogate(currentChar)) {
120 final Font font = fonts.get(i);
121 final int count;
122 if (Character.isHighSurrogate(currentChar)) {
123 count = 2;
124 } else {
125 count = 1;
126 }
127 aString.addAttribute(TextAttribute.FONT, font, i, i + count);
128 }
129 }
130 return aString;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 public static AttributedCharacterIterator textContentAsAttributedCharacterIterator(
148 final LayoutContext contextNow,
149 final JEuclidElement contextElement, final Node node,
150 final float corrector) {
151 AttributedCharacterIterator retVal;
152 if (node instanceof Element) {
153
154 final MultiAttributedCharacterIterator maci = new MultiAttributedCharacterIterator();
155 final NodeList children = node.getChildNodes();
156 AttributedCharacterIterator aci = null;
157 final int childCount = children.getLength();
158 for (int i = 0; i < childCount; i++) {
159 final LayoutContext subContext;
160 final Node child = children.item(i);
161 final JEuclidElement subContextElement;
162 if (child instanceof AbstractJEuclidElement) {
163 subContext = ((AbstractJEuclidElement) child)
164 .applyLocalAttributesToContext(contextNow);
165 subContextElement = (JEuclidElement) child;
166 } else {
167 subContext = contextNow;
168 subContextElement = contextElement;
169 }
170 aci = StringUtil.textContentAsAttributedCharacterIterator(
171 subContext, subContextElement, child, corrector);
172 maci.appendAttributedCharacterIterator(aci);
173 }
174
175 if (childCount != 1) {
176 aci = maci;
177 }
178
179 if (node instanceof TextContentModifier) {
180 final TextContentModifier t = (TextContentModifier) node;
181 retVal = t.modifyTextContent(aci, contextNow);
182 } else {
183 retVal = aci;
184 }
185 } else {
186 final String theText = TextContent.getText(node);
187 final float fontSizeInPoint = GraphicsSupport
188 .getFontsizeInPoint(contextNow)
189 * corrector;
190
191 retVal = StringUtil.convertStringtoAttributedString(theText,
192 contextElement.getMathvariantAsVariant(), fontSizeInPoint,
193 contextNow).getIterator();
194 }
195 return retVal;
196 }
197
198 private static Object[] mapCpavToCpaf(final CodePointAndVariant cpav1,
199 final float fontSize, final LayoutContext context) {
200 final List<CodePointAndVariant> alternatives = StringUtil.CMAP
201 .getAllAlternatives(cpav1);
202
203 Font font = null;
204 int codePoint = 0;
205 final Iterator<CodePointAndVariant> it = alternatives.iterator();
206 boolean cont = true;
207 while (cont) {
208 final CodePointAndVariant cpav = it.next();
209 if (it.hasNext()) {
210 codePoint = cpav.getCodePoint();
211 font = cpav.getVariant().createFont(fontSize, codePoint,
212 context, false);
213 if (font != null) {
214 cont = false;
215 }
216 } else {
217 codePoint = cpav.getCodePoint();
218 font = cpav.getVariant().createFont(fontSize, codePoint,
219 context, true);
220 cont = false;
221 }
222 }
223 return new Object[] { codePoint, font };
224 }
225
226
227
228
229
230
231
232
233
234
235
236
237
238 public static TextLayout createTextLayoutFromAttributedString(
239 final Graphics2D g, final AttributedString aString,
240 final LayoutContext context) {
241 final AttributedCharacterIterator charIter = aString.getIterator();
242 final boolean empty = charIter.first() == CharacterIterator.DONE;
243 final FontRenderContext suggestedFontRenderContext = g
244 .getFontRenderContext();
245 boolean antialiasing = (Boolean) context
246 .getParameter(Parameter.ANTIALIAS);
247 if (!empty) {
248 final Font font = (Font) aString.getIterator().getAttribute(
249 TextAttribute.FONT);
250 if (font != null) {
251 final float fontsize = font.getSize2D();
252 final float minantialias = (Float) context
253 .getParameter(Parameter.ANTIALIAS_MINSIZE);
254 antialiasing &= fontsize >= minantialias;
255 }
256 }
257
258 final FontRenderContext realFontRenderContext = new FontRenderContext(
259 suggestedFontRenderContext.getTransform(), antialiasing, false);
260
261 final TextLayout theLayout;
262 if (empty) {
263 theLayout = new TextLayout(" ", new Font("", 0, 0),
264 realFontRenderContext);
265 } else {
266 synchronized (TextLayout.class) {
267
268
269 theLayout = new TextLayout(aString.getIterator(),
270 realFontRenderContext);
271 }
272 }
273 return theLayout;
274 }
275
276
277
278
279
280
281
282
283 public static float getWidthForTextLayout(final TextLayout layout) {
284 final Rectangle2D r2d = layout.getBounds();
285 float realWidth = (float) r2d.getWidth();
286 final float xo = (float) r2d.getX();
287 if (xo > 0) {
288 realWidth += xo;
289 }
290
291
292 final float invisibleAdvance = layout.getAdvance()
293 - layout.getVisibleAdvance();
294 return realWidth + invisibleAdvance;
295 }
296
297
298
299
300 public static class TextLayoutInfo {
301 private final float ascent;
302
303 private final float descent;
304
305 private final float offset;
306
307 private final float width;
308
309
310
311
312
313
314
315
316
317
318
319
320
321 protected TextLayoutInfo(final float newAscent, final float newDescent,
322 final float newOffset, final float newWidth) {
323 this.ascent = newAscent;
324 this.descent = newDescent;
325 this.offset = newOffset;
326 this.width = newWidth;
327 }
328
329
330
331
332
333
334 public float getAscent() {
335 return this.ascent;
336 }
337
338
339
340
341
342
343 public float getDescent() {
344 return this.descent;
345 }
346
347
348
349
350
351
352 public float getOffset() {
353 return this.offset;
354 }
355
356
357
358
359
360
361 public float getWidth() {
362 return this.width;
363 }
364
365 };
366
367
368
369
370
371
372
373
374
375
376
377 public static TextLayoutInfo getTextLayoutInfo(final TextLayout textLayout,
378 final boolean trim) {
379 final Rectangle2D textBounds = textLayout.getBounds();
380 final float ascent = (float) (-textBounds.getY());
381 final float descent = (float) (textBounds.getY() + textBounds
382 .getHeight());
383 final float xo = (float) textBounds.getX();
384 final float xOffset;
385 if (xo < 0) {
386 xOffset = -xo;
387 } else {
388 if (trim) {
389 xOffset = -xo;
390 } else {
391 xOffset = 0.0f;
392 }
393 }
394 final float width = StringUtil.getWidthForTextLayout(textLayout);
395 return new TextLayoutInfo(ascent, descent, xOffset, width);
396 }
397
398
399
400
401
402
403
404
405
406 public static int countDisplayableCharacters(@Nullable final String s) {
407 if (s == null) {
408 return 0;
409 }
410 int length = 0;
411 final int strLen = s.length();
412 for (int i = 0; i < strLen; i++) {
413 final char charHere = s.charAt(i);
414 if (!Character.isHighSurrogate(charHere)) {
415 final int codepoint = s.codePointAt(i);
416 if (!StringUtil.CMAP.isMark(codepoint)) {
417 length++;
418 }
419 }
420 }
421 return length;
422 }
423
424 }