com.jme3.font.Letters Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-core Show documentation
Show all versions of jme3-core Show documentation
jMonkeyEngine is a 3-D game engine for adventurous Java developers
/*
* Copyright (c) 2009-2021 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.font;
import com.jme3.font.BitmapFont.Align;
import com.jme3.font.BitmapFont.VAlign;
import com.jme3.font.ColorTags.Range;
import com.jme3.math.ColorRGBA;
import java.util.LinkedList;
/**
* Manage and align LetterQuads
* @author YongHoon
*/
class Letters {
private final LetterQuad head;
private final LetterQuad tail;
private final BitmapFont font;
private LetterQuad current;
final private StringBlock block;
private float totalWidth;
private float totalHeight;
final private ColorTags colorTags = new ColorTags();
private ColorRGBA baseColor = null;
private float baseAlpha = -1;
private String plainText;
Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
final String text = bound.getText();
this.block = bound;
this.font = font;
head = new LetterQuad(font, rightToLeft);
tail = new LetterQuad(font, rightToLeft);
setText(text);
}
void setText(final String text) {
colorTags.setText(text);
plainText = colorTags.getPlainText();
head.setNext(tail);
tail.setPrevious(head);
current = head;
if (text != null && plainText.length() > 0) {
LetterQuad l = head;
CharSequence characters = plainText;
if (font.getGlyphParser() != null) {
characters = font.getGlyphParser().parse(plainText);
}
for (int i = 0; i < characters.length(); i++) {
l = l.addNextCharacter(characters.charAt(i));
if (baseColor != null) {
// Give the letter a default color if
// one has been provided.
l.setColor(baseColor);
}
}
}
LinkedList ranges = colorTags.getTags();
if (!ranges.isEmpty()) {
for (int i = 0; i < ranges.size()-1; i++) {
Range start = ranges.get(i);
Range end = ranges.get(i+1);
setColor(start.start, end.start, start.color);
}
Range end = ranges.getLast();
setColor(end.start, plainText.length(), end.color);
}
invalidate();
}
LetterQuad getHead() {
return head;
}
LetterQuad getTail() {
return tail;
}
void update() {
LetterQuad l = head;
int lineCount = 1;
BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
while (!l.isTail()) {
if (l.isInvalid()) {
l.update(block);
// Without a text block, the next line always returns false = no text wrap will be applied.
if (l.isInvalid(block)) {
switch (block.getLineWrapMode()) {
case Character:
lineWrap(l);
lineCount++;
break;
case Word:
if (!l.isBlank()) {
// search last blank character before this word
LetterQuad blank = l;
while (!blank.isBlank()) {
if (blank.isLineStart() || blank.isHead()) {
lineWrap(l);
lineCount++;
blank = null;
break;
}
blank = blank.getPrevious();
}
if (blank != null) {
blank.setEndOfLine();
lineCount++;
while (blank != l) {
blank = blank.getNext();
blank.invalidate();
blank.update(block);
}
}
}
break;
case NoWrap:
LetterQuad cursor = l.getPrevious();
while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
cursor = cursor.getPrevious();
}
cursor.setBitmapChar(ellipsis);
cursor.update(block);
cursor = cursor.getNext();
while (!cursor.isTail() && !cursor.isLineFeed()) {
cursor.setBitmapChar(null);
cursor.update(block);
cursor = cursor.getNext();
}
break;
case Clip:
// Clip the character that falls out of bounds
l.clip(block);
// Clear the rest up to the next line feed.
// = for texts attached to a text block, all coming characters are cleared except a linefeed is explicitly used
for (LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext()) {
q.setBitmapChar(null);
q.update(block);
}
break;
}
}
} else if (current.isInvalid(block)) {
invalidate(current);
}
if (l.isEndOfLine()) {
lineCount++;
}
l = l.getNext();
}
block.setLineCount(lineCount);
align();
rewind();
}
private void align() {
if (block.getTextBox() == null) {
// Without a text block, there is no alignment.
return;
// For unbounded left-to-right texts the letters will simply be shown starting from
// x0 = 0 and advance toward right as line length is considered to be infinite.
// For unbounded right-to-left texts the letters will be shown starting from x0 = 0
// (at the same position as left-to-right texts) but move toward the left from there.
}
final Align alignment = block.getAlignment();
final VAlign valignment = block.getVerticalAlignment();
final float width = block.getTextBox().width;
final float height = block.getTextBox().height;
float lineWidth = 0;
float gapX = 0;
float gapY = 0;
validateSize();
if (totalHeight < height) { // align vertically only for no overflow
switch (valignment) {
case Top:
gapY = 0;
break;
case Center:
gapY = (height - totalHeight) * 0.5f;
break;
case Bottom:
gapY = height - totalHeight;
break;
}
}
if (font.isRightToLeft()) {
if ((alignment == Align.Right && valignment == VAlign.Top)) {
return;
}
LetterQuad cursor = tail.getPrevious();
// Temporary set the flag, it will be reset when invalidated.
cursor.setEndOfLine();
while (!cursor.isHead()) {
if (cursor.isEndOfLine()) {
if (alignment == Align.Left) {
gapX = block.getTextBox().x - cursor.getX0();
} else if (alignment == Align.Center) {
gapX = (block.getTextBox().x - cursor.getX0()) / 2;
} else {
gapX = 0;
}
}
cursor.setAlignment(gapX, gapY);
cursor = cursor.getPrevious();
}
} else { // left-to-right
if (alignment == Align.Left && valignment == VAlign.Top) {
return;
}
LetterQuad cursor = tail.getPrevious();
// Temporary set the flag, it will be reset when invalidated.
cursor.setEndOfLine();
while (!cursor.isHead()) {
if (cursor.isEndOfLine()) {
lineWidth = cursor.getX1() - block.getTextBox().x;
if (alignment == Align.Center) {
gapX = (width - lineWidth) / 2;
} else if (alignment == Align.Right) {
gapX = width - lineWidth;
} else {
gapX = 0;
}
}
cursor.setAlignment(gapX, gapY);
cursor = cursor.getPrevious();
}
}
}
private void lineWrap(LetterQuad l) {
if (l.isHead() || l.isBlank())
return;
l.getPrevious().setEndOfLine();
l.invalidate();
l.update(block);
}
float getCharacterX0() {
return current.getX0();
}
float getCharacterY0() {
return current.getY0();
}
float getCharacterX1() {
return current.getX1();
}
float getCharacterY1() {
return current.getY1();
}
float getCharacterAlignX() {
return current.getAlignX();
}
float getCharacterAlignY() {
return current.getAlignY();
}
float getCharacterWidth() {
return current.getWidth();
}
float getCharacterHeight() {
return current.getHeight();
}
public boolean nextCharacter() {
if (current.isTail())
return false;
current = current.getNext();
return true;
}
public int getCharacterSetPage() {
return current.getBitmapChar().getPage();
}
public LetterQuad getQuad() {
return current;
}
public void rewind() {
current = head;
}
public void invalidate() {
invalidate(head);
}
public void invalidate(LetterQuad cursor) {
totalWidth = -1;
totalHeight = -1;
while (!cursor.isTail() && !cursor.isInvalid()) {
cursor.invalidate();
cursor = cursor.getNext();
}
}
float getScale() {
return block.getSize() / font.getCharSet().getRenderedSize();
}
public boolean isPrintable() {
return current.getBitmapChar() != null;
}
float getTotalWidth() {
validateSize();
return totalWidth;
}
float getTotalHeight() {
validateSize();
return totalHeight;
}
void validateSize() {
// also called from BitMaptext.getLineWidth() via getTotalWidth()
if (totalWidth < 0) {
LetterQuad l = head;
while (!l.isTail()) {
if (font.isRightToLeft()) {
totalWidth = Math.max(totalWidth, Math.abs(l.getX0()));
} else {
totalWidth = Math.max(totalWidth, l.getX1());
}
l = l.getNext();
}
}
totalHeight = font.getLineHeight(block) * block.getLineCount();
}
/**
* @param start start index to set style. inclusive.
* @param end end index to set style. EXCLUSIVE.
* @param style
*/
void setStyle(int start, int end, int style) {
LetterQuad cursor = head.getNext();
while (!cursor.isTail()) {
if (cursor.getIndex() >= start && cursor.getIndex() < end) {
cursor.setStyle(style);
}
cursor = cursor.getNext();
}
}
/**
* Sets the base color for all new letter quads and resets
* the color of existing letter quads.
*/
void setColor(ColorRGBA color) {
baseColor = color;
colorTags.setBaseColor(color);
setColor(0, block.getText().length(), color);
}
ColorRGBA getBaseColor() {
return baseColor;
}
/**
* @param start start index to set style. inclusive.
* @param end end index to set style. EXCLUSIVE.
* @param color
*/
void setColor(int start, int end, ColorRGBA color) {
LetterQuad cursor = head.getNext();
while (!cursor.isTail()) {
if (cursor.getIndex() >= start && cursor.getIndex() < end) {
cursor.setColor(color);
}
cursor = cursor.getNext();
}
}
float getBaseAlpha() {
return baseAlpha;
}
void setBaseAlpha(float alpha) {
this.baseAlpha = alpha;
colorTags.setBaseAlpha(alpha);
if (alpha == -1) {
alpha = baseColor != null ? baseColor.a : 1;
}
// Forward the new alpha to the letter quads
LetterQuad cursor = head.getNext();
while (!cursor.isTail()) {
cursor.setAlpha(alpha);
cursor = cursor.getNext();
}
// If the alpha was reset to "default" (-1),
// then the color tags are potentially reset, and
// we need to reapply them. This has to be done
// second since it may override any alpha values
// set above... but you still need to do the above
// since non-color tagged text is treated differently
// even if part of a color tagged string.
if (baseAlpha == -1) {
LinkedList ranges = colorTags.getTags();
if (!ranges.isEmpty()) {
for (int i = 0; i < ranges.size()-1; i++) {
Range start = ranges.get(i);
Range end = ranges.get(i+1);
setColor(start.start, end.start, start.color);
}
Range end = ranges.getLast();
setColor(end.start, plainText.length(), end.color);
}
}
invalidate();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy