
com.metsci.glimpse.jogamp.opengl.util.awt.text.Glyph Maven / Gradle / Ivy
/*
* Copyright 2012 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community 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.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.metsci.glimpse.jogamp.opengl.util.awt.text;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import com.jogamp.opengl.util.packrect.Rect;
import com.jogamp.opengl.util.texture.TextureCoords;
/**
* Representation of one or multiple unicode characters to be drawn.
*
*
* The reason for the dual behavior is so that we can take in a sequence of unicode characters and
* partition them into runs of individual glyphs, but if we encounter complex text and/or unicode
* sequences we don't understand, we can render them using the string-by-string method.
*
*
Positioning
*
*
* In an effort to make positioning glyphs more intuitive for both Java2D's and OpenGL's coordinate
* systems, {@code Glyph} now stores its measurements differently. This new way is patterned off
* of HTML's box model.
*
*
* Of course, as expected each glyph maintains its width and height. For spacing however, rather
* than storing positions in Java2D space that must be manipulated on a case-by-case basis,
* {@code Glyph} stores two separate pre-computed boundaries representing space around the text.
* Each of the boundaries has separate top, bottom, left, and right components. These components
* should generally be considered positive, but negative values are sometimes necessary in rare
* situations.
*
*
* The first boundary is called padding. Padding is the space between the actual glyph
* itself and its border. It is included in the width and height of the glyph. The second
* boundary that a glyph stores is called margin, which is extra space around the glyph's
* border. The margin is generally used for separating the glyph from other glyphs when it's
* stored.
*
*
* The diagram below shows the boundaries of a glyph and how they relate to its width and height.
* The inner rectangle is the glyph's boundary, and the outer rectangle is the edge of the margin.
*
*
* +--------------------------------------+
* | top margin |
* | |
* | |------ WIDTH -------| |
* | - +--------------------+ |
* | | | top padding | |
* | | | l ________ r | |
* | l | | e / \ i | r |
* | e | f | | g | i |
* | f H | t | | h | g |
* | t E | | | t | h |
* | I | p | _____ | t |
* | m G | a | | p | |
* | a H | d | | a | m |
* | r T | d | | d | a |
* | g | i | | d | r |
* | i | | n | | i | g |
* | n | | g \________/ n | i |
* | | | g | n |
* | | | bottom padding | |
* | - +--------------------+ |
* | |
* | |
* | bottom margin |
* +--------------------------------------+
*
*
*
* In addition, {@code Glyph} also keeps a few other measurements useful for positioning.
* Ascent is the distance between the baseline and the top border, while descent is
* the distance between the baseline and the bottom border. Kerning is the distance between
* the vertical baseline and the left border. Note that in some cases some of these fields can
* match up with padding components, but in general they should be considered separate.
*
*
* Below is a diagram showing ascent, descent, and kerning.
*
*
* +--------------------+ -
* | | |
* | ________ | |
* | / \ | |
* | | | | |
* | | | | |
* | | | | |
* | | _____ | | ascent
* | | | | |
* | | | | |
* | | | | |
* | | | | |
* | | | | |
* | \________/ | -
* | | |
* | | | descent
* +--------------------+ -
*
* |--| kerning
*
*/
/*@NotThreadSafe*/
public final class Glyph {
// TODO: Create separate Glyph implementations -- one for character one for string?
/**
* Unicode ID if this glyph represents a single character, otherwise -1.
*/
/*@CheckForSigned*/
final int id;
/**
* String if this glyph represents multiple characters, otherwise null.
*/
/*@CheckForNull*/
final String str;
/**
* Font's identifier of glyph.
*/
final int code;
/**
* Distance to next glyph.
*/
final float advance;
/**
* Java2D shape of glyph.
*/
/*@Nonnull*/
final GlyphVector glyphVector;
/**
* Actual character if this glyph represents a single character, otherwise NUL.
*/
final char character;
/**
* Width of text with inner padding.
*/
/*@VisibleForTesting*/
public float width;
/**
* Height of text with inner padding.
*/
/*@VisibleForTesting*/
public float height;
/**
* Length from baseline to top border.
*/
float ascent;
/**
* Length from baseline to bottom border.
*/
float descent;
/**
* Length from baseline to left padding.
*/
float kerning;
/**
* Outer boundary excluded from size.
*/
/*@CheckForNull*/
Boundary margin;
/**
* Inner boundary included in size.
*/
/*@CheckForNull*/
Boundary padding;
/**
* Position of this glyph in texture.
*/
/*@CheckForNull*/
public Rect location;
/**
* Coordinates of this glyph in texture, and all inputs used to compute
* the texture coords.
*
* The inputs must be stored because some of them can change without
* notification (e.g. when the glyph atlas gets repacked), and we need
* to detect such a change. When one or more inputs change, the coords
* get recomputed, and the result and the inputs used get stored.
*
* Would be nice to find a more elegant way to do this.
*/
/*@CheckForNull*/
private TextureCoords recentTextureCoords;
private float recentLeft;
private float recentBottom;
private float recentWidth;
private float recentHeight;
private int recentTextureWidth;
private int recentTextureHeight;
/**
* Cached bounding box of glyph.
*/
/*@CheckForNull*/
Rectangle2D bounds;
/**
* Constructs a {@link Glyph} representing an individual Unicode character.
*
* @param id Unicode ID of character
* @param gv Vector shape of character
* @throws IllegalArgumentException if ID is negative
* @throws NullPointerException if glyph is null
*/
public Glyph(/*@Nonnegative*/ final int id, /*@Nonnull*/ final GlyphVector gv) {
Check.argument(id >= 0, "ID cannot be negative");
Check.notNull(gv, "Glyph vector cannot be null");
this.id = id;
this.str = null;
this.code = gv.getGlyphCode(0);
this.advance = gv.getGlyphMetrics(0).getAdvance();
this.glyphVector = gv;
this.character = (char) id;
this.recentTextureCoords = null;
}
/**
* Constructs a {@link Glyph} representing a sequence of characters.
*
* @param str Sequence of characters
* @param gv Vector shape of sequence
* @throws NullPointerException if string or glyph vector is null
*/
public Glyph(/*@Nonnull*/ final String str, /*@Nonnull*/ final GlyphVector gv) {
Check.notNull(str, "String cannot be null");
Check.notNull(gv, "Glyph vector cannot be null");
this.id = -1;
this.str = str;
this.code = -1;
this.advance = 0;
this.glyphVector = gv;
this.character = '\0';
}
public void clearTextureCoordinates( )
{
this.recentTextureCoords = null;
}
public TextureCoords getTextureCoordinates( int textureWidth, int textureHeight )
{
// Determine dimensions in pixels
float width = this.width;
float height = this.height;
float left = this.location.x( ) + this.margin.left;
float bottom = ( int ) ( this.location.y( ) + this.margin.top + this.height );
if ( this.recentTextureCoords == null
|| width != this.recentWidth
|| height != this.recentHeight
|| left != this.recentLeft
|| bottom != this.recentBottom
|| textureWidth != this.recentTextureWidth
|| textureHeight != this.recentTextureHeight )
{
// Convert to normalized texture coordinates
float l = left / textureWidth;
float b = bottom / textureHeight;
float r = ( left + width ) / textureWidth;
float t = ( bottom - height ) / textureHeight;
// Store result
this.recentTextureCoords = new TextureCoords( l, b, r, t );
// Store inputs
this.recentLeft = left;
this.recentBottom = bottom;
this.recentWidth = width;
this.recentHeight = height;
this.recentTextureWidth = textureWidth;
this.recentTextureHeight = textureHeight;
}
return this.recentTextureCoords;
}
/*@Nonnull*/
@Override
public String toString() {
return (str != null) ? str : Character.toString(character);
}
/**
* Space around a rectangle.
*/
/*@Immutable*/
static final class Boundary {
/**
* Space above rectangle.
*/
final int top;
/**
* Space below rectangle.
*/
final int bottom;
/**
* Space beside rectangle to left.
*/
final int left;
/**
* Space beside rectangle to right.
*/
final int right;
/**
* Constructs a {@link Boundary} by computing the distances between two rectangles.
*
* @param large Outer rectangle
* @param small Inner rectangle
* @throws NullPointerException if either rectangle is null
*/
Boundary(/*@Nonnull*/ final Rectangle2D large, /*@Nonnull*/ final Rectangle2D small) {
Check.notNull(large, "Large rectangle cannot be null");
Check.notNull(small, "Small rectangle cannot be null");
top = (int) (large.getMinY() - small.getMinY()) * -1;
left = (int) (large.getMinX() - small.getMinX()) * -1;
bottom = (int) (large.getMaxY() - small.getMaxY());
right = (int) (large.getMaxX() - small.getMaxX());
}
}
}