ES-DE/external/lunasvg/source/property.cpp

695 lines
17 KiB
C++
Raw Normal View History

#include "property.h"
#include "element.h"
#include "lunasvg.h"
#include <algorithm>
#include <cmath>
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<int>(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<int>(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<Point, 3>& 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