package com.xenoage.zong.renderer.awt.text;
import com.xenoage.utils.font.FontInfo;
import com.xenoage.utils.font.FontStyle;
import com.xenoage.zong.core.text.*;
import com.xenoage.zong.renderer.awt.symbol.PathSymbolGraphicAttribute;
import com.xenoage.zong.symbols.PathSymbol;
import com.xenoage.zong.symbols.Symbol;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import static com.xenoage.utils.NullUtils.notNull;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.jse.color.AwtColorUtils.toAwtColor;
import static com.xenoage.utils.jse.font.AwtFontUtils.toAwtFont;
import static com.xenoage.utils.math.geom.Point2f.p;
/**
* Creates {@link TextLayout}s from {@link FormattedText} elements.
*
* @author Andreas Wenger
*/
public class TextLayoutTools {
/**
* Creates {@link TextLayouts} for the given {@link FormattedText},
* including both {@link FormattedTextString}s and {@link FormattedTextSymbol}s.
* @param text the formatted text
* @param widthMm the width of the text frame in mm (needed for non-left alignment)
* @param baseline0 true, if the baseline of the first paragraph should be at y=0,
* false, if y=0 is at the top edge of the first paragraph
* @param frc the font render context needed to layout the text
*/
public static TextLayouts create(FormattedText text, float widthMm, boolean baseline0,
FontRenderContext frc) {
float offsetX = 0;
float offsetY = 0;
ArrayList<TextLayouts.Item> items = alist(text.getParagraphs().size());
for (FormattedTextParagraph p : text.getParagraphs()) {
//x correction
if (p.getAlignment() == Alignment.Center)
offsetX = (widthMm - p.getMetrics().getWidth()) / 2;
else if (p.getAlignment() == Alignment.Right)
offsetX = widthMm - p.getMetrics().getWidth();
else
offsetX = 0;
//top y correction
if (!baseline0)
offsetY += p.getMetrics().getAscent();
TextLayout tl = create(p, frc);
if (tl != null)
items.add(new TextLayouts.Item(tl, p(offsetX, offsetY), p.getText().length()));
//next line
offsetY += p.getMetrics().getAscent() +
p.getMetrics().getDescent() + p.getMetrics().getLeading();
}
return new TextLayouts(items);
}
/**
* Creates a {@link TextLayout} for the given {@link FormattedTextParagraph},
* including both {@link FormattedTextString}s and {@link FormattedTextSymbol}s.
* If impossible (e.g. because the paragraph is empty), null is returned.
*/
private static TextLayout create(FormattedTextParagraph p, FontRenderContext frc) {
//get the raw string
AttributedString as = new AttributedString(p.getText());
//set attributes
int pos = 0;
int count = 0;
for (FormattedTextElement e : p.getElements()) {
int len = e.getLength();
if (len > 0) {
if (e instanceof FormattedTextString) {
as.addAttributes(createAttributesMap(e.getStyle()), pos, pos + len);
count++;
}
else if (e instanceof FormattedTextSymbol) {
FormattedTextSymbol fts = (FormattedTextSymbol) e;
Symbol symbol = fts.getSymbol();
if (symbol instanceof PathSymbol) {
as.addAttribute(TextAttribute.FOREGROUND,
toAwtColor(fts.getStyle().getColor()), pos, pos + 1);
as.addAttribute(TextAttribute.CHAR_REPLACEMENT,
new PathSymbolGraphicAttribute((PathSymbol) symbol, fts.getScaling()), pos, pos + 1);
count += 2;
}
}
pos += len;
}
}
if (count == 0)
return null; //creating a TextLayout would fail
else
return new TextLayout(as.getIterator(), frc);
}
/**
* Creates a Map with the attributes representing
* the given style for an AttibutedString.
*/
private static Map<TextAttribute, Object> createAttributesMap(FormattedTextStyle style) {
Map<TextAttribute, Object> ret = new HashMap<>();
FontInfo fontInfo = notNull(style.getFont(), FontInfo.defaultValue);
//font name
Font font = toAwtFont(fontInfo);
ret.put(TextAttribute.FAMILY, font.getFamily());
//font size
ret.put(TextAttribute.SIZE, font.getSize2D());
//color
ret.put(TextAttribute.FOREGROUND, toAwtColor(style.getColor()));
//bold
FontStyle fontStyle = fontInfo.getStyle();
ret.put(TextAttribute.WEIGHT, (fontStyle.isSet(FontStyle.Bold) ? TextAttribute.WEIGHT_BOLD
: TextAttribute.WEIGHT_REGULAR));
//italic
ret.put(TextAttribute.POSTURE,
(fontStyle.isSet(FontStyle.Italic) ? TextAttribute.POSTURE_OBLIQUE
: TextAttribute.POSTURE_REGULAR));
//underline
ret.put(TextAttribute.UNDERLINE,
(fontStyle.isSet(FontStyle.Underline) ? TextAttribute.UNDERLINE_ON : null));
//striketrough - TODO
//ret.put(TextAttribute.STRIKETHROUGH,
// (style.underline ? TextAttribute.STRIKETHROUGH_ON : null));
//superscript
switch (style.getSuperscript()) {
case Super:
ret.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
break;
case Sub:
ret.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
break;
default:
ret.put(TextAttribute.SUPERSCRIPT, null);
}
return ret;
}
}