View Javadoc

1   /*
2    * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
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  /* $Id: StringUtil.java 457 2007-08-27 11:05:10Z maxberger $ */
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.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Vector;
34  
35  import net.sourceforge.jeuclid.MathBase;
36  import net.sourceforge.jeuclid.ParameterKey;
37  import net.sourceforge.jeuclid.elements.support.attributes.FontFamily;
38  import net.sourceforge.jeuclid.elements.support.attributes.MathVariant;
39  
40  /**
41   * Utilities for String handling.
42   * 
43   * @author Max Berger
44   * @version $Revision: 457 $
45   */
46  public final class StringUtil {
47  
48      private static final int LOWERCASE_START = 0x61;
49  
50      private static final int UPPERCASE_START = 0x41;
51  
52      private static final int NUM_CHARS = 26;
53  
54      private static final Map<Integer, CodePointAndVariant> HIGHPLANE_MAPPING = new HashMap<Integer, CodePointAndVariant>();
55  
56      private static final Map<Integer, Integer> FRAKTUR_MAPPING = new HashMap<Integer, Integer>();
57  
58      private static final Map<Integer, Integer> SCRIPT_MAPPING = new HashMap<Integer, Integer>();
59  
60      private static final Map<Integer, Integer> DOUBLE_MAPPING = new HashMap<Integer, Integer>();
61  
62      private StringUtil() {
63          // do nothing
64      }
65  
66      private static class CodePointAndVariant {
67          private final int codePoint;
68  
69          private final MathVariant variant;
70  
71          protected CodePointAndVariant(final int icodePoint,
72                  final MathVariant ivariant) {
73              this.codePoint = icodePoint;
74              this.variant = ivariant;
75          }
76  
77          /**
78           * @return the codePoint
79           */
80          public final int getCodePoint() {
81              return this.codePoint;
82          }
83  
84          /**
85           * @return the variant
86           */
87          public final MathVariant getVariant() {
88              return this.variant;
89          }
90  
91      }
92  
93      /**
94       * Converts a given String to an attributed string with the proper
95       * variants set.
96       * 
97       * @param inputString
98       *            the string to convert.
99       * @param baseVariant
100      *            variant to base on for regular characters
101      * @param fontSize
102      *            size of Font to use.
103      * @param base
104      *            MathBase to use.
105      * @return an attributed string that has Textattribute.FONT set for all
106      *         characters.
107      */
108     public static AttributedString convertStringtoAttributedString(
109             final String inputString, final MathVariant baseVariant,
110             final float fontSize, final MathBase base) {
111         final StringBuilder builder = new StringBuilder();
112         final List<MathVariant> variants = new Vector<MathVariant>();
113         final String plainString = CharConverter.convertLate(inputString);
114 
115         for (int i = 0; i < plainString.length(); i++) {
116             if (!Character.isLowSurrogate(plainString.charAt(i))) {
117 
118                 CodePointAndVariant cpav = new CodePointAndVariant(
119                         plainString.codePointAt(i), baseVariant);
120 
121                 cpav = StringUtil.mapHighCodepointToLowerCodepoints(cpav);
122                 cpav = StringUtil.mapVariantsToStandardCodepoints(cpav);
123 
124                 final int codePoint = cpav.getCodePoint();
125                 final MathVariant variant = cpav.getVariant();
126 
127                 builder.appendCodePoint(codePoint);
128                 variants.add(variant);
129                 if (Character.isSupplementaryCodePoint(codePoint)) {
130                     variants.add(variant);
131                 }
132             }
133         }
134 
135         final AttributedString aString = new AttributedString(builder
136                 .toString());
137 
138         for (int i = 0; i < builder.length(); i++) {
139             final MathVariant variant = variants.get(i);
140             aString.addAttribute(TextAttribute.FONT, variant.createFont(
141                     fontSize, builder.charAt(i), base), i, i + 1);
142         }
143         return aString;
144     }
145 
146     /**
147      * Safely creates a Text Layout from an attributed string. Unlike the
148      * TextLayout constructor, the String here may actually be empty.
149      * 
150      * @param g
151      *            Graphics context.
152      * @param aString
153      *            an Attributed String
154      * @param mathBase
155      *            MathBase of context.
156      * @return a TextLayout
157      */
158     public static TextLayout createTextLayoutFromAttributedString(
159             final Graphics2D g, final AttributedString aString,
160             final MathBase mathBase) {
161         final AttributedCharacterIterator charIter = aString.getIterator();
162         final boolean empty = charIter.first() == CharacterIterator.DONE;
163         final FontRenderContext suggestedFontRenderContext = g
164                 .getFontRenderContext();
165         boolean antialiasing = Boolean.parseBoolean(mathBase.getParams().get(
166                 ParameterKey.AntiAlias));
167        final FontRenderContext realFontRenderContext = new FontRenderContext(
168                 suggestedFontRenderContext.getTransform(), antialiasing,
169                 false);
170 
171         final TextLayout theLayout;
172         if (!empty) {
173             theLayout = new TextLayout(aString.getIterator(),
174                     realFontRenderContext);
175         } else {
176             theLayout = new TextLayout(" ", new Font("", 0, 0),
177                     realFontRenderContext);
178         }
179         return theLayout;
180     }
181 
182     /**
183      * Maps the characters that have a default representation (such as the
184      * double-struck N) in the Unicode lower plane to that character.
185      * <p>
186      * This is necessary as fonts for special math representations, such as
187      * double-struck may not be available. However, the double-struck n is
188      * very likely do be available in one of the default fonts.
189      * 
190      * @param cpav
191      * @return
192      */
193     private static CodePointAndVariant mapVariantsToStandardCodepoints(
194             final CodePointAndVariant cpav) {
195         int codePoint = cpav.getCodePoint();
196         MathVariant variant = cpav.getVariant();
197         final int awtStyle = variant.getAwtStyle();
198         final FontFamily fontFamily = variant.getFontFamily();
199         if (FontFamily.FRAKTUR.equals(fontFamily)) {
200             final Integer mapping = StringUtil.FRAKTUR_MAPPING.get(codePoint);
201             if (mapping != null) {
202                 codePoint = mapping;
203                 variant = new MathVariant(awtStyle, FontFamily.SANSSERIF);
204             }
205         } else if (FontFamily.SCRIPT.equals(fontFamily)) {
206             final Integer mapping = StringUtil.SCRIPT_MAPPING.get(codePoint);
207             if (mapping != null) {
208                 codePoint = mapping;
209                 variant = new MathVariant(awtStyle, FontFamily.SANSSERIF);
210             }
211         } else if (FontFamily.DOUBLE_STRUCK.equals(fontFamily)) {
212             final Integer mapping = StringUtil.DOUBLE_MAPPING.get(codePoint);
213             if (mapping != null) {
214                 codePoint = mapping;
215                 variant = new MathVariant(awtStyle, FontFamily.SANSSERIF);
216             }
217 
218         }
219         return new CodePointAndVariant(codePoint, variant);
220 
221     }
222 
223     /**
224      * Maps codepoints from the hi plane (> 0x10000) to a representation of
225      * the same character in the lower plane, with the corresponding
226      * mathvariant attribute.
227      * <p>
228      * This is necessary because font support for the high plane is almost
229      * non-existing.
230      * 
231      * @param cpav
232      * @return
233      */
234     private static CodePointAndVariant mapHighCodepointToLowerCodepoints(
235             final CodePointAndVariant cpav) {
236         final int codePoint = cpav.getCodePoint();
237         final CodePointAndVariant mappedTo = StringUtil.HIGHPLANE_MAPPING
238                 .get(codePoint);
239         if (mappedTo != null) {
240             return mappedTo;
241         } else {
242             return cpav;
243         }
244     }
245 
246     /**
247      * Retrieves the real width from a given text layout.
248      * 
249      * @param layout
250      *            the textlayout
251      * @return width
252      */
253     public static float getWidthForTextLayout(final TextLayout layout) {
254         final Rectangle2D r2d = layout.getBounds();
255         float realWidth = (float) r2d.getWidth();
256         final float xo = (float) r2d.getX();
257         if (xo > 0) {
258             realWidth += xo;
259         }
260         // Unfotunately this is necessesary, although it does not look like it
261         // makes a lot of sense.
262         final float invisibleAdvance = layout.getAdvance()
263                 - layout.getVisibleAdvance();
264         return realWidth + invisibleAdvance;
265     }
266 
267     private static void addHighMapping(final int codePointStart,
268             final MathVariant mapToVariant) {
269 
270         for (int i = 0; i < StringUtil.NUM_CHARS; i++) {
271             StringUtil.HIGHPLANE_MAPPING.put(codePointStart + i,
272                     new CodePointAndVariant(StringUtil.UPPERCASE_START + i,
273                             mapToVariant));
274         }
275         for (int i = 0; i < StringUtil.NUM_CHARS; i++) {
276             StringUtil.HIGHPLANE_MAPPING.put(codePointStart
277                     + StringUtil.NUM_CHARS + i, new CodePointAndVariant(
278                     StringUtil.LOWERCASE_START + i, mapToVariant));
279         }
280     }
281 
282     private static void initializeVariantToStandardMapping() {
283         // CHECKSTYLE:OFF
284 
285         // From: http://www.w3.org/TR/MathML2/fraktur.html
286         StringUtil.FRAKTUR_MAPPING.put((int) 'C', 0x0212D);
287         StringUtil.FRAKTUR_MAPPING.put((int) 'H', 0x0210C);
288         StringUtil.FRAKTUR_MAPPING.put((int) 'I', 0x02111);
289         StringUtil.FRAKTUR_MAPPING.put((int) 'R', 0x0211C);
290         StringUtil.FRAKTUR_MAPPING.put((int) 'Z', 0x02128);
291 
292         // From: http://www.w3.org/TR/MathML2/script.html
293         StringUtil.SCRIPT_MAPPING.put((int) 'B', 0x212C);
294         StringUtil.SCRIPT_MAPPING.put((int) 'E', 0x2130);
295         StringUtil.SCRIPT_MAPPING.put((int) 'e', 0x212F);
296         StringUtil.SCRIPT_MAPPING.put((int) 'F', 0x2131);
297         StringUtil.SCRIPT_MAPPING.put((int) 'g', 0x210A);
298         StringUtil.SCRIPT_MAPPING.put((int) 'H', 0x210B);
299         StringUtil.SCRIPT_MAPPING.put((int) 'I', 0x2110);
300         StringUtil.SCRIPT_MAPPING.put((int) 'L', 0x2112);
301         StringUtil.SCRIPT_MAPPING.put((int) 'M', 0x2133);
302         StringUtil.SCRIPT_MAPPING.put((int) 'o', 0x2134);
303         StringUtil.SCRIPT_MAPPING.put((int) 'R', 0x211B);
304 
305         // From: http://www.w3.org/TR/MathML2/double-struck.html
306         StringUtil.DOUBLE_MAPPING.put((int) 'C', 0x2102);
307         StringUtil.DOUBLE_MAPPING.put((int) 'H', 0x210D);
308         StringUtil.DOUBLE_MAPPING.put((int) 'N', 0x2115);
309         StringUtil.DOUBLE_MAPPING.put((int) 'P', 0x2119);
310         StringUtil.DOUBLE_MAPPING.put((int) 'Q', 0x211A);
311         StringUtil.DOUBLE_MAPPING.put((int) 'R', 0x211D);
312         StringUtil.DOUBLE_MAPPING.put((int) 'Z', 0x2124);
313         // CHECKSTYLE:ON
314     }
315 
316     private static void initializeHighPlaneMappings() {
317         // CHECKSTYLE:OFF
318         StringUtil.addHighMapping(0x1D400, MathVariant.BOLD);
319         StringUtil.addHighMapping(0x1D434, MathVariant.ITALIC);
320         StringUtil.addHighMapping(0x1D468, MathVariant.BOLD_ITALIC);
321         StringUtil.addHighMapping(0x1D49C, MathVariant.SCRIPT);
322         StringUtil.addHighMapping(0x1D4D0, MathVariant.BOLD_SCRIPT);
323         StringUtil.addHighMapping(0x1D504, MathVariant.FRAKTUR);
324         StringUtil.addHighMapping(0x1D538, MathVariant.DOUBLE_STRUCK);
325         StringUtil.addHighMapping(0x1D56C, MathVariant.BOLD_FRAKTUR);
326         StringUtil.addHighMapping(0x1D5A0, MathVariant.SANS_SERIF);
327         StringUtil.addHighMapping(0x1D5D4, MathVariant.BOLD_SANS_SERIF);
328         StringUtil.addHighMapping(0x1D608, MathVariant.SANS_SERIF_ITALIC);
329         StringUtil
330                 .addHighMapping(0x1D63C, MathVariant.SANS_SERIF_BOLD_ITALIC);
331         StringUtil.addHighMapping(0x1D670, MathVariant.MONOSPACE);
332 
333         // TODO: Greek Mappings
334         // TODO: Number mappings
335         // CHECKSTYLE:ON
336     }
337 
338     static {
339         StringUtil.initializeVariantToStandardMapping();
340         StringUtil.initializeHighPlaneMappings();
341     }
342 
343 }