Beispiel #1
0
    public void BasicTest(string input, string[] output)
    {
        var customBreaker = new CustomBreaker();

        customBreaker.BreakWords(input, false);
        var outputList = new List <int> {
            0
        };

        customBreaker.LoadBreakAtList(outputList);
        for (int i = 0; i < outputList.Count - 1; i++)
        {
            Assert.AreEqual
            (
                output[i],
                input.Substring(outputList[i], outputList[i + 1] - outputList[i])
            );
        }
    }
Beispiel #2
0
        public void DoBreak(char[] inputBuffer, int startIndex, int len, List <int> breakAtList)
        {
            myTextBreaker.BreakWords(inputBuffer, startIndex, len);

            myTextBreaker.LoadBreakAtList(breakAtList);
        }
Beispiel #3
0
      /* //Paste this into the C# Interactive, fill <username> yourself
#r "C:/Users/<username>/source/repos/CSharpMath/Typography/Build/NetStandard/Typography.TextBreak/bin/Debug/netstandard1.3/Typography.TextBreak.dll"
using Typography.TextBreak;
(int, WordKind, char)[] BreakText(string text) {
  var breaker = new CustomBreaker();
  var breakList = new List<BreakAtInfo>();
  breaker.BreakWords(text);
  breaker.LoadBreakAtList(breakList);
  //index is after the boundary -> last one will be out of range
  return breakList.Select(i => (i.breakAt, i.wordKind, text.ElementAtOrDefault(i.breakAt))).ToArray();
}
BreakText(@"Here are some text $1 + 12 \frac23 \sqrt4$ $$Display$$ text")
       */
      /* //Version 2
#r "C:/Users/<username>/source/repos/CSharpMath/Typography/Build/NetStandard/Typography.TextBreak/bin/Debug/netstandard1.3/Typography.TextBreak.dll"
using Typography.TextBreak;
string BreakText(string text, string seperator = "|")
{
    var breaker = new CustomBreaker();
    var breakList = new List<BreakAtInfo>();
    breaker.BreakWords(text);
    breaker.LoadBreakAtList(breakList);
    //reverse to ensure earlier inserts do not affect later ones
    foreach (var @break in breakList.Select(i => i.breakAt).Reverse())
        text = text.Insert(@break, seperator);
    return text;
}
BreakText(@"Here are some text $1 + 12 \frac23 \sqrt4$ $$Display$$ text")
       */
    public static Result<TextAtom> Build(string text, bool enhancedColors) {
      if (string.IsNullOrEmpty(text)) return new TextAtom.List(Array.Empty<TextAtom>(), 0);
      bool? displayMath = null;
      StringBuilder mathLaTeX = null;
      bool backslashEscape = false;
      bool afterCommand = false; //ignore spaces after command
      int dollarCount = 0;
      var atoms = new TextAtomListBuilder();
      var breaker = new CustomBreaker();
      var breakList = new List<BreakAtInfo>();
      breaker.BreakWords(text, false);
      breaker.LoadBreakAtList(breakList);
      Result CheckDollarCount() {
        switch (dollarCount) {
          case 0:
            break;
          case 1:
            dollarCount = 0;
            switch (displayMath) {
              case true:
                return "Cannot close display math mode with $";
              case false:
                if (atoms.Add(mathLaTeX.ToString(), false).Error is string mathError)
                  return "[Math mode error] " + mathError;
                mathLaTeX = null;
                displayMath = null;
                break;
              case null:
                mathLaTeX = new StringBuilder();
                displayMath = false;
                break;
            }
            break;
          case 2:
            dollarCount = 0;
            switch (displayMath) {
              case true:
                if (atoms.Add(mathLaTeX.ToString(), true).Error is string mathError)
                  return "[Math mode error] " + mathError;
                mathLaTeX = null;
                displayMath = null;
                break;
              case false:
                return "Cannot close inline math mode with $$";
              case null:
                mathLaTeX = new StringBuilder();
                displayMath = true;
                break;
            }
            break;
          default:
            return "Invalid number of $: " + dollarCount;
        }
        return Ok();
      }
      (int startAt, int endAt, char endingChar, WordKind wordKind) ObtainRange(int i) =>
        (i == 0 ? 0 : breakList[i - 1].breakAt, breakList[i].breakAt, text[breakList[i].breakAt - 1], breakList[i].wordKind);
      for (var i = 0; i < breakList.Count; i++) {
        var (startAt, endAt, endingChar, wordKind) = ObtainRange(i);
        bool SetNextRange() {
          bool success = ++i < breakList.Count;
          if(success) (startAt, endAt, endingChar, wordKind) = ObtainRange(i);
          return success;
        }
        Result<string> ReadArgument() {
          afterCommand = false;
          if (!SetNextRange()) return Err("Missing argument");
          if (endingChar != '{') {
            var toReturn = text[startAt].ToString();
#warning Not one char only, should skip spaces then read next char, and it is a possible command
            //range contains one char only
            if (startAt == endAt)
              _ = SetNextRange(); //reaching the end does not affect validity of argument
            else
              startAt += 1;
            return Ok(toReturn);
          }
          int endingIndex = -1;
          //startAt + 1 to not start at the { we started at
          for (int j = startAt + 1, bracketDepth = 0; j < text.Length; j++) {
            if (text[j] == '{') bracketDepth++;
            else if (text[j] == '}')
              if (bracketDepth > 0) bracketDepth--;
              else { endingIndex = j; break; }
          }
          if (endingIndex == -1) return Err("Missing }");
          var resultText = text.Substring(endAt, endingIndex - endAt);
          while (startAt < endingIndex)
            _ = SetNextRange(); //this never fails because the above check
          return Ok(resultText);
        }
        atoms.TextLength = startAt;
        if (endingChar == '$') {
          if (backslashEscape)
            if (displayMath != null) mathLaTeX.Append(@"\$");
            else atoms.Add("$");
          else {
            dollarCount++;
            continue;
          }
          backslashEscape = false;
        } else {
          { if (CheckDollarCount().Error is string error) return error; }

          //Normal unescaped text section, could be in display/inline math mode
          if (!backslashEscape) {
            var textSection = text.Substring(startAt, endAt - startAt);
            switch (endingChar) {
              case '$':
                throw new InvalidCodePathException("The $ case should have been accounted for.");
              case '\\':
                backslashEscape = true;
                continue;
              case var sp when wordKind == WordKind.Whitespace || wordKind == WordKind.NewLine:
                //Collpase spaces
                //Consume newlines after commands
                if (displayMath == null)
                  if (afterCommand) continue;
                  else atoms.Add();
                else mathLaTeX.Append(textSection);
                break;
              case var punc when displayMath == null && wordKind == WordKind.Punc && atoms.Last is TextAtom.Text t:
                //Append punctuation to text
                t.Append(textSection);
                break;
              default: //Just ordinary text
                if (displayMath == null) atoms.Add(textSection);
                else mathLaTeX.Append(textSection);
                break;
            }
            afterCommand = false;
            continue;
          }

          //Escaped text section but in inline/display math mode
          if (displayMath != null) {
            switch (endingChar) {
              case '$':
                throw new InvalidCodePathException("The $ case should have been accounted for.");
              case '(':
                switch (displayMath) {
                  case true:
                    return "Cannot open inline math mode in display math mode";
                  case false:
                    return "Cannot open inline math mode in inline math mode";
                  default:
                    throw new InvalidCodePathException("displayMath is null. This switch should not be hit.");
                }
              case ')':
                switch (displayMath) {
                  case true:
                    return "Cannot close inline math mode in display math mode";
                  case false:
                    if (atoms.Add(mathLaTeX.ToString(), false).Error is string mathError)
                      return "[Math mode error] " + mathError;
                    mathLaTeX = null;
                    displayMath = null;
                    break;
                  default:
                    throw new InvalidCodePathException("displayMath is null. This switch should not be hit.");
                }
                break;
              case '[':
                switch (displayMath) {
                  case true:
                    return "Cannot open display math mode in display math mode";
                  case false:
                    return "Cannot open display math mode in inline math mode";
                  default:
                    throw new InvalidCodePathException("displayMath is null. This switch should not be hit.");
                }
              case ']':
                switch (displayMath) {
                  case true:
                    if (atoms.Add(mathLaTeX.ToString(), true).Error is string mathError)
                      return "[Math mode error] " + mathError;
                    mathLaTeX = null;
                    displayMath = null;
                    break;
                  case false:
                    return "Cannot close display math mode in inline math mode";
                  default:
                    throw new InvalidCodePathException("displayMath is null. This switch should not be hit.");
                }
                break;
              default:
                mathLaTeX.Append($@"\{text.Substring(startAt, endAt - startAt)}");
                break;
            }
            backslashEscape = false;
            continue;
          }

          //Escaped text section and not in inline/display math mode
          afterCommand = true;
          switch (text.Substring(startAt, endAt - startAt)) {
            case "(":
              mathLaTeX = new StringBuilder();
              displayMath = false;
              break;
            case ")":
              return "Cannot close inline math mode outside of math mode";
            case "[":
              mathLaTeX = new StringBuilder();
              displayMath = true;
              break;
            case "]":
              return "Cannot close display math mode outside of math mode";
            case @"\":
              atoms.Break(1);
              break;
            case ",":
              atoms.Add(Space.ShortSpace, 1);
              break;
            case var _ when wordKind == WordKind.Whitespace: //control space
              atoms.Add();
              break;
            case "backslash":
              atoms.Add(@"\");
              break;
            case "par":
              atoms.Break(3);
#warning Should the newline and space occupy the same range?
              atoms.TextLength -= 3;
              atoms.Add(Space.ParagraphIndent, 3);
              break;
            case "fontsize": {
                if (ReadArgument().Bind(fontSize =>
                    float.TryParse(fontSize, System.Globalization.NumberStyles.AllowDecimalPoint |
                                             System.Globalization.NumberStyles.AllowLeadingWhite |
                                             System.Globalization.NumberStyles.AllowTrailingWhite,
                                             System.Globalization.CultureInfo.InvariantCulture,
                                             out var parsedResult) ?
                    Ok(parsedResult) :
                    Err("Invalid font size")
                  ).Bind(
                    ReadArgument().Bind(resizedContent => Build(resizedContent, enhancedColors)),
                    (fontSize, resizedContent) =>
                      atoms.Add(resizedContent, fontSize, "fontsize".Length)
                  ).Error is string error
                ) return error;
                break;
              }
            case "color": {
                if (ReadArgument().Bind(color =>
                    Color.Create(color, enhancedColors) is Color value ?
                    Ok(value) :
                    Err("Invalid color")
                  ).Bind(
                    ReadArgument().Bind(coloredContent => Build(coloredContent, enhancedColors)),
                    (color, coloredContent) =>
                      atoms.Add(coloredContent, color, "color".Length)
                  ).Error is string error
                ) return error;
                break;
              }
            //case "red", "yellow", ...
            case var shortColor when enhancedColors && Color.PredefinedColors.Contains(shortColor): {
                if (Ok(Color.Create(shortColor, enhancedColors) ??
                      throw new InvalidCodePathException(
                        "This case's condition should have checked the validity of shortColor.")
                  ).Bind(
                    ReadArgument().Bind(coloredContent => Build(coloredContent, enhancedColors)),
                    (color, coloredContent) =>
                      atoms.Add(coloredContent, color, shortColor.Length)
                  ).Error is string error
                ) return error;
                break;
              }
            //case "textbf", "textit", ...
            case var command when !command.Contains("math") && FontStyleExtensions.FontStyles.TryGetByFirst(command.Replace("text", "math"), out var fontStyle): {
                if (ReadArgument()
                  .Bind(content => Build(content, enhancedColors))
                  .Bind(builtContent => atoms.Add(builtContent, fontStyle, command.Length))
                  .Error is string error)
                  return error;
                break;
              }
            case var command:
              if (displayMath != null) mathLaTeX.Append(command); //don't eat the command when parsing math
              else return @"Unknown command \" + command;
              break;
          }
          backslashEscape = false;
        }
      }
      { if (CheckDollarCount().Error is string error) return error; }
      if (backslashEscape) return @"Unknown command \";
      if (displayMath != null) return "Math mode was not terminated";
      return atoms.Build();
    }