#include "box/box_group.h" #include #include "box/box_single.h" #include "graphic/graphic.h" using namespace std; using namespace microtex; /************************************* horizontal box implementation ******************************/ HBox::HBox(const sptr& box, float width, float bias) { if (width == POS_INF) { add(box); return; } float rest = width - box->_width; if (rest < 0) { add(box); return; } if (std::abs(bias) < PREC) { auto space = StrutBox::create(rest / 2); add(space); add(box); add(space); } else { add(StrutBox::create(rest / 2 + bias)); add(box); add(StrutBox::create(rest / 2 - bias)); } } HBox::HBox(const sptr& box, float width, Alignment alignment) { if (width == POS_INF) { add(box); return; } float rest = width - box->_width; if (rest <= 0) { add(box); return; } if (alignment == Alignment::center || alignment == Alignment::none) { auto space = StrutBox::create(rest / 2); add(space); add(box); add(space); } else if (alignment == Alignment::left) { add(box); auto space = StrutBox::create(rest); add(space); } else if (alignment == Alignment::right) { auto space = StrutBox::create(rest); add(space); add(box); } else { add(box); } } HBox::HBox(const sptr& box) { add(box); } void HBox::recalculate(const Box& box) { // \left(\!\!\!\begin{array}{c}n\\\\r\end{array}\!\!\!\right)+123 // curPos += box._width; // width = max(width, curPos); _width += box._width; const float h = _children.empty() ? NEG_INF : _height; _height = max(h, box._height - box._shift); const float d = _children.empty() ? NEG_INF : _depth; _depth = max(d, box._depth + box._shift); } sptr HBox::cloneBox() { auto* b = new HBox(); b->_shift = _shift; return sptr(b); } void HBox::add(const sptr& box) { recalculate(*box); BoxGroup::add(box); } void HBox::add(int pos, const sptr& box) { recalculate(*box); BoxGroup::add(pos, box); } void HBox::replaceFirst(const sptr& from, const sptr& to) { auto replaced = false; for (auto& i : _children) { if (i == from) { replaced = true; i = to; break; } } if (!replaced) return; _width -= from->_width - to->_width; auto mh = 0.f, md = 0.f; for (auto& i : _children) { mh = std::max(mh, i->_height - i->_shift); md = std::max(md, i->_depth + i->_shift); } _height = mh; _depth = md; } pair, sptr> HBox::split(int pos, int shift) { auto hb1 = cloneBox(); auto hb2 = cloneBox(); for (int i = 0; i <= pos; i++) { hb1->add(_children[i]); } for (size_t i = pos + shift; i < _children.size(); i++) { hb2->add(_children[i]); } if (!_breakPositions.empty()) { for (int _breakPosition : _breakPositions) { if (_breakPosition > pos + 1) { hb2->addBreakPosition(_breakPosition - pos - 1); } } } return make_pair(hb1, hb2); } void HBox::draw(Graphics2D& g2, float x, float y) { float xPos = x; for (const auto& box : _children) { box->draw(g2, xPos, y + box->_shift); xPos += box->_width; } } /************************************* vertical box implementation ********************************/ VBox::VBox(const sptr& box, float rest, Alignment alignment) : _leftMostPos(F_MAX), _rightMostPos(F_MIN) { add(box); if (alignment == Alignment::center) { auto s = sptrOf(0.f, rest / 2, 0.f, 0.f); BoxGroup::add(0, s); _height += rest / 2.f; _depth += rest / 2.f; BoxGroup::add(s); } else if (alignment == Alignment::top) { _depth += rest; auto s = sptrOf(0.f, rest, 0.f, 0.f); BoxGroup::add(s); } else if (alignment == Alignment::bottom) { _height += rest; auto s = sptrOf(0.f, rest, 0.f, 0.f); BoxGroup::add(0, s); } } void VBox::recalculateWidth(const Box& box) { _leftMostPos = min(_leftMostPos, box._shift); _rightMostPos = max(_rightMostPos, box._shift + (box._width > 0 ? box._width : 0)); _width = _rightMostPos - _leftMostPos; } void VBox::add(const sptr& box) { BoxGroup::add(box); if (_children.size() == 1) { _height = box->_height; _depth = box->_depth; } else { _depth += box->_height + box->_depth; } recalculateWidth(*box); } void VBox::add(const sptr& box, float interline) { if (!_children.empty()) { auto s = sptrOf(0.f, interline, 0.f, 0.f); add(s); } add(box); } void VBox::add(int pos, const sptr& box) { BoxGroup::add(pos, box); if (pos == 0) { _depth += box->_depth + _height; _height = box->_height; } else { _depth += box->_height + box->_depth; } recalculateWidth(*box); } void VBox::draw(Graphics2D& g2, float x, float y) { float yPos = y - _height; for (const auto& b : _children) { yPos += b->_height; b->draw(g2, x + b->_shift - _leftMostPos, yPos); yPos += b->_depth; } } /********************************** color box implementation ********************************/ ColorBox::ColorBox(const sptr& box, color fg, color bg) : DecorBox(box) { _foreground = fg; _background = bg; _type = box->_type; copyMetrics(box); } void ColorBox::draw(Graphics2D& g2, float x, float y) { const color prev = g2.getColor(); if (!isTransparent(_background)) { g2.setColor(_background); g2.fillRect(x, y - _height, _width, _height + _depth); } g2.setColor(isTransparent(_foreground) ? prev : _foreground); _base->draw(g2, x, y); g2.setColor(prev); } /*************************************** scale box implementation *********************************/ void ScaleBox::init(const sptr& b, float sx, float sy) { _sx = (isnan(sx) || isinf(sx)) ? 1 : sx; _sy = (isnan(sy) || isinf(sy)) ? 1 : sy; _width = b->_width * abs(_sx); _height = _sy > 0 ? b->_height * _sy : -b->_depth * _sy; _depth = _sy > 0 ? b->_depth * _sy : -b->_height * _sy; _shift = b->_shift * _sy; } void ScaleBox::draw(Graphics2D& g2, float x, float y) { if (_sx == 0 || _sy == 0) return; float dec = _sx < 0 ? _width : 0; g2.translate(x + dec, y); g2.scale(_sx, _sy); _base->draw(g2, 0, 0); g2.scale(1.f / _sx, 1.f / _sy); g2.translate(-x - dec, -y); } /************************************** reflect box implementation ********************************/ ReflectBox::ReflectBox(const sptr& b) : DecorBox(b) { copyMetrics(b); } void ReflectBox::draw(Graphics2D& g2, float x, float y) { g2.translate(x, y); g2.scale(-1, 1); _base->draw(g2, -_width, 0); g2.scale(-1, 1); g2.translate(-x, -y); } /************************************** rotate box implementation *********************************/ void RotateBox::init(const sptr& b, float angle, float x, float y) { _angle = angle * PI / 180; _height = b->_height; _depth = b->_depth; _width = b->_width; float s = sin(_angle); float c = cos(_angle); _shiftX = x * (1 - c) + y * s; _shiftY = y * (1 - c) - x * s; // clang-format off _xmax = max( -_height * s, max( _depth * s, max(_width * c + _depth * s, _width * c - _height * s) ) ) + _shiftX; _xmin = min( -_height * s, min( _depth * s, min(_width * c + _depth * s, _width * c - _height * s) ) ) + _shiftX; _ymax = max( _height * c, max( -_depth * c, max(_width * s - _depth * c, _width * s + _height * c) ) ); _ymin = min( _height * c, min( -_depth * c, min(_width * s - _depth * c, _width * s + _height * c) ) ); // clang-format on _width = _xmax - _xmin; _height = _ymax + _shiftY; _depth = -_ymin - _shiftY; } Point RotateBox::calculateShift(const Box& b, Rotation option) { Point p(0, -b._depth); switch (option) { case Rotation::bl: p.x = 0; p.y = -b._depth; break; case Rotation::br: p.x = b._width; p.y = -b._depth; break; case Rotation::bc: p.x = b._width / 2.f; p.y = -b._depth; break; case Rotation::tl: p.x = 0; p.y = b._height; break; case Rotation::tr: p.x = b._width; p.y = b._height; break; case Rotation::tc: p.x = b._width / 2.f; p.y = b._height; break; case Rotation::Bl: p.x = 0; p.y = 0; break; case Rotation::Br: p.x = b._width; p.y = 0; break; case Rotation::Bc: p.x = b._width / 2.f; p.y = 0; break; case Rotation::cl: p.x = 0; p.y = (b._height - b._depth) / 2.f; break; case Rotation::cr: p.x = b._width; p.y = (b._height - b._depth) / 2.f; break; case Rotation::cc: p.x = b._width / 2.f; p.y = (b._height - b._depth) / 2.f; break; default: break; } return p; } Rotation RotateBox::getOrigin(string option) { if (option.empty()) return Rotation::Bl; if (option.size() == 1) option += "c"; if (option == "bl" || option == "lb") return Rotation::bl; if (option == "bc" || option == "cb") return Rotation::bc; if (option == "br" || option == "rb") return Rotation::br; if (option == "cl" || option == "lc") return Rotation::cl; if (option == "cc") return Rotation::cc; if (option == "cr" || option == "rc") return Rotation::cr; if (option == "tl" || option == "lt") return Rotation::tl; if (option == "tc" || option == "ct") return Rotation::tc; if (option == "tr" || option == "rt") return Rotation::tr; if (option == "Bl" || option == "lB") return Rotation::Bl; if (option == "Bc" || option == "cB") return Rotation::Bc; if (option == "Br" || option == "rB") return Rotation::Br; return Rotation::Bl; } void RotateBox::draw(Graphics2D& g2, float x, float y) { y -= _shiftY; x += _shiftX - _xmin; g2.rotate(-_angle, x, y); _base->draw(g2, x, y); g2.rotate(_angle, x, y); } /************************************* framed box implementation **********************************/ void FramedBox::init(const sptr& box, float thickness, float space) { _line = transparent; _bg = transparent; const Box& b = *box; _width = b._width + 2 * thickness + 2 * space; _height = b._height + thickness + space; _depth = b._depth + thickness + space; _shift = b._shift; _thickness = thickness; _space = space; } void FramedBox::draw(Graphics2D& g2, float x, float y) { const Stroke& st = g2.getStroke(); g2.setStroke(Stroke(_thickness, CAP_BUTT, JOIN_MITER)); float th = _thickness / 2.f; if (!isTransparent(_bg)) { color prev = g2.getColor(); g2.setColor(_bg); g2.fillRect(x + th, y - _height + th, _width - _thickness, _height + _depth - _thickness); g2.setColor(prev); } if (!isTransparent(_line)) { color prev = g2.getColor(); g2.setColor(_line); g2.drawRect(x + th, y - _height + th, _width - _thickness, _height + _depth - _thickness); g2.setColor(prev); } else { g2.drawRect(x + th, y - _height + th, _width - _thickness, _height + _depth - _thickness); } g2.setStroke(st); _base->draw(g2, x + _space + _thickness, y); } void OvalBox::draw(Graphics2D& g2, float x, float y) { const Stroke& st = g2.getStroke(); g2.setStroke(Stroke(_thickness, CAP_BUTT, JOIN_MITER)); float th = _thickness / 2.f; float r = 0.f; if (_diameter != 0) { r = _diameter; } else { r = _multiplier * min(_width - _thickness, _height + _depth - _thickness); } g2.drawRoundRect( x + th, y - _height + th, _width - _thickness, _height + _depth - _thickness, r, r ); g2.setStroke(st); _base->draw(g2, x + _space + _thickness, y); } void ShadowBox::draw(Graphics2D& g2, float x, float y) { const float th = _thickness / 2.f; const Stroke& st = g2.getStroke(); g2.setStroke(Stroke(_thickness, CAP_BUTT, JOIN_MITER)); g2.drawRect( x + th, y - _height + th, _width - _shadowRule - _thickness, _height + _depth - _shadowRule - _thickness ); float penth = abs(1.f / g2.sx()); g2.setStroke(Stroke(penth, CAP_BUTT, JOIN_MITER)); g2.fillRect( x + _shadowRule - penth, y + _depth - _shadowRule - penth, _width - _shadowRule, _shadowRule ); g2.fillRect( x + _width - _shadowRule - penth, y - _height + th + _shadowRule, _shadowRule, _depth + _height - 2 * _shadowRule - th ); g2.setStroke(st); _base->draw(g2, x + _space + _thickness, y); } /************************************ wrapper box implementation **********************************/ void WrapperBox::addInsets(float l, float t, float r, float b) { _l += l; _width += l + r; _height += t; _depth += b; } void WrapperBox::draw(Graphics2D& g2, float x, float y) { const color prev = g2.getColor(); if (!isTransparent(_bg)) { g2.setColor(_bg); g2.fillRect(x, y - _height, _width, _height + _depth); } g2.setColor(isTransparent(_fg) ? prev : _fg); _base->draw(g2, x + _l, y + _base->_shift); g2.setColor(prev); }