package com.himamis.retex.editor.share.serializer;
import com.himamis.retex.editor.share.meta.MetaModel;
import com.himamis.retex.editor.share.model.MathArray;
import com.himamis.retex.editor.share.model.MathCharacter;
import com.himamis.retex.editor.share.model.MathFunction;
import com.himamis.retex.editor.share.model.MathSequence;
import com.himamis.retex.renderer.share.util.LaTeXUtil;
/**
* Serializes internal format into TeX format.
*/
public class TeXSerializer extends SerializerAdapter {
private static final String cursor = "\\jlmcursor{0}";
private static final String cursorBig = "\\jlmcursor{0.9}";
private static final String selection_start = "\\jlmselection{";
private static final String selection_end = "}";
private static final String characterMissing = "\\nbsp ";
private MetaModel metaModel;
/**
* @param metaModel
* model
*/
public TeXSerializer(MetaModel metaModel) {
this.metaModel = metaModel;
}
@Override
public void serialize(MathCharacter mathCharacter, StringBuilder stringBuilder) {
if (mathCharacter.getUnicode() == '\u200b') {
return;
}
// jmathtex v0.7: incompatibility
if (mathCharacter == currentSelStart) {
stringBuilder.append(selection_start);
}
if ("=".equals(mathCharacter.getName())) {
stringBuilder.append("\\,=\\,");
} else if ("@".equals(mathCharacter.getName())) {
stringBuilder.append("\\@ ");
} else if (" ".equals(mathCharacter.getName())) {
stringBuilder.append("\\nbsp ");
} else {
String texName = mathCharacter.getTexName();
if (LaTeXUtil.isSymbolEscapeable(texName)) {
// escape special symbols
stringBuilder.append('\\');
stringBuilder.append(texName);
} else if (LaTeXUtil.isReplaceableSymbol(texName)) {
stringBuilder.append(LaTeXUtil.replaceSymbol(texName));
} else {
stringBuilder.append(texName);
}
}
if (mathCharacter == currentSelEnd) {
stringBuilder.append(selection_end);
}
// safety space after operator / symbol
if (mathCharacter.isOperator() || mathCharacter.isSymbol()) {
stringBuilder.append(' ');
}
}
@Override
public void serialize(MathSequence sequence, StringBuilder stringBuilder) {
if (sequence == null) {
stringBuilder.append("?");
return;
}
int lengthBefore = stringBuilder.length();
boolean addBraces = (sequence.hasChildren() || // {a^b_c}
sequence.size() > 1 || // {aa}
(sequence.size() == 1 && letterLength(sequence, 0) > 1) || // {\pi}
(sequence.size() == 0 && sequence != currentField) || // {\triangleright}
(sequence.size() == 1 && sequence == currentField))
&& // {a|}
(stringBuilder.length() > 0 && stringBuilder.charAt(stringBuilder.length() - 1) != '{');
if (sequence == currentSelStart) {
stringBuilder.append(selection_start);
}
if (addBraces) {
// when necessary add curly braces
stringBuilder.append('{');
}
if (sequence.size() == 0) {
if (sequence == currentField) {
if (currentSelStart == null) {
stringBuilder.append(cursorBig);
}
} else {
if (sequence.getParent() == null
|| /* symbol.getParent() instanceof MathOperator || */
(sequence.getParent() instanceof MathFunction && sequence
.getParentIndex() == sequence.getParent()
.getInsertIndex())) {
stringBuilder.append(characterMissing);
} else {
stringBuilder.append(characterMissing);
}
}
} else {
if (sequence == currentField) {
if (currentOffset > 0) {
serialize(sequence, stringBuilder, 0, currentOffset);
}
if (currentSelStart == null) {
stringBuilder.append(cursor);
}
if (currentOffset < sequence.size()) {
serialize(sequence, stringBuilder, currentOffset,
sequence.size());
}
boolean emptyFormula = stringBuilder
.substring(lengthBefore, stringBuilder.length())
.replace("\\nbsp", "").replace(cursor, "").trim()
.isEmpty();
if(emptyFormula){
String cursorFix = stringBuilder.toString().replace(cursor,cursorBig);
stringBuilder.setLength(0);
stringBuilder.append(cursorFix);
}
} else {
serialize(sequence, stringBuilder, 0, sequence.size());
}
}
if (addBraces) {
// when necessary add curly braces
stringBuilder.append('}');
}
if (sequence == currentSelEnd) {
stringBuilder.append(selection_end);
}
}
@Override
public void serialize(MathSequence sequence, StringBuilder stringBuilder, int from, int to) {
for (int i = from; i < to; i++) {
serialize(sequence.getArgument(i), stringBuilder);
}
}
@Override
public void serialize(MathFunction function, StringBuilder stringBuilder) {
if (function == currentSelStart) {
stringBuilder.append(selection_start);
}
if (metaModel.isGeneral(function.getName())) {
if ("^".equals(function.getName())
|| "_".equals(function.getName())) {
MathSequence parent = function.getParent();
int index = function.getParentIndex();
if (index == 0
|| (index > 0
&& parent.getArgument(index - 1) instanceof MathCharacter && ((MathCharacter) parent
.getArgument(index - 1)).isOperator())) {
stringBuilder.append(characterMissing);
}
stringBuilder.append(function.getName() + '{');
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append('}');
} else if ("frac".equals(function.getName())) {
stringBuilder.append("{");
stringBuilder.append(function.getTexName());
stringBuilder.append("{");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("}{");
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append("}}");
} else if ("sqrt".equals(function.getName())) {
stringBuilder.append(function.getTexName());
stringBuilder.append("{");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("}");
} else if ("nroot".equals(function.getName())) {
stringBuilder.append(function.getTexName());
stringBuilder.append('[');
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("]{");
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append('}');
} else if ("sum".equals(function.getName())
|| "prod".equals(function.getName())) {
stringBuilder.append(function.getTexName());
stringBuilder.append("_{");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append('=');
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append("}^");
serialize(function.getArgument(2), stringBuilder);
boolean addBraces = function.getArgument(3).hasOperator();
addWithBraces(stringBuilder, function.getArgument(3),
addBraces);
} else if ("nsum".equals(function.getName())
|| "nprod".equals(function.getName())) {
stringBuilder.append(function.getTexName());
stringBuilder.append("_{");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append('=');
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append('}');
boolean addBraces = function.getArgument(2).hasOperator();
addWithBraces(stringBuilder, function.getArgument(2),
addBraces);
} else if ("int".equals(function.getName())) {
stringBuilder.append(function.getTexName());
stringBuilder.append('_');
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append('^');
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append('{');
boolean addBraces = currentBraces;
if (addBraces) {
stringBuilder.append("\\left(");
}
serialize(function.getArgument(2), stringBuilder);
// jmathtex v0.7: incompatibility
stringBuilder.append(" " + ("\\nbsp") + " d");
serialize(function.getArgument(3), stringBuilder);
if (addBraces) {
stringBuilder.append("\\right)");
}
stringBuilder.append('}');
} else if ("lim".equals(function.getName())) {
// lim not implemented in jmathtex
stringBuilder.append("\\lim_{");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append(" \\rightarrow ");
serialize(function.getArgument(1), stringBuilder);
// jmathtex v0.7: incompatibility
stringBuilder.append("} " + ("\\nbsp") + " {");
boolean addBraces = (function.getArgument(2).hasOperator()
&& function
.getParent().hasOperator());
this.addWithBraces(stringBuilder, function.getArgument(2),
addBraces);
stringBuilder.append('}');
} else if ("factorial".equals(function.getName())) {
boolean addBraces = function.getArgument(0).hasOperator();
addWithBraces(stringBuilder, function.getArgument(0),
addBraces);
stringBuilder.append(function.getTexName());
} else if ("'".equals(function.getName())) {
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("'");
} else if ("abs".equals(function.getName())) {
stringBuilder.append("\\left|");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("\\right|");
} else if ("floor".equals(function.getName())) {
stringBuilder.append("\\left\\lfloor ");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("\\right\\rfloor ");
} else if ("ceil".equals(function.getName())) {
stringBuilder.append("\\left\\lceil ");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("\\right\\rceil ");
} else if ("function".equals(function.getName())) {
stringBuilder.append("\\mathrm{" + function.getTexName() + "} ");
// jmathtex v0.7: incompatibility
stringBuilder.append("\\nbsp ");
serialize(function.getArgument(0), stringBuilder);
stringBuilder.append("\\left(");
serialize(function.getArgument(1), stringBuilder);
stringBuilder.append("\\right)=");
boolean addBraces = currentBraces
|| (function.getArgument(2).hasOperator() && function
.getParent().hasOperator());
if (addBraces) {
stringBuilder.append("\\left(");
}
serialize(function.getArgument(2), stringBuilder);
if (addBraces) {
stringBuilder.append("\\right)");
}
}
} else {
stringBuilder.append("{\\mathrm{");
stringBuilder.append(function.getTexName());
stringBuilder.append("}");
stringBuilder.append("\\left");
stringBuilder.append(MathFunction.getOpeningBracket());
for (int i = 0; i < function.size(); i++) {
serialize(function.getArgument(i), stringBuilder);
if (i + 1 < function.size()) {
stringBuilder.append(",");
}
}
stringBuilder.append("\\right");
stringBuilder.append(MathFunction.getClosingBracket());
stringBuilder.append("}");
}
if (function == currentSelEnd) {
stringBuilder.append(selection_end);
}
}
private void addWithBraces(StringBuilder stringBuilder,
MathSequence argument, boolean addBraces) {
if (currentBraces || addBraces) {
stringBuilder.append("\\left(");
}
serialize(argument, stringBuilder);
if (currentBraces || addBraces) {
stringBuilder.append("\\right)");
}
}
@Override
public void serialize(MathArray array, StringBuilder stringBuilder) {
if (this.currentSelStart == array) {
stringBuilder.append(TeXSerializer.selection_start);
}
stringBuilder.append(array.getOpen().getTexName());
for (int i = 0; i < array.rows(); i++) {
for (int j = 0; j < array.columns(); j++) {
serialize(array.getArgument(i, j), stringBuilder);
if (j + 1 < array.columns()) {
stringBuilder.append(array.getField().getTexName());
} else if (i + 1 < array.rows()) {
stringBuilder.append(array.getRow().getTexName());
}
}
}
stringBuilder.append(array.getClose().getTexName());
if (this.currentSelEnd == array) {
stringBuilder.append(TeXSerializer.selection_end);
}
}
private static int letterLength(MathSequence symbol, int i) {
if (symbol.getArgument(i) instanceof MathCharacter) {
return ((MathCharacter) symbol.getArgument(i)).getTexName()
.length();
}
return 2;
}
/**
* @param ms
* sequence
* @param model
* model
* @return TeX representation of the sequence
*/
public static String serialize(MathSequence ms, MetaModel model) {
StringBuilder b = new StringBuilder();
new TeXSerializer(model).serialize(ms, b);
return b.toString();
}
}