Coverage Report - net.sourceforge.jeuclid.elements.support.text.StringUtil
 
Classes in this File Line Coverage Branch Coverage Complexity
StringUtil
92%
155/167
80%
77/96
3,077
StringUtil$TextLayoutInfo
100%
16/16
N/A
3,077
 
 1  
 /*
 2  
  * Copyright 2002 - 2009 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,v 74b8e95997bf 2010/08/11 17:45:46 max $ */
 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  
  * Utilities for String handling.
 49  
  *
 50  
  * @version $Revision: 74b8e95997bf $
 51  
  */
 52  
 // CHECKSTYLE:OFF
 53  
 // Data Abstraction Coupling is too high. Hover, String handling is not
 54  
 // simple.
 55  
 public final class StringUtil {
 56  
     // CHECKSTYLE:ON
 57  
 
 58  
     /**
 59  198
      * Set to true if we're running under Mac OS X.
 60  
      */
 61  209
     public static final boolean OSX = System.getProperty("mrj.version") != null; //$NON-NLS-1$
 62  
 
 63  11
     static final CharacterMapping CMAP = CharacterMapping.getInstance();
 64  
 
 65  0
     private StringUtil() {
 66  
         // do nothing
 67  0
     }
 68  
 
 69  
     /**
 70  
      * Converts a given String to an attributed string with the proper variants
 71  
      * set.
 72  
      *
 73  
      * @param inputString
 74  
      *            the string to convert.
 75  
      * @param baseVariant
 76  
      *            variant to base on for regular characters
 77  
      * @param fontSize
 78  
      *            size of Font to use.
 79  
      * @param context
 80  
      *            Layout Context to use.
 81  
      * @return an attributed string that has Textattribute.FONT set for all
 82  
      *         characters.
 83  
      */
 84  
     public static AttributedString convertStringtoAttributedString(
 85  47324
             final String inputString, final MathVariant baseVariant,
 86  0
             final float fontSize, final LayoutContext context) {
 87  2635
         if (inputString == null) {
 88  47324
             return new AttributedString("");
 89  47324
         }
 90  49959
         final StringBuilder builder = new StringBuilder();
 91  2635
         final List<Font> fonts = new ArrayList<Font>();
 92  116095
         final String plainString = CharConverter.convertLate(inputString);
 93  66136
 
 94  6321
         for (int i = 0; i < plainString.length(); i++) {
 95  69822
             if (!Character.isLowSurrogate(plainString.charAt(i))) {
 96  
 
 97  69822
                 final CodePointAndVariant cpav1 = new CodePointAndVariant(
 98  
                         plainString.codePointAt(i), baseVariant);
 99  69822
                 final Object[] codeAndFont = StringUtil.mapCpavToCpaf(cpav1,
 100  66136
                         fontSize, context);
 101  3686
                 final int codePoint = (Integer) codeAndFont[0];
 102  69822
                 final Font font = (Font) codeAndFont[1];
 103  66136
 
 104  69822
                 builder.appendCodePoint(codePoint);
 105  3686
                 fonts.add(font);
 106  3686
                 if (Character.isSupplementaryCodePoint(codePoint)) {
 107  0
                     fonts.add(font);
 108  
                 }
 109  
             }
 110  47324
         }
 111  
 
 112  2635
         final AttributedString aString = new AttributedString(builder
 113  47324
                 .toString());
 114  
 
 115  116095
         final int len = builder.length();
 116  66136
 
 117  72457
         for (int i = 0; i < len; i++) {
 118  69822
             final char currentChar = builder.charAt(i);
 119  3686
             if (!Character.isLowSurrogate(currentChar)) {
 120  69822
                 final Font font = fonts.get(i);
 121  0
                 final int count;
 122  3686
                 if (Character.isHighSurrogate(currentChar)) {
 123  66136
                     count = 2;
 124  
                 } else {
 125  69822
                     count = 1;
 126  
                 }
 127  3686
                 aString.addAttribute(TextAttribute.FONT, font, i, i + count);
 128  47324
             }
 129  
         }
 130  2635
         return aString;
 131  
     }
 132  
 
 133  
     /**
 134  
      * Provide the text content of the current element as
 135  
      * AttributedCharacterIterator.
 136  
      *
 137  
      * @param contextNow
 138  
      *            LayoutContext of the parent element.
 139  
      * @param contextElement
 140  
      *            Parent Element.
 141  
      * @param node
 142  
      *            Current node.
 143  
      * @param corrector
 144  
      *            Font-size corrector.
 145  
      * @return An {@link AttributedCharacterIterator} over the text contents.
 146  
      */
 147  
     public static AttributedCharacterIterator textContentAsAttributedCharacterIterator(
 148  
             final LayoutContext contextNow,
 149  
             final JEuclidElement contextElement, final Node node,
 150  48316
             final float corrector) {
 151  
         AttributedCharacterIterator retVal;
 152  26854
         if (node instanceof Element) {
 153  24158
 
 154  25506
             final MultiAttributedCharacterIterator maci = new MultiAttributedCharacterIterator();
 155  25506
             final NodeList children = node.getChildNodes();
 156  49664
             AttributedCharacterIterator aci = null;
 157  1348
             final int childCount = children.getLength();
 158  26854
             for (int i = 0; i < childCount; i++) {
 159  
                 final LayoutContext subContext;
 160  25506
                 final Node child = children.item(i);
 161  0
                 final JEuclidElement subContextElement;
 162  1348
                 if (child instanceof AbstractJEuclidElement) {
 163  0
                     subContext = ((AbstractJEuclidElement) child)
 164  
                             .applyLocalAttributesToContext(contextNow);
 165  24158
                     subContextElement = (JEuclidElement) child;
 166  24158
                 } else {
 167  1348
                     subContext = contextNow;
 168  25506
                     subContextElement = contextElement;
 169  
                 }
 170  25506
                 aci = StringUtil.textContentAsAttributedCharacterIterator(
 171  
                         subContext, subContextElement, child, corrector);
 172  1348
                 maci.appendAttributedCharacterIterator(aci);
 173  24158
             }
 174  0
 
 175  1348
             if (childCount != 1) {
 176  0
                 aci = maci;
 177  24158
             }
 178  0
 
 179  1348
             if (node instanceof TextContentModifier) {
 180  0
                 final TextContentModifier t = (TextContentModifier) node;
 181  24158
                 retVal = t.modifyTextContent(aci, contextNow);
 182  0
             } else {
 183  25506
                 retVal = aci;
 184  24158
             }
 185  25506
         } else {
 186  1348
             final String theText = TextContent.getText(node);
 187  1348
             final float fontSizeInPoint = GraphicsSupport
 188  
                     .getFontsizeInPoint(contextNow)
 189  24158
                     * corrector;
 190  
 
 191  1348
             retVal = StringUtil.convertStringtoAttributedString(theText,
 192  
                     contextElement.getMathvariantAsVariant(), fontSizeInPoint,
 193  48316
                     contextNow).getIterator();
 194  
         }
 195  2696
         return retVal;
 196  
     }
 197  
 
 198  66136
     private static Object[] mapCpavToCpaf(final CodePointAndVariant cpav1,
 199  
             final float fontSize, final LayoutContext context) {
 200  3686
         final List<CodePointAndVariant> alternatives = StringUtil.CMAP
 201  66136
                 .getAllAlternatives(cpav1);
 202  66136
 
 203  69822
         Font font = null;
 204  69822
         int codePoint = 0;
 205  135958
         final Iterator<CodePointAndVariant> it = alternatives.iterator();
 206  69822
         boolean cont = true;
 207  73508
         while (cont) {
 208  16754
             final CodePointAndVariant cpav = it.next();
 209  16754
             if (it.hasNext()) {
 210  726
                 codePoint = cpav.getCodePoint();
 211  13794
                 font = cpav.getVariant().createFont(fontSize, codePoint,
 212  13068
                         context, false);
 213  726
                 if (font != null) {
 214  726
                     cont = false;
 215  53068
                 }
 216  53068
             } else {
 217  2960
                 codePoint = cpav.getCodePoint();
 218  56028
                 font = cpav.getVariant().createFont(fontSize, codePoint,
 219  
                         context, true);
 220  69096
                 cont = false;
 221  66136
             }
 222  3686
         }
 223  3686
         return new Object[] { codePoint, font };
 224  
     }
 225  
 
 226  
     /**
 227  
      * Safely creates a Text Layout from an attributed string. Unlike the
 228  
      * TextLayout constructor, the String here may actually be empty.
 229  
      *
 230  
      * @param g
 231  
      *            Graphics context.
 232  
      * @param aString
 233  
      *            an Attributed String
 234  
      * @param context
 235  
      *            Layout Context to use.
 236  
      * @return a TextLayout
 237  
      */
 238  
     public static TextLayout createTextLayoutFromAttributedString(
 239  47324
             final Graphics2D g, final AttributedString aString,
 240  47324
             final LayoutContext context) {
 241  49959
         final AttributedCharacterIterator charIter = aString.getIterator();
 242  2635
         final boolean empty = charIter.first() == CharacterIterator.DONE;
 243  49959
         final FontRenderContext suggestedFontRenderContext = g
 244  
                 .getFontRenderContext();
 245  49959
         boolean antialiasing = (Boolean) context
 246  47126
                 .getParameter(Parameter.ANTIALIAS);
 247  2635
         if (!empty) {
 248  49750
             final Font font = (Font) aString.getIterator().getAttribute(
 249  47126
                     TextAttribute.FONT);
 250  49750
             if (font != null) {
 251  2624
                 final float fontsize = font.getSize2D();
 252  49750
                 final float minantialias = (Float) context
 253  
                         .getParameter(Parameter.ANTIALIAS_MINSIZE);
 254  2624
                 antialiasing &= fontsize >= minantialias;
 255  
             }
 256  47324
         }
 257  
 
 258  2635
         final FontRenderContext realFontRenderContext = new FontRenderContext(
 259  
                 suggestedFontRenderContext.getTransform(), antialiasing, false);
 260  47324
 
 261  198
         final TextLayout theLayout;
 262  2635
         if (empty) {
 263  11
             theLayout = new TextLayout(" ", new Font("", 0, 0),
 264  47126
                     realFontRenderContext);
 265  
         } else {
 266  2624
             synchronized (TextLayout.class) {
 267  47126
                 // Catches a rare NullPointerException in
 268  
                 // sun.font.FileFontStrike.getCachedGlyphPtr(FileFontStrike.java:448)
 269  49750
                 theLayout = new TextLayout(aString.getIterator(),
 270  
                         realFontRenderContext);
 271  49948
             }
 272  
         }
 273  2635
         return theLayout;
 274  
     }
 275  
 
 276  
     /**
 277  
      * Retrieves the real width from a given text layout.
 278  
      *
 279  
      * @param layout
 280  
      *            the textlayout
 281  
      * @return width
 282  47324
      */
 283  47324
     public static float getWidthForTextLayout(final TextLayout layout) {
 284  49959
         final Rectangle2D r2d = layout.getBounds();
 285  49959
         float realWidth = (float) r2d.getWidth();
 286  8179
         final float xo = (float) r2d.getX();
 287  2635
         if (xo > 0) {
 288  309
             realWidth += xo;
 289  
         }
 290  47324
         // Unfortunately this is necessary, although it does not look like it
 291  
         // makes a lot of sense.
 292  49959
         final float invisibleAdvance = layout.getAdvance()
 293  
                 - layout.getVisibleAdvance();
 294  2635
         return realWidth + invisibleAdvance;
 295  
     }
 296  
 
 297  
     /**
 298  
      * Contains layout information retrieved from a TextLayout.
 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  
          * Default Constructor.
 311  
          *
 312  
          * @param newAscent
 313  
          *            text ascent.
 314  
          * @param newDescent
 315  
          *            text descent.
 316  
          * @param newOffset
 317  
          *            text start offset.
 318  
          * @param newWidth
 319  
          *            text width.
 320  47324
          */
 321  47324
         protected TextLayoutInfo(final float newAscent, final float newDescent,
 322  49959
                 final float newOffset, final float newWidth) {
 323  49959
             this.ascent = newAscent;
 324  49959
             this.descent = newDescent;
 325  49959
             this.offset = newOffset;
 326  2635
             this.width = newWidth;
 327  2635
         }
 328  
 
 329  
         /**
 330  
          * Getter method for ascent.
 331  
          *
 332  
          * @return the ascent
 333  45938
          */
 334  
         public float getAscent() {
 335  2558
             return this.ascent;
 336  
         }
 337  
 
 338  
         /**
 339  
          * Getter method for descent.
 340  
          *
 341  
          * @return the descent
 342  45938
          */
 343  
         public float getDescent() {
 344  2558
             return this.descent;
 345  
         }
 346  
 
 347  
         /**
 348  
          * Getter method for offset.
 349  
          *
 350  
          * @return the offset
 351  53462
          */
 352  
         public float getOffset() {
 353  2976
             return this.offset;
 354  
         }
 355  
 
 356  
         /**
 357  
          * Getter method for width.
 358  
          *
 359  
          * @return the width
 360  39800
          */
 361  
         public float getWidth() {
 362  2217
             return this.width;
 363  
         }
 364  
 
 365  
     };
 366  
 
 367  
     /**
 368  
      * Retrieve the actual layout information from a textLayout. This is
 369  
      * different than the values given when calling the functions directly.
 370  
      *
 371  
      * @param textLayout
 372  
      *            TextLayout to look at.
 373  
      * @param trim
 374  
      *            Trim to actual content
 375  
      * @return a TextLayoutInfo.
 376  
      */
 377  47324
     public static TextLayoutInfo getTextLayoutInfo(final TextLayout textLayout,
 378  47324
             final boolean trim) {
 379  49959
         final Rectangle2D textBounds = textLayout.getBounds();
 380  2635
         final float ascent = (float) (-textBounds.getY());
 381  49959
         final float descent = (float) (textBounds.getY() + textBounds
 382  
                 .getHeight());
 383  49959
         final float xo = (float) textBounds.getX();
 384  6336
         final float xOffset;
 385  2635
         if (xo < 0) {
 386  41341
             xOffset = -xo;
 387  23166
         } else {
 388  2282
             if (trim) {
 389  19109
                 xOffset = -xo;
 390  
             } else {
 391  995
                 xOffset = 0.0f;
 392  47324
             }
 393  47324
         }
 394  2635
         final float width = StringUtil.getWidthForTextLayout(textLayout);
 395  2635
         return new TextLayoutInfo(ascent, descent, xOffset, width);
 396  
     }
 397  
 
 398  
     /**
 399  
      * Counts the displayable characters only. Counts high-surrogates as one
 400  
      * character. Also, ignores combining mark characters.
 401  
      *
 402  
      * @param s
 403  
      *            string to count length of.
 404  
      * @return display length of the string.
 405  
      */
 406  
     public static int countDisplayableCharacters(@Nullable final String s) {
 407  12657
         if (s == null) {
 408  11
             return 0;
 409  
         }
 410  12646
         int length = 0;
 411  12646
         final int strLen = s.length();
 412  17015
         for (int i = 0; i < strLen; i++) {
 413  4369
             final char charHere = s.charAt(i);
 414  4369
             if (!Character.isHighSurrogate(charHere)) {
 415  4358
                 final int codepoint = s.codePointAt(i);
 416  4358
                 if (!StringUtil.CMAP.isMark(codepoint)) {
 417  4336
                     length++;
 418  
                 }
 419  
             }
 420  
         }
 421  12646
         return length;
 422  
     }
 423  
 
 424  
 }