Text highlighter optimizations
This commit is contained in:
parent
25c83bac94
commit
2215b11e6f
@ -22,6 +22,8 @@
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.common.text.Strings;
|
||||
|
||||
@ -35,7 +37,7 @@ import jscl.NumeralBase;
|
||||
* Date: 12/15/11
|
||||
* Time: 9:01 PM
|
||||
*/
|
||||
public abstract class AbstractNumberBuilder {
|
||||
public abstract class BaseNumberBuilder {
|
||||
|
||||
@Nonnull
|
||||
protected final CalculatorEngine engine;
|
||||
@ -46,7 +48,7 @@ public abstract class AbstractNumberBuilder {
|
||||
@Nullable
|
||||
protected NumeralBase nb;
|
||||
|
||||
protected AbstractNumberBuilder(@Nonnull CalculatorEngine engine) {
|
||||
protected BaseNumberBuilder(@Nonnull CalculatorEngine engine) {
|
||||
this.engine = engine;
|
||||
this.nb = engine.getNumeralBase();
|
||||
}
|
||||
@ -58,15 +60,14 @@ public abstract class AbstractNumberBuilder {
|
||||
* @return true if we can continue of processing of current number, if false - new number should be constructed
|
||||
*/
|
||||
protected boolean canContinue(@Nonnull MathType.Result mathTypeResult) {
|
||||
boolean result = mathTypeResult.getMathType().getGroupType() == MathType.MathGroupType.number &&
|
||||
return mathTypeResult.type.getGroupType() == MathType.MathGroupType.number &&
|
||||
!spaceBefore(mathTypeResult) &&
|
||||
numeralBaseCheck(mathTypeResult) &&
|
||||
numeralBaseInTheStart(mathTypeResult.getMathType()) || isSignAfterE(mathTypeResult);
|
||||
return result;
|
||||
numeralBaseInTheStart(mathTypeResult.type) || isSignAfterE(mathTypeResult);
|
||||
}
|
||||
|
||||
private boolean spaceBefore(@Nonnull MathType.Result mathTypeResult) {
|
||||
return numberBuilder == null && Strings.isEmpty(mathTypeResult.getMatch().trim());
|
||||
return numberBuilder == null && Strings.isEmpty(mathTypeResult.match.trim());
|
||||
}
|
||||
|
||||
private boolean numeralBaseInTheStart(@Nonnull MathType mathType) {
|
||||
@ -74,12 +75,12 @@ public abstract class AbstractNumberBuilder {
|
||||
}
|
||||
|
||||
private boolean numeralBaseCheck(@Nonnull MathType.Result mathType) {
|
||||
return mathType.getMathType() != MathType.digit || getNumeralBase().getAcceptableCharacters().contains(mathType.getMatch().charAt(0));
|
||||
return mathType.type != MathType.digit || getNumeralBase().getAcceptableCharacters().contains(mathType.match.charAt(0));
|
||||
}
|
||||
|
||||
private boolean isSignAfterE(@Nonnull MathType.Result mathTypeResult) {
|
||||
if (!isHexMode()) {
|
||||
final String match = mathTypeResult.getMatch();
|
||||
final String match = mathTypeResult.match;
|
||||
if ("−".equals(match) || "-".equals(match) || "+".equals(match)) {
|
||||
final StringBuilder localNb = numberBuilder;
|
||||
if (localNb != null && localNb.length() > 0) {
|
||||
@ -100,4 +101,6 @@ public abstract class AbstractNumberBuilder {
|
||||
protected NumeralBase getNumeralBase() {
|
||||
return nb == null ? engine.getNumeralBase() : nb;
|
||||
}
|
||||
|
||||
public abstract int process(@Nonnull SpannableStringBuilder sb, @Nonnull MathType.Result result);
|
||||
}
|
@ -63,7 +63,7 @@ public class CalculatorKeyboardImpl implements CalculatorKeyboard {
|
||||
final StringBuilder textToBeInserted = new StringBuilder(text);
|
||||
|
||||
final MathType.Result mathType = MathType.getType(text, 0, false);
|
||||
switch (mathType.getMathType()) {
|
||||
switch (mathType.type) {
|
||||
case function:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
@ -78,7 +78,7 @@ public class CalculatorKeyboardImpl implements CalculatorKeyboard {
|
||||
}
|
||||
|
||||
if (cursorPositionOffset == 0) {
|
||||
if (MathType.openGroupSymbols.contains(text)) {
|
||||
if (MathType.groupSymbols.contains(text)) {
|
||||
cursorPositionOffset = -1;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -34,27 +36,33 @@ import jscl.NumeralBase;
|
||||
* Time: 8:33 PM
|
||||
*/
|
||||
|
||||
public class LiteNumberBuilder extends AbstractNumberBuilder {
|
||||
public class LiteNumberBuilder extends BaseNumberBuilder {
|
||||
|
||||
public LiteNumberBuilder(@Nonnull CalculatorEngine engine) {
|
||||
super(engine);
|
||||
this.nb = engine.getNumeralBase();
|
||||
}
|
||||
|
||||
public void process(@Nonnull MathType.Result mathTypeResult) {
|
||||
if (canContinue(mathTypeResult)) {
|
||||
@Override
|
||||
public int process(@Nonnull SpannableStringBuilder sb, @Nonnull MathType.Result result) {
|
||||
process(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void process(@Nonnull MathType.Result result) {
|
||||
if (canContinue(result)) {
|
||||
// let's continue building number
|
||||
if (numberBuilder == null) {
|
||||
// if new number => create new builder
|
||||
numberBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
if (mathTypeResult.getMathType() != MathType.numeral_base) {
|
||||
if (result.type != MathType.numeral_base) {
|
||||
// just add matching string
|
||||
numberBuilder.append(mathTypeResult.getMatch());
|
||||
numberBuilder.append(result.match);
|
||||
} else {
|
||||
// set explicitly numeral base (do not include it into number)
|
||||
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
|
||||
nb = NumeralBase.getByPrefix(result.match);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -22,8 +22,9 @@
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -46,35 +47,28 @@ import jscl.text.Parser;
|
||||
* Date: 10/23/11
|
||||
* Time: 2:57 PM
|
||||
*/
|
||||
public class NumberBuilder extends AbstractNumberBuilder {
|
||||
public class NumberBuilder extends BaseNumberBuilder {
|
||||
|
||||
public NumberBuilder(@Nonnull CalculatorEngine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MathType.Result replaceNumberInText(@Nonnull StringBuilder text,
|
||||
@Nullable String number,
|
||||
int trimmedChars,
|
||||
@Nullable MutableObject<Integer> offset,
|
||||
@Nonnull NumeralBase nb,
|
||||
@Nonnull final MathEngine engine) {
|
||||
MathType.Result result = null;
|
||||
|
||||
if (number != null) {
|
||||
// in any case remove old number from text
|
||||
final int oldNumberLength = number.length() + trimmedChars;
|
||||
text.delete(text.length() - oldNumberLength, text.length());
|
||||
|
||||
final String newNumber = formatNumber(number, nb, engine);
|
||||
if (offset != null) {
|
||||
// register offset between old number and new number
|
||||
offset.setObject(newNumber.length() - oldNumberLength);
|
||||
}
|
||||
text.append(newNumber);
|
||||
private static int replaceNumberInText(@Nonnull SpannableStringBuilder sb,
|
||||
@Nullable String number,
|
||||
int trimmedChars,
|
||||
@Nonnull NumeralBase nb,
|
||||
@Nonnull final MathEngine engine) {
|
||||
if (number == null) {
|
||||
return 0;
|
||||
}
|
||||
// in any case remove old number from text
|
||||
final int oldNumberLength = number.length() + trimmedChars;
|
||||
sb.delete(sb.length() - oldNumberLength, sb.length());
|
||||
|
||||
return result;
|
||||
final String newNumber = formatNumber(number, nb, engine);
|
||||
sb.append(newNumber);
|
||||
// offset between old number and new number
|
||||
return newNumber.length() - oldNumberLength;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -138,47 +132,40 @@ public class NumberBuilder extends AbstractNumberBuilder {
|
||||
/**
|
||||
* Method replaces number in text according to some rules (e.g. formatting)
|
||||
*
|
||||
* @param text text where number can be replaced
|
||||
* @param mathTypeResult math type result of current token
|
||||
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
* @return new math type result (as one can be changed due to substituting of number with constant)
|
||||
* @param sb text where number can be replaced
|
||||
* @param result math type result of current token
|
||||
* @return offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
*/
|
||||
@Nonnull
|
||||
public MathType.Result process(@Nonnull StringBuilder text, @Nonnull MathType.Result mathTypeResult, @Nullable MutableObject<Integer> offset) {
|
||||
final MathType.Result possibleResult;
|
||||
if (canContinue(mathTypeResult)) {
|
||||
@Override
|
||||
public int process(@Nonnull SpannableStringBuilder sb, @Nonnull MathType.Result result) {
|
||||
if (canContinue(result)) {
|
||||
// let's continue building number
|
||||
if (numberBuilder == null) {
|
||||
// if new number => create new builder
|
||||
numberBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
if (mathTypeResult.getMathType() != MathType.numeral_base) {
|
||||
if (result.type != MathType.numeral_base) {
|
||||
// just add matching string
|
||||
numberBuilder.append(mathTypeResult.getMatch());
|
||||
numberBuilder.append(result.match);
|
||||
} else {
|
||||
// set explicitly numeral base (do not include it into number)
|
||||
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
|
||||
nb = NumeralBase.getByPrefix(result.match);
|
||||
}
|
||||
|
||||
possibleResult = null;
|
||||
return 0;
|
||||
} else {
|
||||
// process current number (and go to the next one)
|
||||
possibleResult = processNumber(text, offset);
|
||||
return processNumber(sb);
|
||||
}
|
||||
|
||||
return possibleResult == null ? mathTypeResult : possibleResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method replaces number in text according to some rules (e.g. formatting)
|
||||
*
|
||||
* @param text text where number can be replaced
|
||||
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
* @return new math type result (as one can be changed due to substituting of number with constant)
|
||||
* @param sb text where number can be replaced
|
||||
* @return offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
*/
|
||||
@Nullable
|
||||
public MathType.Result processNumber(@Nonnull StringBuilder text, @Nullable MutableObject<Integer> offset) {
|
||||
public int processNumber(@Nonnull SpannableStringBuilder sb) {
|
||||
// total number of trimmed chars
|
||||
int trimmedChars = 0;
|
||||
|
||||
@ -216,6 +203,6 @@ public class NumberBuilder extends AbstractNumberBuilder {
|
||||
nb = engine.getNumeralBase();
|
||||
}
|
||||
|
||||
return replaceNumberInText(text, number, trimmedChars, offset, localNb, engine.getMathEngine0());
|
||||
return replaceNumberInText(sb, number, trimmedChars, localNb, engine.getMathEngine0());
|
||||
}
|
||||
}
|
||||
|
@ -78,20 +78,20 @@ public class ToJsclTextProcessor implements TextProcessor<PreparedExpression, St
|
||||
|
||||
if (mathTypeBefore != null) {
|
||||
|
||||
final MathType current = mathTypeResult.getMathType();
|
||||
final MathType current = mathTypeResult.type;
|
||||
|
||||
if (current.isNeedMultiplicationSignBefore(mathTypeBefore.getMathType())) {
|
||||
if (current.isNeedMultiplicationSignBefore(mathTypeBefore.type)) {
|
||||
result.append("*");
|
||||
}
|
||||
}
|
||||
|
||||
if (mathTypeBefore != null &&
|
||||
(mathTypeBefore.getMathType() == MathType.function || mathTypeBefore.getMathType() == MathType.operator) &&
|
||||
Collections.find(MathType.openGroupSymbols, startsWithFinder) != null) {
|
||||
final String functionName = mathTypeBefore.getMatch();
|
||||
(mathTypeBefore.type == MathType.function || mathTypeBefore.type == MathType.operator) &&
|
||||
Collections.find(MathType.groupSymbols, startsWithFinder) != null) {
|
||||
final String functionName = mathTypeBefore.match;
|
||||
final Function function = Locator.getInstance().getEngine().getFunctionsRegistry().get(functionName);
|
||||
if (function == null || function.getMinParameters() > 0) {
|
||||
throw new CalculatorParseException(i, s, new CalculatorMessage(CalculatorMessages.msg_005, MessageType.error, mathTypeBefore.getMatch()));
|
||||
throw new CalculatorParseException(i, s, new CalculatorMessage(CalculatorMessages.msg_005, MessageType.error, mathTypeBefore.match));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ public enum MathType {
|
||||
}
|
||||
},
|
||||
|
||||
open_group_symbol(800, true, false, MathGroupType.other, "[", "(", "{") {
|
||||
open_group_symbol(800, true, false, MathGroupType.other, "(", "[", "{") {
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
|
||||
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != function && mathTypeBefore != operator;
|
||||
@ -141,7 +141,7 @@ public enum MathType {
|
||||
}
|
||||
},
|
||||
|
||||
close_group_symbol(900, false, true, MathGroupType.other, "]", ")", "}") {
|
||||
close_group_symbol(900, false, true, MathGroupType.other, ")", "]", "}") {
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
|
||||
return false;
|
||||
@ -223,68 +223,10 @@ public enum MathType {
|
||||
}
|
||||
};
|
||||
|
||||
public static final List<String> openGroupSymbols = Arrays.asList("[]", "()", "{}");
|
||||
public static final List<String> groupSymbols = Arrays.asList("()", "[]", "{}");
|
||||
public final static Character POWER_10 = 'E';
|
||||
public static final String IMAGINARY_NUMBER = "i";
|
||||
public static final String IMAGINARY_NUMBER_JSCL = "√(-1)";
|
||||
public static final String PI = "π";
|
||||
public static final String E = "e";
|
||||
public static final String C = "c";
|
||||
public static final Double C_VALUE = 299792458d;
|
||||
public static final String G = "G";
|
||||
|
||||
/* public static int getPostfixFunctionStart(@Nonnull CharSequence s, int position) throws ParseException {
|
||||
assert s.length() > position;
|
||||
|
||||
int numberOfOpenGroups = 0;
|
||||
int result = position;
|
||||
for (; result >= 0; result--) {
|
||||
|
||||
final MathType mathType = getType(s.toString(), result).getMathType();
|
||||
|
||||
if (Collections.contains(mathType, digit, dot, grouping_separator, power_10)) {
|
||||
// continue
|
||||
} else if (mathType == close_group_symbol) {
|
||||
numberOfOpenGroups++;
|
||||
} else if (mathType == open_group_symbol) {
|
||||
if (numberOfOpenGroups > 0) {
|
||||
numberOfOpenGroups--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (stop(s, numberOfOpenGroups, result)) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfOpenGroups != 0){
|
||||
throw new ParseException("Could not find start of prefix function!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean stop(CharSequence s, int numberOfOpenGroups, int i) {
|
||||
if (numberOfOpenGroups == 0) {
|
||||
if (i > 0) {
|
||||
final EndsWithFinder endsWithFinder = new EndsWithFinder(s);
|
||||
endsWithFinder.setI(i + 1);
|
||||
if (!Collections.contains(function.getTokens(), FilterType.included, endsWithFinder)) {
|
||||
MathType type = getType(s.toString(), i).getMathType();
|
||||
if (type != constant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
public static final Double G_VALUE = 6.6738480E-11;
|
||||
public static final String H_REDUCED = "h";
|
||||
public static final Double H_REDUCED_VALUE = 6.6260695729E-34 / (2 * Math.PI);
|
||||
public final static String NAN = "NaN";
|
||||
public final static String INFINITY = "∞";
|
||||
public final static String INFINITY_JSCL = "Infinity";
|
||||
@ -425,6 +367,14 @@ public enum MathType {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isOpenGroupSymbol(char c) {
|
||||
return c == '(' || c == '[' || c == '{';
|
||||
}
|
||||
|
||||
public static boolean isCloseGroupSymbol(char c) {
|
||||
return c == ')' || c == ']' || c == '}';
|
||||
}
|
||||
|
||||
public static enum MathGroupType {
|
||||
function,
|
||||
number,
|
||||
@ -435,33 +385,23 @@ public enum MathType {
|
||||
public static class Result {
|
||||
|
||||
@Nonnull
|
||||
private final MathType mathType;
|
||||
public final MathType type;
|
||||
|
||||
@Nonnull
|
||||
private final String match;
|
||||
public final String match;
|
||||
|
||||
public Result(@Nonnull MathType mathType, @Nonnull String match) {
|
||||
this.mathType = mathType;
|
||||
public Result(@Nonnull MathType type, @Nonnull String match) {
|
||||
this.type = type;
|
||||
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
public int processToJscl(@Nonnull StringBuilder result, int i) throws CalculatorParseException {
|
||||
return mathType.processToJscl(result, i, match);
|
||||
return type.processToJscl(result, i, match);
|
||||
}
|
||||
|
||||
public int processFromJscl(@Nonnull StringBuilder result, int i) {
|
||||
return mathType.processFromJscl(result, i, match);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getMatch() {
|
||||
return match;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MathType getMathType() {
|
||||
return mathType;
|
||||
return type.processFromJscl(result, i, match);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class VarEditorSaver<T extends MathEntity> implements View.OnClickListene
|
||||
if (canBeSaved) {
|
||||
final MathType.Result mathType = MathType.getType(name, 0, false);
|
||||
|
||||
if (mathType.getMathType() == MathType.text || mathType.getMathType() == MathType.constant) {
|
||||
if (mathType.type == MathType.text || mathType.type == MathType.constant) {
|
||||
|
||||
if (Strings.isEmpty(value)) {
|
||||
// value is empty => undefined variable
|
||||
|
@ -81,14 +81,14 @@ public class FromJsclSimplifyTextProcessor implements TextProcessor<String, Gene
|
||||
mathTypeAfter = null;
|
||||
}
|
||||
|
||||
if (needMultiplicationSign(mathTypeBefore == null ? null : mathTypeBefore.getMathType(), mathTypeAfter == null ? null : mathTypeAfter.getMathType())) {
|
||||
if (needMultiplicationSign(mathTypeBefore == null ? null : mathTypeBefore.type, mathTypeAfter == null ? null : mathTypeAfter.type)) {
|
||||
sb.append(Locator.getInstance().getEngine().getMultiplicationSign());
|
||||
}
|
||||
|
||||
} else {
|
||||
if (mathType.getMathType() == MathType.constant || mathType.getMathType() == MathType.function || mathType.getMathType() == MathType.operator) {
|
||||
sb.append(mathType.getMatch());
|
||||
i += mathType.getMatch().length() - 1;
|
||||
if (mathType.type == MathType.constant || mathType.type == MathType.function || mathType.type == MathType.operator) {
|
||||
sb.append(mathType.match);
|
||||
i += mathType.match.length() - 1;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
|
||||
import org.solovyev.android.calculator.App;
|
||||
@ -50,7 +49,7 @@ public final class EditorTextProcessor implements TextProcessor<TextProcessorEdi
|
||||
try {
|
||||
final TextProcessorEditorResult processesText = getTextHighlighter().process(text);
|
||||
|
||||
result = new TextProcessorEditorResult(Html.fromHtml(processesText.toString()), processesText.getOffset());
|
||||
result = new TextProcessorEditorResult(processesText.getCharSequence(), processesText.getOffset());
|
||||
} catch (CalculatorParseException e) {
|
||||
// set raw text
|
||||
result = new TextProcessorEditorResult(text, 0);
|
||||
|
@ -22,15 +22,28 @@
|
||||
|
||||
package org.solovyev.android.calculator.view;
|
||||
|
||||
import org.solovyev.android.calculator.*;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.solovyev.android.Check;
|
||||
import org.solovyev.android.calculator.BaseNumberBuilder;
|
||||
import org.solovyev.android.calculator.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.LiteNumberBuilder;
|
||||
import org.solovyev.android.calculator.Locator;
|
||||
import org.solovyev.android.calculator.NumberBuilder;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.android.calculator.text.TextProcessorEditorResult;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
@ -80,148 +93,128 @@ public class TextHighlighter implements TextProcessor<TextProcessorEditorResult,
|
||||
@Nonnull
|
||||
@Override
|
||||
public TextProcessorEditorResult process(@Nonnull String text) throws CalculatorParseException {
|
||||
final CharSequence result;
|
||||
final CalculatorEngine engine = Locator.getInstance().getEngine();
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
final BaseNumberBuilder nb = !formatNumber ? new LiteNumberBuilder(engine) : new NumberBuilder(engine);
|
||||
|
||||
int maxNumberOfOpenGroupSymbols = 0;
|
||||
int numberOfOpenGroupSymbols = 0;
|
||||
int offset = 0;
|
||||
int groupsCount = 0;
|
||||
int openGroupsCount = 0;
|
||||
|
||||
final StringBuilder text1 = new StringBuilder(5 * text.length());
|
||||
|
||||
int resultOffset = 0;
|
||||
|
||||
final AbstractNumberBuilder numberBuilder;
|
||||
if (!formatNumber) {
|
||||
numberBuilder = new LiteNumberBuilder(Locator.getInstance().getEngine());
|
||||
} else {
|
||||
numberBuilder = new NumberBuilder(Locator.getInstance().getEngine());
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
MathType.Result mathType = MathType.getType(text, i, numberBuilder.isHexMode());
|
||||
final MathType.Result result = MathType.getType(text, i, nb.isHexMode());
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<>(0);
|
||||
((NumberBuilder) numberBuilder).process(text1, mathType, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
} else {
|
||||
((LiteNumberBuilder) numberBuilder).process(mathType);
|
||||
}
|
||||
offset += nb.process(sb, result);
|
||||
|
||||
final String match = mathType.getMatch();
|
||||
switch (mathType.getMathType()) {
|
||||
final String match = result.match;
|
||||
switch (result.type) {
|
||||
case open_group_symbol:
|
||||
numberOfOpenGroupSymbols++;
|
||||
maxNumberOfOpenGroupSymbols = Math.max(maxNumberOfOpenGroupSymbols, numberOfOpenGroupSymbols);
|
||||
text1.append(text.charAt(i));
|
||||
openGroupsCount++;
|
||||
groupsCount = Math.max(groupsCount, openGroupsCount);
|
||||
sb.append(text.charAt(i));
|
||||
break;
|
||||
case close_group_symbol:
|
||||
numberOfOpenGroupSymbols--;
|
||||
text1.append(text.charAt(i));
|
||||
openGroupsCount--;
|
||||
sb.append(text.charAt(i));
|
||||
break;
|
||||
case operator:
|
||||
text1.append(match);
|
||||
if (match.length() > 1) {
|
||||
i += match.length() - 1;
|
||||
}
|
||||
i += append(sb, match);
|
||||
break;
|
||||
case function:
|
||||
i = processHighlightedText(text1, i, match, "i", null);
|
||||
i += append(sb, match);
|
||||
makeItalic(sb, i + 1 - match.length(), i + 1);
|
||||
break;
|
||||
case constant:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
case numeral_base:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
i += append(sb, match);
|
||||
makeBold(sb, i + 1 - match.length(), i + 1);
|
||||
break;
|
||||
default:
|
||||
if (mathType.getMathType() == MathType.text || match.length() <= 1) {
|
||||
text1.append(text.charAt(i));
|
||||
if (result.type == MathType.text || match.length() <= 1) {
|
||||
sb.append(text.charAt(i));
|
||||
} else {
|
||||
text1.append(match);
|
||||
i += match.length() - 1;
|
||||
i += append(sb, match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).processNumber(text1, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
if (nb instanceof NumberBuilder) {
|
||||
offset += ((NumberBuilder) nb).processNumber(sb);
|
||||
}
|
||||
|
||||
if (maxNumberOfOpenGroupSymbols > 0) {
|
||||
|
||||
final StringBuilder text2 = new StringBuilder(text1.length());
|
||||
|
||||
int i = processBracketGroup(text2, text1, 0, 0, maxNumberOfOpenGroupSymbols);
|
||||
for (; i < text1.length(); i++) {
|
||||
text2.append(text1.charAt(i));
|
||||
}
|
||||
|
||||
result = text2.toString();
|
||||
} else {
|
||||
result = text1.toString();
|
||||
if (groupsCount == 0) {
|
||||
return new TextProcessorEditorResult(sb, offset);
|
||||
}
|
||||
|
||||
return new TextProcessorEditorResult(result, resultOffset);
|
||||
final List<GroupSpan> groupSpans = new ArrayList<>(groupsCount);
|
||||
fillGroupSpans(sb, 0, 0, groupsCount, groupSpans);
|
||||
for (GroupSpan groupSpan : Lists.reverse(groupSpans)) {
|
||||
makeColor(sb, groupSpan.start, groupSpan.end, getColor(groupSpan.group, groupsCount));
|
||||
}
|
||||
return new TextProcessorEditorResult(sb, offset);
|
||||
}
|
||||
|
||||
private int processHighlightedText(@Nonnull StringBuilder result, int i, @Nonnull String match, @Nonnull String tag, @Nullable Map<String, String> tagAttributes) {
|
||||
result.append("<").append(tag);
|
||||
|
||||
if (tagAttributes != null && !tagAttributes.entrySet().isEmpty()) {
|
||||
for (Map.Entry<String, String> entry : tagAttributes.entrySet()) {
|
||||
// attr1="attr1_value" attr2="attr2_value"
|
||||
result.append(" ").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(">").append(match).append("</").append(tag).append(">");
|
||||
private int append(SpannableStringBuilder t, String match) {
|
||||
t.append(match);
|
||||
if (match.length() > 1) {
|
||||
return i + match.length() - 1;
|
||||
} else {
|
||||
return i;
|
||||
return match.length() - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int processBracketGroup(@Nonnull StringBuilder result, @Nonnull CharSequence s, int i, int numberOfOpenings, int maxNumberOfGroups) {
|
||||
private static void makeItalic(@Nonnull SpannableStringBuilder t, int start, int end) {
|
||||
t.setSpan(new StyleSpan(Typeface.ITALIC), start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
private static void makeBold(@Nonnull SpannableStringBuilder t, int start, int end) {
|
||||
t.setSpan(new StyleSpan(Typeface.BOLD), start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
for (; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
String strCh = String.valueOf(ch);
|
||||
private static void makeColor(@Nonnull SpannableStringBuilder t, int start, int end, int color) {
|
||||
t.setSpan(new ForegroundColorSpan(color), start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
if (MathType.open_group_symbol.getTokens().contains(strCh)) {
|
||||
result.append(ch);
|
||||
result.append("</font>");
|
||||
i = processBracketGroup(result, s, i + 1, numberOfOpenings + 1, maxNumberOfGroups);
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
if (i < s.length() && MathType.close_group_symbol.getTokens().contains(String.valueOf(s.charAt(i)))) {
|
||||
result.append(s.charAt(i));
|
||||
}
|
||||
} else if (MathType.close_group_symbol.getTokens().contains(strCh)) {
|
||||
break;
|
||||
} else {
|
||||
result.append(ch);
|
||||
private int fillGroupSpans(@Nonnull SpannableStringBuilder sb, int start, int group, int groupsCount, @Nonnull List<GroupSpan> spans) {
|
||||
for (int i = start; i < sb.length(); i++) {
|
||||
final char c = sb.charAt(i);
|
||||
if (MathType.isOpenGroupSymbol(c)) {
|
||||
i = highlightGroup(sb, i, group + 1, groupsCount, spans);
|
||||
} else if (MathType.isCloseGroupSymbol(c)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
result.append("</font>");
|
||||
|
||||
|
||||
return i;
|
||||
return sb.length();
|
||||
}
|
||||
|
||||
private String getColor(int totalNumberOfOpenings, int numberOfOpenings) {
|
||||
int offset = ((int) (255 * 0.8)) * numberOfOpenings / (totalNumberOfOpenings + 1);
|
||||
private int highlightGroup(SpannableStringBuilder sb, int start, int group, int groupsCount, @Nonnull List<GroupSpan> spans) {
|
||||
final int end = fillGroupSpans(sb, start + 1, group, groupsCount, spans);
|
||||
if (start + 1 < end) {
|
||||
spans.add(new GroupSpan(start + 1, end, group));
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
private int getColor(int group, int groupsCount) {
|
||||
int offset = ((int) (255 * 0.8)) * group / (groupsCount + 1);
|
||||
if (!dark) {
|
||||
offset = -offset;
|
||||
}
|
||||
|
||||
// for tests:
|
||||
// int result = Color.rgb(BASE_COLOUR_RED_COMPONENT - offset, BASE_COLOUR_GREEN_COMPONENT - offset, BASE_COLOUR_BLUE_COMPONENT - offset);
|
||||
int result = (0xFF << 24) | ((red + offset) << 16) | ((green + offset) << 8) | (blue + offset);
|
||||
return (0xFF << 24) | ((red + offset) << 16) | ((green + offset) << 8) | (blue + offset);
|
||||
}
|
||||
|
||||
return "#" + App.toColorString(result);
|
||||
private static class GroupSpan {
|
||||
final int start;
|
||||
final int end;
|
||||
final int group;
|
||||
|
||||
private GroupSpan(int start, int end, int group) {
|
||||
Check.isTrue(start < end);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,29 +45,29 @@ public class MathTypeTest extends AbstractCalculatorTest {
|
||||
|
||||
@Test
|
||||
public void testGetType() throws Exception {
|
||||
assertEquals(MathType.function, MathType.getType("sin", 0, false).getMathType());
|
||||
assertEquals(MathType.text, MathType.getType("sn", 0, false).getMathType());
|
||||
assertEquals(MathType.text, MathType.getType("s", 0, false).getMathType());
|
||||
assertEquals(MathType.text, MathType.getType("", 0, false).getMathType());
|
||||
assertEquals(MathType.function, MathType.getType("sin", 0, false).type);
|
||||
assertEquals(MathType.text, MathType.getType("sn", 0, false).type);
|
||||
assertEquals(MathType.text, MathType.getType("s", 0, false).type);
|
||||
assertEquals(MathType.text, MathType.getType("", 0, false).type);
|
||||
|
||||
try {
|
||||
assertEquals(MathType.text, MathType.getType("22", -1, false).getMathType());
|
||||
assertEquals(MathType.text, MathType.getType("22", -1, false).type);
|
||||
Assert.fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
assertEquals(MathType.text, MathType.getType("22", 2, false).getMathType());
|
||||
assertEquals(MathType.text, MathType.getType("22", 2, false).type);
|
||||
Assert.fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
assertEquals("atanh", MathType.getType("atanh", 0, false).getMatch());
|
||||
assertEquals("atanh", MathType.getType("atanh", 0, false).match);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostfixFunctionsProcessing() throws Exception {
|
||||
assertEquals(postfix_function, MathType.getType("5!", 1, false).getMathType());
|
||||
assertEquals(postfix_function, MathType.getType("!", 0, false).getMathType());
|
||||
assertEquals(postfix_function, MathType.getType("5!", 1, false).type);
|
||||
assertEquals(postfix_function, MathType.getType("!", 0, false).type);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user