#include "property.h" #include "element.h" #include "lunasvg.h" #include #include namespace lunasvg { const Color Color::Black(0xFF000000); const Color Color::White(0xFFFFFFFF); const Color Color::Transparent(0x00000000); Color& Color::combine(double opacity) { *this = combined(opacity); return *this; } Color Color::combined(double opacity) const { auto rgb = m_value & 0x00FFFFFF; auto a = static_cast(std::clamp(opacity * alpha(), 0.0, 255.0)); return Color(rgb | a << 24); } Paint::Paint(const Color& color) : m_color(color) { } Paint::Paint(const std::string& ref, const Color& color) : m_ref(ref), m_color(color) { } Point::Point(double x, double y) : x(x), y(y) { } const Rect Rect::Empty{0, 0, 0, 0}; const Rect Rect::Invalid{0, 0, -1, -1}; Rect::Rect(double x, double y, double w, double h) : x(x), y(y), w(w), h(h) { } Rect::Rect(const Box& box) : x(box.x), y(box.y), w(box.w), h(box.h) { } Rect Rect::operator&(const Rect& rect) const { if(!rect.valid()) return *this; if(!valid()) return rect; auto l = std::max(x, rect.x); auto t = std::max(y, rect.y); auto r = std::min(x + w, rect.x + rect.w); auto b = std::min(y + h, rect.y + rect.h); return Rect{l, t, r-l, b-t}; } Rect Rect::operator|(const Rect& rect) const { if(!rect.valid()) return *this; if(!valid()) return rect; auto l = std::min(x, rect.x); auto t = std::min(y, rect.y); auto r = std::max(x + w, rect.x + rect.w); auto b = std::max(y + h, rect.y + rect.h); return Rect{l, t, r-l, b-t}; } Rect& Rect::intersect(const Rect& rect) { *this = *this & rect; return *this; } Rect& Rect::unite(const Rect& rect) { *this = *this | rect; return *this; } Transform::Transform(double m00, double m10, double m01, double m11, double m02, double m12) : m00(m00), m10(m10), m01(m01), m11(m11), m02(m02), m12(m12) { } Transform::Transform(const Matrix& matrix) : m00(matrix.a), m10(matrix.b), m01(matrix.c), m11(matrix.d), m02(matrix.e), m12(matrix.f) { } Transform Transform::inverted() const { double det = (this->m00 * this->m11 - this->m10 * this->m01); if(det == 0.0) return Transform{}; double inv_det = 1.0 / det; double m00 = this->m00 * inv_det; double m10 = this->m10 * inv_det; double m01 = this->m01 * inv_det; double m11 = this->m11 * inv_det; double m02 = (this->m01 * this->m12 - this->m11 * this->m02) * inv_det; double m12 = (this->m10 * this->m02 - this->m00 * this->m12) * inv_det; return Transform{m11, -m10, -m01, m00, m02, m12}; } Transform Transform::operator*(const Transform& transform) const { double m00 = this->m00 * transform.m00 + this->m10 * transform.m01; double m10 = this->m00 * transform.m10 + this->m10 * transform.m11; double m01 = this->m01 * transform.m00 + this->m11 * transform.m01; double m11 = this->m01 * transform.m10 + this->m11 * transform.m11; double m02 = this->m02 * transform.m00 + this->m12 * transform.m01 + transform.m02; double m12 = this->m02 * transform.m10 + this->m12 * transform.m11 + transform.m12; return Transform{m00, m10, m01, m11, m02, m12}; } Transform& Transform::operator*=(const Transform& transform) { *this = *this * transform; return *this; } Transform& Transform::premultiply(const Transform& transform) { *this = transform * *this; return *this; } Transform& Transform::postmultiply(const Transform& transform) { *this = *this * transform; return *this; } Transform& Transform::rotate(double angle) { *this = rotated(angle) * *this; return *this; } Transform& Transform::rotate(double angle, double cx, double cy) { *this = rotated(angle, cx, cy) * *this; return *this; } Transform& Transform::scale(double sx, double sy) { *this = scaled(sx, sy) * *this; return *this; } Transform& Transform::shear(double shx, double shy) { *this = sheared(shx, shy) * *this; return *this; } Transform& Transform::translate(double tx, double ty) { *this = translated(tx, ty) * *this; return *this; } Transform& Transform::transform(double m00, double m10, double m01, double m11, double m02, double m12) { *this = Transform{m00, m10, m01, m11, m02, m12} * *this; return *this; } Transform& Transform::identity() { *this = Transform{1, 0, 0, 1, 0, 0}; return *this; } Transform& Transform::invert() { *this = inverted(); return *this; } void Transform::map(double x, double y, double* _x, double* _y) const { *_x = x * m00 + y * m01 + m02; *_y = x * m10 + y * m11 + m12; } Point Transform::map(double x, double y) const { map(x, y, &x, &y); return Point{x, y}; } Point Transform::map(const Point& point) const { return map(point.x, point.y); } Rect Transform::map(const Rect& rect) const { if(!rect.valid()) return Rect::Invalid; auto x1 = rect.x; auto y1 = rect.y; auto x2 = rect.x + rect.w; auto y2 = rect.y + rect.h; const Point p[] = { map(x1, y1), map(x2, y1), map(x2, y2), map(x1, y2) }; auto l = p[0].x; auto t = p[0].y; auto r = p[0].x; auto b = p[0].y; for(int i = 1;i < 4;i++) { if(p[i].x < l) l = p[i].x; if(p[i].x > r) r = p[i].x; if(p[i].y < t) t = p[i].y; if(p[i].y > b) b = p[i].y; } return Rect{l, t, r-l, b-t}; } static const double pi = 3.14159265358979323846; Transform Transform::rotated(double angle) { auto c = std::cos(angle * pi / 180.0); auto s = std::sin(angle * pi / 180.0); return Transform{c, s, -s, c, 0, 0}; } Transform Transform::rotated(double angle, double cx, double cy) { auto c = std::cos(angle * pi / 180.0); auto s = std::sin(angle * pi / 180.0); auto x = cx * (1 - c) + cy * s; auto y = cy * (1 - c) - cx * s; return Transform{c, s, -s, c, x, y}; } Transform Transform::scaled(double sx, double sy) { return Transform{sx, 0, 0, sy, 0, 0}; } Transform Transform::sheared(double shx, double shy) { auto x = std::tan(shx * pi / 180.0); auto y = std::tan(shy * pi / 180.0); return Transform{1, y, x, 1, 0, 0}; } Transform Transform::translated(double tx, double ty) { return Transform{1, 0, 0, 1, tx, ty}; } void Path::moveTo(double x, double y) { m_commands.push_back(PathCommand::MoveTo); m_points.emplace_back(x, y); } void Path::lineTo(double x, double y) { m_commands.push_back(PathCommand::LineTo); m_points.emplace_back(x, y); } void Path::cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) { m_commands.push_back(PathCommand::CubicTo); m_points.emplace_back(x1, y1); m_points.emplace_back(x2, y2); m_points.emplace_back(x3, y3); } void Path::close() { if(m_commands.empty()) return; if(m_commands.back() == PathCommand::Close) return; m_commands.push_back(PathCommand::Close); } void Path::reset() { m_commands.clear(); m_points.clear(); } bool Path::empty() const { return m_commands.empty(); } void Path::quadTo(double cx, double cy, double x1, double y1, double x2, double y2) { auto cx1 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * cx; auto cy1 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * cy; auto cx2 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2; auto cy2 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2; cubicTo(cx1, cy1, cx2, cy2, x2, y2); } void Path::arcTo(double cx, double cy, double rx, double ry, double xAxisRotation, bool largeArcFlag, bool sweepFlag, double x, double y) { rx = std::fabs(rx); ry = std::fabs(ry); auto sin_th = std::sin(xAxisRotation * pi / 180.0); auto cos_th = std::cos(xAxisRotation * pi / 180.0); auto dx = (cx - x) / 2.0; auto dy = (cy - y) / 2.0; auto dx1 = cos_th * dx + sin_th * dy; auto dy1 = -sin_th * dx + cos_th * dy; auto Pr1 = rx * rx; auto Pr2 = ry * ry; auto Px = dx1 * dx1; auto Py = dy1 * dy1; auto check = Px / Pr1 + Py / Pr2; if(check > 1) { rx = rx * std::sqrt(check); ry = ry * std::sqrt(check); } auto a00 = cos_th / rx; auto a01 = sin_th / rx; auto a10 = -sin_th / ry; auto a11 = cos_th / ry; auto x0 = a00 * cx + a01 * cy; auto y0 = a10 * cx + a11 * cy; auto x1 = a00 * x + a01 * y; auto y1 = a10 * x + a11 * y; auto d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); auto sfactor_sq = 1.0 / d - 0.25; if(sfactor_sq < 0) sfactor_sq = 0; auto sfactor = std::sqrt(sfactor_sq); if(sweepFlag == largeArcFlag) sfactor = -sfactor; auto xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); auto yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); auto th0 = std::atan2(y0 - yc, x0 - xc); auto th1 = std::atan2(y1 - yc, x1 - xc); double th_arc = th1 - th0; if(th_arc < 0.0 && sweepFlag) th_arc += 2.0 * pi; else if(th_arc > 0.0 && !sweepFlag) th_arc -= 2.0 * pi; auto n_segs = static_cast(std::ceil(std::fabs(th_arc / (pi * 0.5 + 0.001)))); for(int i = 0;i < n_segs;i++) { auto th2 = th0 + i * th_arc / n_segs; auto th3 = th0 + (i + 1) * th_arc / n_segs; auto a00 = cos_th * rx; auto a01 = -sin_th * ry; auto a10 = sin_th * rx; auto a11 = cos_th * ry; auto thHalf = 0.5 * (th3 - th2); auto t = (8.0 / 3.0) * std::sin(thHalf * 0.5) * std::sin(thHalf * 0.5) / std::sin(thHalf); auto x1 = xc + std::cos(th2) - t * std::sin(th2); auto y1 = yc + std::sin(th2) + t * std::cos(th2); auto x3 = xc + std::cos(th3); auto y3 = yc + std::sin(th3); auto x2 = x3 + t * std::sin(th3); auto y2 = y3 - t * std::cos(th3); auto cx1 = a00 * x1 + a01 * y1; auto cy1 = a10 * x1 + a11 * y1; auto cx2 = a00 * x2 + a01 * y2; auto cy2 = a10 * x2 + a11 * y2; auto cx3 = a00 * x3 + a01 * y3; auto cy3 = a10 * x3 + a11 * y3; cubicTo(cx1, cy1, cx2, cy2, cx3, cy3); } } static const double kappa = 0.55228474983079339840; void Path::ellipse(double cx, double cy, double rx, double ry) { auto left = cx - rx; auto top = cy - ry; auto right = cx + rx; auto bottom = cy + ry; auto cpx = rx * kappa; auto cpy = ry * kappa; moveTo(cx, top); cubicTo(cx+cpx, top, right, cy-cpy, right, cy); cubicTo(right, cy+cpy, cx+cpx, bottom, cx, bottom); cubicTo(cx-cpx, bottom, left, cy+cpy, left, cy); cubicTo(left, cy-cpy, cx-cpx, top, cx, top); close(); } void Path::rect(double x, double y, double w, double h, double rx, double ry) { rx = std::min(rx, w * 0.5); ry = std::min(ry, h * 0.5); auto right = x + w; auto bottom = y + h; if(rx == 0.0 && ry == 0.0) { moveTo(x, y); lineTo(right, y); lineTo(right, bottom); lineTo(x, bottom); lineTo(x, y); close(); } else { double cpx = rx * kappa; double cpy = ry * kappa; moveTo(x, y+ry); cubicTo(x, y+ry-cpy, x+rx-cpx, y, x+rx, y); lineTo(right-rx, y); cubicTo(right-rx+cpx, y, right, y+ry-cpy, right, y+ry); lineTo(right, bottom-ry); cubicTo(right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); lineTo(x+rx, bottom); cubicTo(x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); lineTo(x, y+ry); close(); } } Rect Path::box() const { if(m_points.empty()) return Rect{}; auto l = m_points[0].x; auto t = m_points[0].y; auto r = m_points[0].x; auto b = m_points[0].y; for(std::size_t i = 1;i < m_points.size();i++) { if(m_points[i].x < l) l = m_points[i].x; if(m_points[i].x > r) r = m_points[i].x; if(m_points[i].y < t) t = m_points[i].y; if(m_points[i].y > b) b = m_points[i].y; } return Rect{l, t, r-l, b-t}; } PathIterator::PathIterator(const Path& path) : m_commands(path.commands()), m_points(path.points().data()) { } PathCommand PathIterator::currentSegment(std::array& points) const { auto command = m_commands[m_index]; switch(command) { case PathCommand::MoveTo: points[0] = m_points[0]; m_startPoint = points[0]; break; case PathCommand::LineTo: points[0] = m_points[0]; break; case PathCommand::CubicTo: points[0] = m_points[0]; points[1] = m_points[1]; points[2] = m_points[2]; break; case PathCommand::Close: points[0] = m_startPoint; break; } return command; } bool PathIterator::isDone() const { return (m_index >= m_commands.size()); } void PathIterator::next() { switch(m_commands[m_index]) { case PathCommand::MoveTo: case PathCommand::LineTo: m_points += 1; break; case PathCommand::CubicTo: m_points += 3; break; default: break; } m_index += 1; } const Length Length::Unknown{0, LengthUnits::Unknown}; const Length Length::Zero{0, LengthUnits::Number}; const Length Length::One{1, LengthUnits::Number}; const Length Length::Three{3, LengthUnits::Number}; const Length Length::HundredPercent{100, LengthUnits::Percent}; const Length Length::FiftyPercent{50, LengthUnits::Percent}; const Length Length::OneTwentyPercent{120, LengthUnits::Percent}; const Length Length::MinusTenPercent{-10, LengthUnits::Percent}; Length::Length(double value) : m_value(value) { } Length::Length(double value, LengthUnits units) : m_value(value), m_units(units) { } static const double dpi = 96.0; double Length::value(double max) const { switch(m_units) { case LengthUnits::Number: case LengthUnits::Px: return m_value; case LengthUnits::In: return m_value * dpi; case LengthUnits::Cm: return m_value * dpi / 2.54; case LengthUnits::Mm: return m_value * dpi / 25.4; case LengthUnits::Pt: return m_value * dpi / 72.0; case LengthUnits::Pc: return m_value * dpi / 6.0; case LengthUnits::Percent: return m_value * max / 100.0; default: break; } return 0.0; } static const double sqrt2 = 1.41421356237309504880; double Length::value(const Element* element, LengthMode mode) const { if(m_units == LengthUnits::Percent) { auto viewport = element->currentViewport(); auto w = viewport.w; auto h = viewport.h; auto max = (mode == LengthMode::Width) ? w : (mode == LengthMode::Height) ? h : std::sqrt(w*w+h*h) / sqrt2; return m_value * max / 100.0; } return value(1.0); } LengthContext::LengthContext(const Element* element) : m_element(element) { } LengthContext::LengthContext(const Element* element, Units units) : m_element(element), m_units(units) { } double LengthContext::valueForLength(const Length& length, LengthMode mode) const { if(m_units == Units::ObjectBoundingBox) return length.value(1.0); return length.value(m_element, mode); } PreserveAspectRatio::PreserveAspectRatio(Align align, MeetOrSlice scale) : m_align(align), m_scale(scale) { } Transform PreserveAspectRatio::getMatrix(double width, double height, const Rect& viewBox) const { if(viewBox.empty()) return Transform{}; auto xscale = width / viewBox.w; auto yscale = height / viewBox.h; if(m_align == Align::None) { auto xoffset = -viewBox.x * xscale; auto yoffset = -viewBox.y * yscale; return Transform{xscale, 0, 0, yscale, xoffset, yoffset}; } auto scale = (m_scale == MeetOrSlice::Meet) ? std::min(xscale, yscale) : std::max(xscale, yscale); auto viewWidth = viewBox.w * scale; auto viewHeight = viewBox.h * scale; auto xoffset = -viewBox.x * scale; auto yoffset = -viewBox.y * scale; switch(m_align) { case Align::xMidYMin: case Align::xMidYMid: case Align::xMidYMax: xoffset += (width - viewWidth) * 0.5; break; case Align::xMaxYMin: case Align::xMaxYMid: case Align::xMaxYMax: xoffset += (width - viewWidth); break; default: break; } switch(m_align) { case Align::xMinYMid: case Align::xMidYMid: case Align::xMaxYMid: yoffset += (height - viewHeight) * 0.5; break; case Align::xMinYMax: case Align::xMidYMax: case Align::xMaxYMax: yoffset += (height - viewHeight); break; default: break; } return Transform{scale, 0, 0, scale, xoffset, yoffset}; } Rect PreserveAspectRatio::getClip(double width, double height, const Rect& viewBox) const { if(viewBox.empty()) return Rect{0, 0, width, height}; return viewBox; } Angle::Angle(MarkerOrient type) : m_type(type) { } Angle::Angle(double value, MarkerOrient type) : m_value(value), m_type(type) { } } // namespace lunasvg