/*
* Copyright (c) 2002 Shaven Puppy Ltd All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. Neither the name of 'Shaven Puppy' nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package com.rugl.text;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.lwjgl.util.vector.Vector2f;
/**
* A single glyph in a Font. This class started life in the SPGL, but
* has been extensively refactored - converted to floating point etc
*/
public final class Glyph
{
/**
* The characters that this glyph represents
*/
public final char character;
/**
* The glyph image
*/
public final GlyphImage image;
/**
* Glyph advance. The distance from the start of this character to
* the start of the next character, disregarding kerning
*/
public final float advance;
/**
* The offset from the baseline of the character to where to draw
* the glyph quad
*/
private final Vector2f glyphOffset;
/**
* The characters that we kern with when placed after
*/
private char[] kernsWith;
/**
* The corresponding kerning values
*/
private float[] kerning;
/**
* @param character
* The character that this glyph represents
* @param image
* the glyph image
* @param origin
* The offset from the glyph's baseline to the origin of
* the glyph
* @param advance
* @param kernsWith
* @param kerning
*/
public Glyph( char character, GlyphImage image, Vector2f origin, float advance,
char[] kernsWith, float[] kerning )
{
this.character = character;
this.image = image;
glyphOffset = origin;
this.advance = advance;
this.kernsWith = kernsWith;
this.kerning = kerning;
}
/**
* Reads a {@link Glyph} from a buffer
*
* @param data
* the buffer
* @param images
* An array of {@link GlyphImage}s to choose from
*/
Glyph( ByteBuffer data, GlyphImage[] images )
{
character = data.getChar();
GlyphImage im = null;
for( int i = 0; i < images.length; i++ )
{
if( images[ i ].represents( character ) )
{
im = images[ i ];
break;
}
}
assert im != null;
image = im;
advance = data.getFloat();
glyphOffset = new Vector2f( data.getFloat(), data.getFloat() );
kernsWith = new char[ data.getInt() ];
kerning = new float[ kernsWith.length ];
for( int i = 0; i < kernsWith.length; i++ )
{
kernsWith[ i ] = data.getChar();
kerning[ i ] = data.getFloat();
}
}
/**
* Reads a {@link Glyph} from a stream
*
* @param is
* @param images
* An array of {@link GlyphImage}s to choose from
* @throws IOException
*/
Glyph( InputStream is, GlyphImage[] images ) throws IOException
{
DataInputStream dis = new DataInputStream( is );
character = dis.readChar();
GlyphImage im = null;
for( int i = 0; i < images.length; i++ )
{
if( images[ i ].represents( character ) )
{
im = images[ i ];
break;
}
}
assert im != null;
image = im;
advance = dis.readFloat();
glyphOffset = new Vector2f( dis.readFloat(), dis.readFloat() );
int kc = dis.readInt();
kernsWith = new char[ kc ];
kerning = new float[ kc ];
for( int i = 0; i < kc; i++ )
{
kernsWith[ i ] = dis.readChar();
kerning[ i ] = dis.readFloat();
}
}
/**
* Stores a glyph in a buffer
*
* @param data
* the buffer
*/
void write( ByteBuffer data )
{
data.putChar( character );
data.putFloat( advance );
data.putFloat( glyphOffset.x );
data.putFloat( glyphOffset.y );
if( kernsWith == null )
{
data.putInt( 0 );
}
else
{
data.putInt( kernsWith.length );
for( int i = 0; i < kernsWith.length; i++ )
{
data.putChar( kernsWith[ i ] );
data.putFloat( kerning[ i ] );
}
}
}
/**
* Calculates the size of the buffer needed to store this glyph, in
* bytes
*
* @return The number of bytes needed to store this glyph
*/
public int dataSize()
{
int size = 0;
// char
size += 2;
// advance
size += 4;
// origin
size += 2 * 4;
// kerns size
size += 4;
// kerns table
if( kernsWith != null )
{
size += kernsWith.length * ( 2 + 4 );
}
return size;
}
/**
* If we have just laid out a glyph, <code>g</code>, and we want to
* lay out this glyph next to it, this function will return the
* required kerning to do so. For example, the kerning value for
* laying a 'A' after a 'W' is likely to be some negative number.
* Add it to 'W''s advance to get the origin of the 'A' glyph
*
* @param g
* The glyph immediately preceding this one
* @return The horizontal offset to apply to the advance.
*/
public float getKerningAfter( char g )
{
if( kernsWith.length == 0 )
{
return 0;
}
int i = Arrays.binarySearch( kernsWith, g );
if( i < 0 || i >= kerning.length )
{
return 0;
}
else
{
if( kernsWith[ i ] != g )
{
return 0;
}
else
{
return kerning[ i ];
}
}
}
/**
* Gets the offset from the character's baseline to the bottom-left
* corner of the textured quad used to render the glyph
*
* @param dest
* The {@link Vector2f} in which to store the results, or
* null to construct a new {@link Vector2f}
* @return The baseline-quad offset
*/
public Vector2f getGlyphOffset( Vector2f dest )
{
if( dest == null )
{
dest = new Vector2f();
}
dest.set( glyphOffset );
return dest;
}
/**
* Updates this {@link Glyph}'s kerning table
*
* @param c
* The preceding character
* @param k
* The kerning value when this glyph succeeds c
*/
public void updateKerning( char c, float k )
{
if( k != 0 )
{
int insertion = Arrays.binarySearch( kernsWith, c );
assert insertion < 0;
insertion += 1;
insertion = -insertion;
char[] newKernsWith = new char[ kernsWith.length + 1 ];
float[] newKerning = new float[ kerning.length + 1 ];
System.arraycopy( kernsWith, 0, newKernsWith, 0, insertion );
System.arraycopy( kerning, 0, newKerning, 0, insertion );
newKernsWith[ insertion ] = c;
newKerning[ insertion ] = k;
if( insertion < newKernsWith.length )
{
System.arraycopy( kernsWith, insertion, newKernsWith, insertion + 1,
kernsWith.length - insertion );
System.arraycopy( kerning, insertion, newKerning, insertion + 1,
kerning.length - insertion );
}
kernsWith = newKernsWith;
kerning = newKerning;
}
}
@Override
public String toString()
{
StringBuilder buff =
new StringBuilder( "Glyph \'" + character + "\' adv = " + advance
+ " origin = " + glyphOffset + " size = " + image.image.getWidth()
+ "x" + image.image.getHeight() );
if( kernsWith.length > 0 )
{
buff.append( "\n\t\tKerning : " );
for( int i = 0; i < kernsWith.length; i++ )
{
buff.append( " " );
buff.append( kernsWith[ i ] );
buff.append( "=" );
buff.append( kerning[ i ] );
}
}
return buff.toString();
}
}
|