TextLayout.java :  » Game » rugl » com » rugl » text » Java Open Source

Java Open Source » Game » rugl 
rugl » com » rugl » text » TextLayout.java

package com.rugl.text;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;

import com.rugl.geom.ColouredShape;
import com.rugl.geom.TexturedShape;
import com.rugl.geom.TexturedShapeBuilder;

/**
 * Represents a block of line-wrapped text
 * 
 * @author ryanm
 */
public class TextLayout
{
  /**
   * Defines how text is laid out on each line
   * 
   * @author ryanm
   */
  public static enum Alignment
  {
    /**
     * Text is placed against the left side of the line
     */
    LEFT
    {
      @Override
      public void layoutLine( CharSequence line, float lineLength, float[] vertices )
      {
        // no op
      }
    },
    /**
     * Text is placed against the right side of the line
     */
    RIGHT
    {
      @Override
      public void layoutLine( CharSequence line, float lineLength, float[] vertices )
      {
        float whiteSpace = lineLength - vertices[ vertices.length - 3 ];

        for( int i = 0; i < vertices.length; i += 3 )
        {
          vertices[ i ] += whiteSpace;
        }
      }
    },
    /**
     * Text is placed in the center of the line
     */
    CENTER
    {
      @Override
      public void layoutLine( CharSequence line, float lineLength, float[] vertices )
      {
        float whiteSpace = lineLength - vertices[ vertices.length - 3 ];

        whiteSpace /= 2;

        for( int i = 0; i < vertices.length; i += 3 )
        {
          vertices[ i ] += whiteSpace;
        }
      }
    },
    /**
     * The whitespace in the text is expanded to fill the line
     */
    JUSTIFY
    {
      @Override
      public void layoutLine( CharSequence line, float lineLength, float[] vertices )
      {
        float whiteSpace = lineLength - vertices[ vertices.length - 3 ];

        int spaces = 0;
        for( int i = 0; i < line.length(); i++ )
        {
          if( line.charAt( i ) == ' ' )
          {
            spaces++;
          }
        }
        whiteSpace /= spaces;

        float offset = 0;
        for( int i = 0; i < line.length(); i++ )
        {
          vertices[ 3 * 4 * i ] += offset;
          vertices[ 3 * ( 4 * i + 1 ) ] += offset;

          if( line.charAt( i ) == ' ' )
          {
            offset += whiteSpace;
          }

          vertices[ 3 * ( 4 * i + 2 ) ] += offset;
          vertices[ 3 * ( 4 * i + 3 ) ] += offset;
        }
      }
    };

    /**
     * Lays out the line of text.
     * 
     * @param line
     *           The text to lay out
     * @param lineLength
     *           The length of the line
     * @param vertices
     *           The vertices of the characters, in bl tl, br tr
     *           order
     */
    public abstract void layoutLine( CharSequence line, float lineLength,
        float[] vertices );
  };

  /**
   * The full text of this {@link TextLayout}
   */
  public final String fullText;

  /**
   * Array of separate lines
   */
  public final CharSequence[] lines;

  /**
   * The alignment used
   */
  public final Alignment alignment;

  /**
   * The font used
   */
  public final Font font;

  /**
   * The generated layout. The origin of the first character is at
   * the (0,0)
   */
  public final TexturedShape textShape;

  /**
   * Constructs a new {@link TextLayout}
   * 
   * @param text
   * @param font
   * @param alignment
   * @param lineLength
   * @param colour
   *           <code>null</code> for white
   */
  public TextLayout( CharSequence text, Font font, Alignment alignment,
      float lineLength, int colour )
  {
    fullText = text.toString();
    this.font = font;
    this.alignment = alignment;

    List<CharSequence> linesList = new ArrayList<CharSequence>();

    if( alignment == null )
    {
      alignment = Alignment.LEFT;
    }

    TexturedShapeBuilder tsb = new TexturedShapeBuilder();

    float yOffset = 0;

    int start = 0;
    int end = 0;
    do
    {
      while( end < text.length() && text.charAt( end ) != '\n' )
      {
        end++;
      }

      CharSequence[] lineArray =
          splitParagraph( text.subSequence( start, end ), font, lineLength );

      Collections.addAll( linesList, lineArray );

      // add lines
      for( int i = 0; i < lineArray.length; i++ )
      {
        ColouredShape ts = font.buildTextShape( lineArray[ i ], colour );

        ts.translate( 0, yOffset, 0 );

        if( alignment != Alignment.JUSTIFY || i < lineArray.length - 1 )
        {
          alignment.layoutLine( lineArray[ i ], lineLength, ts.vertices );
        }

        tsb.addShape( ts );

        yOffset -= font.size;
      }

      end++;
      start = end;
    }
    while( end < text.length() );

    textShape = tsb.compile();

    lines = linesList.toArray( new CharSequence[ linesList.size() ] );
  }

  /**
   * Splits a paragraph into lines
   * 
   * @param text
   * @param f
   * @param lineLength
   */
  private static CharSequence[] splitParagraph( CharSequence text, Font f,
      float lineLength )
  {
    List<CharSequence> lines = new ArrayList<CharSequence>();

    int prev = 0;
    int increment;
    int next = 0;

    do
    {
      prev = next;
      increment =
          TextMeasurer.indexOfCharAt( f, text.subSequence( prev, text.length() ),
              lineLength );

      if( increment == -1 )
      {
        next = text.length();
      }
      else
      {
        increment = Math.max( 1, increment );

        next += increment;

        next = Math.min( next, text.length() - 1 );

        // rewind till the last whitespace
        while( next > prev && !Character.isWhitespace( text.charAt( next ) ) )
        {
          next--;
        }

        if( next == prev )
        { // we have no choice but to split a word
          next += increment;
        }
      }

      if( prev != next )
      {
        String l = text.subSequence( prev, next ).toString();

        if( prev == 0 && next == text.length() - 1 )
        {
          l = l.trim();
        }

        lines.add( l );
      }
    }
    while( increment > 0 );

    return lines.toArray( new CharSequence[ lines.size() ] );
  }

  /**
   * Computes the caret position
   * 
   * @param caretPosition
   *           The index of the character that the caret is in front
   *           of.
   * @param dest
   *           The {@link Vector2f} array in which to store the
   *           result, or null to construct a new {@link Vector2f}[4]
   * @return <code>dest</code>, or a new {@link Vector2f}[4]
   *         containing the result
   */
  public Vector2f getCaretPosition( int caretPosition, Vector2f dest )
  {
    if( dest == null )
    {
      dest = new Vector2f();
    }

    if( caretPosition < 0 )
    {
      caretPosition = 0;
    }

    if( caretPosition < fullText.length() )
    {
      // 4 vertices per character, so...
      Vector3f v = textShape.getVertex( 4 * caretPosition, null );

      // the offset from the char baseline to the bl vertex of the
      // quad

      Glyph glyph = font.map( fullText.charAt( caretPosition ) );
      Vector2f glyphOffset = glyph.getGlyphOffset( null );

      dest.set( v.x - glyphOffset.x, v.y - glyphOffset.y );
    }
    else
    {
      Vector3f v = textShape.getVertex( textShape.vertexCount() - 4, null );

      Glyph glyph = font.map( fullText.charAt( fullText.length() - 1 ) );
      Vector2f glyphOffset = glyph.getGlyphOffset( null );

      dest.set( v.x - glyphOffset.x + glyph.advance, v.y - glyphOffset.y );
    }

    return dest;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.