    return atom;
  }

  auto atom = convertCharacter(ch, true);
  _pos++;
  return atom;
}

pair<UnitType, float> TeXParser::getLength() {
  if (_pos == _len) return make_pair(UnitType::none, -1.f);

  wchar_t ch = L'\0';

  skipWhiteSpace();
  const int start = _pos;
  while (_pos < _len && ch!='' && ch!= ESCAPE) {
    ch = _latex[_pos++];
  }
  const int end = _pos;
  if (ch == '\\') _pos--;
  else skipWhiteSpace();

  return SpaceAtom::getLength(_latex.substr(start, end - start - 1));
}

bool TeXParser::replaceScript() {
  wchar_t ch = _latex[_pos];
  auto it = SUP_SCRIPT_MAP.find(ch);
  if (it!= SUP_SCRIPT_MAP.end()) {
    wstring sup = wstring(L"\\mathcumsup{").append(1, (wchar_t) (it->second)).append(L"}");
    _latex.replace(_pos, 1, sup);
    _len = _latex.length();
    _pos += sup.size();
    return true;
  }
  it = SUB_SCRIPT_MAP.find(ch);
  if (it!= SUB_SCRIPT_MAP.end()) {
    wstring sub = wstring(L"\\mathcumsub{").append(1, (wchar_t) (it->second)).append(L"}");
    _latex.replace(_pos, 1, sub);
    _len = _latex.length();
    _pos += sub.size();
    return true;
  }
  return false;
}

void TeXParser::preprocess(wstring& cmd, Args& args, int& pos) {
  if (cmd == L"newcommand" || cmd == L"renewcommand") {
    preprocessNewCmd(cmd, args, pos);
  } else if (cmd == L"newenvironment" || cmd == L"renewenvironment") {
    preprocessNewCmd(cmd, args, pos);
  } else if (NewCommandMacro::isMacro(cmd)) {
    inflateNewCmd(cmd, args, pos);
  } else if (cmd == L"begin") {
    inflateEnv(cmd, args, pos);
  } else if (cmd == L"makeatletter") {
    _atIsLetter++;
  } else if (cmd == L"makeatother") {
    _atIsLetter--;
  } else if (_unparsedContents.find(cmd)!= _unparsedContents.end()) {
    getOptsArgs(1, 0, args);
  }
}

void TeXParser::preprocessNewCmd(wstring& cmd, Args& args, int& pos) {
  // The macro must exists
  auto mac = MacroInfo::get(cmd);
  getOptsArgs(mac->_argc, mac->_posOpts, args);
  mac->invoke(*this, args);
  _latex.erase(pos, _pos - pos);
  _len = _latex.length();
  _pos = pos;
}

void TeXParser::inflateNewCmd(wstring& cmd, Args& args, int& pos) {
  // The macro must exists
  auto mac = MacroInfo::get(cmd);
  getOptsArgs(mac->_argc, mac->_posOpts, args);
  args[0] = cmd;
  try {
    mac->invoke(*this, args);
    // The last element is the returned value (after inflated macro)
    _latex.replace(pos, _pos - pos, args.back());
  } catch (ex_parse& e) {
    if (!_isPartial) throw;
    pos += cmd.length() + 1;
  }
  _len = _latex.length();
  _pos = pos;
}

void TeXParser::inflateEnv(wstring& cmd, Args& args, int& pos) {
  getOptsArgs(1, 0, args);
  wstring env = args[1] + L"@env";
  auto mac = MacroInfo::get(env);
  if (mac == nullptr) {
    throw ex_parse(
      "Unknown environment: "
      + wide2utf8(args[1])
      + " at position " + tostring(getLine())
      + ":" + tostring(getCol())
    );
  }
  vector<wstring> optargs;
  getOptsArgs(mac->_argc - 1, 0, optargs);
  wstring grp = getGroup(L"\\begin{" + args[1] + L"}", L"\\end{" + args[1] + L"}");
  wstring expr = L"{\\makeatletter \\" + args[1] + L"@env";
  for (int i = 1; i <= mac->_argc - 1; i++) expr += L"{" + optargs[i] + L"}";
  expr += L"{" + grp + L"}\\makeatother}";
  _latex.replace(pos, _pos - pos, expr);
  _len = _latex.length();
  _pos = pos;
}

void TeXParser::preprocess() {
  if (_len == 0) return;

  wchar_t ch;
  int spos;
  vector<wstring> args;
  while (_pos < _len) {
    if (replaceScript()) continue;

    ch = _latex[_pos];
    switch (ch) {
      case ESCAPE: {
        spos = _pos;
        wstring cmd = getCommand();
        try {
          preprocess(cmd, args, spos);
        } catch (ex_parse& e) {
          if (!_isPartial) throw;
        }
        args.clear();
        break;
      }
      case PERCENT: {
        spos = _pos++;
        wchar_t chr;
        while (_pos < _len) {
          chr = _latex[_pos++];
          if (chr == '\r' || chr == '\n') break;
        }
        if (_pos < _len) _pos--;
        _latex.replace(spos, _pos - spos, L"");
        _len = _latex.length();
        _pos = spos;
        break;
      }
      case DEGRE: {
        _latex.replace(_pos, 1, L"^{\\circ}");
        _len = _latex.length();
        _pos++;
        break;
      }
      default:
        _pos++;
        break;
    }
  }
  _pos = 0;
  _len = _latex.length();
}

void TeXParser::parse() {
  if (_len == 0) {
    if (_formula->_root == nullptr &&!_arrayMode)
      _formula->add(sptrOf<EmptyAtom>());
    return;
  }

  wchar_t ch;
  while (_pos < _len) {
    ch = _latex[_pos];

    switch (ch) {
      case '\n':
        _line++;
        _col = _pos;
      case '\t':
      case '\r':
        _pos++;
        break;
      case'': {
        _pos++;
        if (!_isMathMode) {  // we are in mbox
          _formula->add(sptrOf<SpaceAtom>());
          _formula->add(sptrOf<BreakMarkAtom>());
          while (_pos < _len) {
            ch = _latex[_pos];
            if (ch!='' || ch!= '\t' || ch!= '\r') break;
            _pos++;
          }
        }
      }
        break;
      case DOLLAR: {
        _pos++;
        if (!_isMathMode) {  // we are in mbox
          TexStyle style = TexStyle::text;
          bool doubleDollar = false;
          if (_latex[_pos] == DOLLAR) {
            style = TexStyle::display;
            doubleDollar = true;
            _pos++;
          }

          auto atom = sptrOf<MathAtom>(
            Formula(*this, getDollarGroup(DOLLAR), false)._root,
            style
          );
          _formula->add(atom