#include "atom/atom_row.h" #include "atom/atom_basic.h" #include "atom/atom_char.h" #include "atom/atom_space.h" #include "atom/atom_text.h" #include "box/box_group.h" #include "box/box_single.h" #include "core/glue.h" #include "env/env.h" #include "utils/utf.h" using namespace std; using namespace microtex; bool AtomDecor::isChar() const { return _atom->isChar(); } bool AtomDecor::isMathMode() const { if (!isChar()) return false; return static_cast(_atom.get())->isMathMode(); } Char AtomDecor::getChar(Env& env) const { if (!isChar()) return {}; return ((CharSymbol*)_atom.get())->getChar(env); } void AtomDecor::changeAtom(const sptr& atom) { _textSymbol = false; _atom = atom; _type = AtomType::none; } sptr AtomDecor::createBox(Env& env) { if (_textSymbol) ((CharSymbol*)_atom.get())->markAsText(); auto box = _atom->createBox(env); if (_textSymbol) ((CharSymbol*)_atom.get())->removeMark(); return box; } bool AtomDecor::isKern() const { auto* x = dynamic_cast(_atom.get()); return (x != nullptr); } void AtomDecor::setPreviousAtom(const sptr& prev) { auto* row = dynamic_cast(_atom.get()); if (row != nullptr) row->setPreviousAtom(prev); } bool RowAtom::_breakEverywhere = false; // clang-format off bitset<16> RowAtom::_binSet = bitset<16>() .set(static_cast(AtomType::binaryOperator)) .set(static_cast(AtomType::bigOperator)) .set(static_cast(AtomType::relation)) .set(static_cast(AtomType::opening)) .set(static_cast(AtomType::punctuation)); bitset<16> RowAtom::_ligKernSet = bitset<16>() .set(static_cast(AtomType::ordinary)) .set(static_cast(AtomType::bigOperator)) .set(static_cast(AtomType::binaryOperator)) .set(static_cast(AtomType::relation)) .set(static_cast(AtomType::opening)) .set(static_cast(AtomType::closing)) .set(static_cast(AtomType::punctuation)); // clang-format on RowAtom::RowAtom(const sptr& atom) : _lookAtLastAtom(false), _previousAtom(nullptr), _breakable(true) { if (atom != nullptr) { auto* x = dynamic_cast(atom.get()); if (x != nullptr) { // no need to make a row, the only element of a row _elements.insert(_elements.end(), x->_elements.begin(), x->_elements.end()); } else { _elements.push_back(atom); } } } sptr RowAtom::getFirstAtom() { if (!_elements.empty()) return _elements.front(); return nullptr; } sptr RowAtom::popBack() { if (!_elements.empty()) { sptr x = _elements.back(); _elements.pop_back(); return x; } return SpaceAtom::empty(); } sptr RowAtom::get(size_t pos) { if (pos >= _elements.size()) return SpaceAtom::empty(); return _elements[pos]; } void RowAtom::add(const sptr& atom) { if (atom != nullptr) _elements.push_back(atom); } void RowAtom::changeToOrd(AtomDecor* cur, AtomDecor* prev, Atom* next) { AtomType type = cur->leftType(); // clang-format off if ((type == AtomType::binaryOperator) && ((prev == nullptr || _binSet[static_cast(prev->rightType())]) || next == nullptr) ) { // clang-format on cur->_type = AtomType::ordinary; } else if (next != nullptr && cur->rightType() == AtomType::binaryOperator) { AtomType nextType = next->leftType(); // clang-format off if (nextType == AtomType::relation || nextType == AtomType::closing || nextType == AtomType::punctuation ) { // clang-format on cur->_type = AtomType::ordinary; } } } AtomType RowAtom::leftType() const { if (_elements.empty()) return AtomType::ordinary; return _elements.front()->leftType(); } AtomType RowAtom::rightType() const { if (_elements.empty()) return AtomType::ordinary; return _elements.back()->rightType(); } sptr RowAtom::currentChar(int i) { if (i >= _elements.size()) return nullptr; const auto a = _elements[i]; if (!a->isChar()) return nullptr; return static_pointer_cast(a); } int RowAtom::processInvalid(const sptr& txt, bool isMathMode, int i, Env& env) { const auto a = currentChar(i); if (a == nullptr || a->isMathMode() != isMathMode) return i; const auto& chr = a->getChar(env); if (chr.isValid()) return i; txt->append(chr.mappedCode); return processInvalid(txt, isMathMode, i + 1, env); } sptr RowAtom::processContinues(int& i, bool isMathMode) { if (i >= _elements.size()) return nullptr; int cnt = 0; auto txt = sptrOf(isMathMode); const auto next = [&]() { auto a = currentChar(i + cnt); return a == nullptr || a->isMathMode() != isMathMode ? 0 : a->unicode(); }; const auto collect = [&](c32 code) { txt->append(code); ++cnt; }; microtex::scanContinuedUnicodes(next, collect); if (cnt <= 1) return nullptr; i += cnt - 1; return txt; } sptr RowAtom::createBox(Env& env) { auto hbox = new HBox(); // convert atoms to boxes and add to the horizontal box const int end = _elements.size() - 1; for (int i = -1; i < end;) { auto raw = _elements[++i]; // 1. Skip break marks bool hasBreak = false; auto ba = dynamic_cast(raw.get()); while (ba != nullptr) { hasBreak = true; if (i < end) { raw = _elements[++i]; ba = dynamic_cast(raw.get()); } else { break; } } auto curr = sptrOf(raw); auto tmp = curr; // 2. process continued and invalid chars auto t = processContinues(i, curr->isMathMode()); if (t != nullptr) { curr = sptrOf(t); tmp = i < end ? sptrOf(_elements[i + 1]) : sptrOf(EmptyAtom::create()); } Char c = tmp->getChar(env); if (tmp->isChar() && !c.isValid()) { const auto isMathMode = tmp->isMathMode(); if (t == nullptr) t = sptrOf(isMathMode); else ++i; t->append(c.mappedCode); i = processInvalid(t, isMathMode, i + 1, env) - 1; curr = sptrOf(t); } // 3. Change atom type // if necessary, change BIN to ORD // i.e. for formula: $+ e - f$, the plus sign should be treated as an ordinary atom sptr nextAtom = nullptr; if (i < end) nextAtom = _elements[i + 1]; changeToOrd(curr.get(), _previousAtom.get(), nextAtom.get()); // 4. Check for ligatures and kerning float kern = 0.f; if (nextAtom != nullptr && curr->rightType() == AtomType::ordinary && curr->isChar()) { curr->markAsTextSymbol(); // initialize const auto& chr = c; const auto font = chr.fontId; auto ligs = FontContext::getFont(font)->otf().ligatures(); auto lig = ligs == nullptr ? nullptr : (*ligs)[chr.glyphId]; // find target from the ligatures table int index = i; const LigaTable* target = nullptr; i32 nextGlyph = -1; while (i < end && lig != nullptr) { auto next = _elements[++i]; if (!next->isChar() || !_ligKernSet[static_cast(next->leftType())]) { break; } // is safe to cast the atom to CharSymbol since it is a char auto nextChar = static_cast(next.get()); auto c = nextChar->getChar(env); // not in same font, break the iteration if (c.fontId != font) { break; } // record the first following glyph if (nextGlyph == -1) nextGlyph = c.glyphId; // is ligature found? lig = (*lig)[c.glyphId]; if (lig != nullptr && lig->value() > 0) { target = lig; index = i; } } // reset the current index i = index; auto rawKern = [](const Char& p, i32 q) { // The glyph is guaranteed to be valid auto kern = FontContext::getFont(p.fontId)->otf().glyph(p.glyphId)->kernRecord()[q]; if (kern == 0) { // Try find from class-kerning kern = FontContext::getFont(p.fontId)->otf().classKerning(p.glyphId, q); } return kern; }; if (target != nullptr) { // We found it! Replace with ligature char const auto& fixed = Char::onlyGlyph(chr.fontId, target->value(), chr.scale); curr->changeAtom(sptrOf(fixed)); // TODO record the original code-points? } else if (nextGlyph != -1) { kern = rawKern(chr, nextGlyph) * chr.scale; } else if (nextAtom->isChar() && _ligKernSet[static_cast(nextAtom->leftType())]) { auto nextChar = static_cast(nextAtom.get()); kern = rawKern(chr, nextChar->getChar(env).glyphId) * chr.scale; } } // 5. Insert glue, unless it's the first element of the row // or the previous element or the current is a kerning if (i != 0 && _previousAtom != nullptr && !_previousAtom->isKern() && !curr->isKern()) { const auto glue = Glue::get(_previousAtom->rightType(), curr->leftType(), env); if (std::abs(glue->_width) > PREC) { hbox->add(glue); } } // 6. Add break mark to box if (_breakable) { if (_breakEverywhere) { hbox->addBreakPosition(hbox->size()); } else { if (hasBreak) { hbox->addBreakPosition(hbox->size()); } else { auto charAtom = dynamic_cast(raw.get()); // KP: disable automatic breaking on digits // if (charAtom != nullptr && isUnicodeDigit(charAtom->unicode())) { // hbox->addBreakPosition(hbox->size()); // } } } } // 7. Add italic curr->setPreviousAtom(_previousAtom); auto box = curr->createBox(env); /* FIXME * In most cases we don't care the italic corrections * on math environment, although it is looks crappy if (auto cb = dynamic_cast(box.get()); cb != nullptr && curr->isMathMode() && nextAtom != nullptr && nextAtom->isChar()) { kern += cb->italic(); }*/ // 8. Append atom's box and kerning to horizontal box hbox->add(box); if (std::abs(kern) > PREC) hbox->add(StrutBox::create(kern)); env.setLastFontId(box->lastFontId()); // kerning do not interfere with the normal glue-rules without kerning if (!curr->isKern()) _previousAtom = curr; } // reset previous atom _previousAtom = nullptr; return sptr(hbox); } void RowAtom::setPreviousAtom(const sptr& prev) { _previousAtom = prev; }