package com.xenoage.zong.core.text; import static com.xenoage.utils.collections.CList.clist; import static com.xenoage.utils.iterators.It.it; import static com.xenoage.utils.kernel.Range.range; import static com.xenoage.utils.kernel.Tuple2.t; import static com.xenoage.zong.core.text.FormattedText.fText; import static com.xenoage.zong.core.text.FormattedTextParagraph.fPara; import static com.xenoage.zong.core.text.FormattedTextString.fString; import com.xenoage.utils.collections.CList; import com.xenoage.utils.iterators.It; import com.xenoage.utils.kernel.Tuple2; /** * This class creates {@link FormattedText} instances. * * @author Andreas Wenger */ public class FormattedTextUtils { /** * Creates a {@link FormattedText} from the given {@link Text} instance. * If it is already a {@link FormattedText}, it is returned. Otherwise, * the given default style and alignment is applied. */ public static FormattedText styleText(Text text, FormattedTextStyle defaultStyle, Alignment defaultAlignment) { if (text instanceof FormattedText) return (FormattedText) text; else return fText(text.toString(), defaultStyle, defaultAlignment); } /** * Creates a {@link FormattedText} from the given {@link Text} instance. * If it is already a {@link FormattedText}, it is returned. Otherwise, * the given default style and left alignment is applied. */ public static FormattedText styleText(Text text, FormattedTextStyle defaultStyle) { if (text instanceof FormattedText) return (FormattedText) text; else return fText(text.toString(), defaultStyle, Alignment.Left); } /** * Creates a {@link FormattedText} from the given String. * The given default style and left alignment is applied. */ public static FormattedText styleText(String text, FormattedTextStyle defaultStyle) { return fText(text, defaultStyle, Alignment.Left); } /** * Merges the given two texts. * The last paragraph of the first text and the first paragraph of the second * text are merged into a single paragraph with the alignment of the * last paragraph of the first text. */ public static FormattedText merge(FormattedText text1, FormattedText text2) { CList<FormattedTextParagraph> ps = clist(); //first text for (int i : range(0, text1.getParagraphs().size() - 2)) ps.add(text1.getParagraphs().get(i)); //connection paragraph FormattedTextParagraph lastPText1 = text1.getParagraphs().getLast(); FormattedTextParagraph firstPText2 = text2.getParagraphs().getFirst(); CList<FormattedTextElement> middleP = clist(); middleP.addAll(lastPText1.getElements()); middleP.addAll(firstPText2.getElements()); ps.add(fPara(middleP.close(), lastPText1.getAlignment())); //second text for (int i : range(1, text2.getParagraphs().size() - 1)) ps.add(text2.getParagraphs().get(i)); return clean(fText(ps.close())); } /** * Splits the given {@link FormattedText} at the given position. */ public static Tuple2<FormattedText, FormattedText> split(FormattedText source, int position) { int curPos = 0; CList<FormattedTextParagraph> ps1 = clist(); CList<FormattedTextParagraph> ps2 = clist(); //collect data up to split position int iP; firstText: for (iP = 0; iP < source.getParagraphs().size(); iP++) { FormattedTextParagraph oldP = source.getParagraphs().get(iP); for (int iE : range(oldP.getElements())) { FormattedTextElement oldE = oldP.getElements().get(iE); if (position >= curPos && position <= curPos + oldE.getLength()) { //element found. if we are at the beginning or end of the found element, we can reuse it. //otherwise, we have to split the found element. if (position == curPos) { //easy case: at the beginning of an element. //just use all elements before this one for the first text //and this one and the following ones for the second text CList<FormattedTextElement> es1 = clist(); CList<FormattedTextElement> es2 = clist(); for (int i : range(oldP.getElements())) { if (i < iE) es1.add(oldP.getElements().get(i)); else es2.add(oldP.getElements().get(i)); } ps1.add(fPara(es1.close(), oldP.getAlignment())); ps2.add(fPara(es2.close(), oldP.getAlignment())); } else if (position == curPos + oldE.getLength()) { //easy case: at the end of an element. //just use all elements up to this one for the first text //and the following ones for the second text CList<FormattedTextElement> es1 = clist(); CList<FormattedTextElement> es2 = clist(); for (int i : range(oldP.getElements())) { if (i <= iE) es1.add(oldP.getElements().get(i)); else es2.add(oldP.getElements().get(i)); } ps1.add(fPara(es1.close(), oldP.getAlignment())); ps2.add(fPara(es2.close(), oldP.getAlignment())); } else { //split element: //use all elements before this one and the first part of this one for the first text; //and the second part of this one and the following elements for the second text int splitPos = position - curPos; String splitText = oldE.getText(); FormattedTextStyle style = oldE.getStyle(); FormattedTextElement oldEPart1 = fString(splitText.substring(0, splitPos), style); FormattedTextElement oldEPart2 = splitText.length() > splitPos ? fString( splitText.substring(splitPos, splitText.length()), style) : null; //collect elements CList<FormattedTextElement> es1 = clist(); CList<FormattedTextElement> es2 = clist(); for (int i : range(oldP.getElements())) { if (i < iE) es1.add(oldP.getElements().get(i)); else if (i == iE) { es1.add(oldEPart1); if (oldEPart2 != null) es2.add(oldEPart2); } else es2.add(oldP.getElements().get(i)); } ps1.add(fPara(es1.close(), oldP.getAlignment())); ps2.add(fPara(es2.close(), oldP.getAlignment())); } break firstText; } else { //position not yet reached. go on. curPos += oldE.getLength(); } } ps1.add(oldP); curPos += 1; //line break character } //collect data after split position iP++; for (; iP < source.getParagraphs().size(); iP++) { ps2.add(source.getParagraphs().get(iP)); } return t(fText(ps1.close()), fText(ps2.close())); } /** * Inserts the given {@link FormattedText} at the given position. */ public static FormattedText insert(FormattedText source, int position, FormattedText input) { Tuple2<FormattedText, FormattedText> sourceSplit = split(source, position); return merge(merge(sourceSplit.get1(), input), sourceSplit.get2()); } /** * Inserts the given {@link FormattedTextElement} at the given position. */ public static FormattedText insert(FormattedText source, int position, FormattedTextElement input) { FormattedText tInput = fText(fPara(input)); return insert(source, position, tInput); } /** * Inserts the given string at the given position. * The style at the given position is used for the inserted string. */ public static FormattedText insert(FormattedText source, int position, String input) { FormattedTextStyle style = getStyle(source, position); return insert(source, position, fString(input, style)); } /** * Gets the style of the given text at the given position. */ private static FormattedTextStyle getStyle(FormattedText text, int position) { //get the style at the insert position int curPos = 0; It<FormattedTextParagraph> oldPs = it(text.getParagraphs()); for (FormattedTextParagraph oldP : oldPs) { It<FormattedTextElement> oldEs = it(oldP.getElements()); for (FormattedTextElement oldE : oldEs) { if (position > curPos && position <= curPos + oldE.getLength()) { return oldE.getStyle(); } else { //position not yet reached. go on. curPos += oldE.getLength(); } } curPos += 1; //line break character } throw new IndexOutOfBoundsException("Invalid position"); } /** * Cleans the given text by merging adjacent {@link FormattedTextString}s * with the same style. * PERFORMANCE: only copy array if there is really a change */ public static FormattedText clean(FormattedText text) { CList<FormattedTextParagraph> newPs = clist(text.getParagraphs()); for (int iP : range(newPs)) { FormattedTextParagraph oldP = text.getParagraphs().get(iP); CList<FormattedTextElement> newEs = clist(oldP.getElements()); int iE = 0; boolean changed = false; while (iE < newEs.size() - 1) { //look at pairs of adjacent elements FormattedTextElement e1 = newEs.get(iE); FormattedTextElement e2 = newEs.get(iE + 1); if (e1 instanceof FormattedTextString && e2 instanceof FormattedTextString && e1.getStyle().equals(e2.getStyle())) { //merge two adjacent strings with the same style changed = true; newEs.remove(iE + 1); newEs.remove(iE); newEs.add(iE, new FormattedTextString(e1.getText() + e2.getText(), e1.getStyle())); } else { iE++; } } if (changed) newPs.set(iP, new FormattedTextParagraph(newEs, oldP.getAlignment())); } return new FormattedText(newPs.close()); } }