WIP: lunasvg implementation, things stopped working
This commit is contained in:
693
vendor/lunasvg/source/graphics.cpp
vendored
Normal file
693
vendor/lunasvg/source/graphics.cpp
vendored
Normal file
@@ -0,0 +1,693 @@
|
||||
#include "graphics.h"
|
||||
#include "lunasvg.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
const Color Color::Black(0xFF000000);
|
||||
const Color Color::White(0xFFFFFFFF);
|
||||
const Color Color::Transparent(0x00000000);
|
||||
|
||||
const Rect Rect::Empty(0, 0, 0, 0);
|
||||
const Rect Rect::Invalid(0, 0, -1, -1);
|
||||
const Rect Rect::Infinite(-FLT_MAX / 2.f, -FLT_MAX / 2.f, FLT_MAX, FLT_MAX);
|
||||
|
||||
Rect::Rect(const Box& box)
|
||||
: x(box.x), y(box.y), w(box.w), h(box.h)
|
||||
{
|
||||
}
|
||||
|
||||
const Transform Transform::Identity(1, 0, 0, 1, 0, 0);
|
||||
|
||||
Transform::Transform()
|
||||
{
|
||||
plutovg_matrix_init_identity(&m_matrix);
|
||||
}
|
||||
|
||||
Transform::Transform(float a, float b, float c, float d, float e, float f)
|
||||
{
|
||||
plutovg_matrix_init(&m_matrix, a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
Transform::Transform(const Matrix& matrix)
|
||||
: Transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f)
|
||||
{
|
||||
}
|
||||
|
||||
Transform Transform::operator*(const Transform& transform) const
|
||||
{
|
||||
plutovg_matrix_t result;
|
||||
plutovg_matrix_multiply(&result, &transform.m_matrix, &m_matrix);
|
||||
return result;
|
||||
}
|
||||
|
||||
Transform& Transform::operator*=(const Transform& transform)
|
||||
{
|
||||
return (*this = *this * transform);
|
||||
}
|
||||
|
||||
Transform& Transform::multiply(const Transform& transform)
|
||||
{
|
||||
return (*this *= transform);
|
||||
}
|
||||
|
||||
Transform& Transform::translate(float tx, float ty)
|
||||
{
|
||||
return multiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Transform& Transform::scale(float sx, float sy)
|
||||
{
|
||||
return multiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Transform& Transform::rotate(float angle, float cx, float cy)
|
||||
{
|
||||
return multiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Transform& Transform::shear(float shx, float shy)
|
||||
{
|
||||
return multiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Transform& Transform::postMultiply(const Transform& transform)
|
||||
{
|
||||
return (*this = transform * *this);
|
||||
}
|
||||
|
||||
Transform& Transform::postTranslate(float tx, float ty)
|
||||
{
|
||||
return postMultiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Transform& Transform::postScale(float sx, float sy)
|
||||
{
|
||||
return postMultiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Transform& Transform::postRotate(float angle, float cx, float cy)
|
||||
{
|
||||
return postMultiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Transform& Transform::postShear(float shx, float shy)
|
||||
{
|
||||
return postMultiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Transform Transform::inverse() const
|
||||
{
|
||||
plutovg_matrix_t inverse;
|
||||
plutovg_matrix_invert(&m_matrix, &inverse);
|
||||
return inverse;
|
||||
}
|
||||
|
||||
Transform& Transform::invert()
|
||||
{
|
||||
plutovg_matrix_invert(&m_matrix, &m_matrix);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Transform::reset()
|
||||
{
|
||||
plutovg_matrix_init_identity(&m_matrix);
|
||||
}
|
||||
|
||||
Point Transform::mapPoint(float x, float y) const
|
||||
{
|
||||
plutovg_matrix_map(&m_matrix, x, y, &x, &y);
|
||||
return Point(x, y);
|
||||
}
|
||||
|
||||
Point Transform::mapPoint(const Point& point) const
|
||||
{
|
||||
return mapPoint(point.x, point.y);
|
||||
}
|
||||
|
||||
Rect Transform::mapRect(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid()) {
|
||||
return Rect::Invalid;
|
||||
}
|
||||
|
||||
plutovg_rect_t result = {rect.x, rect.y, rect.w, rect.h};
|
||||
plutovg_matrix_map_rect(&m_matrix, &result, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
float Transform::xScale() const
|
||||
{
|
||||
return std::sqrt(m_matrix.a * m_matrix.a + m_matrix.b * m_matrix.b);
|
||||
}
|
||||
|
||||
float Transform::yScale() const
|
||||
{
|
||||
return std::sqrt(m_matrix.c * m_matrix.c + m_matrix.d * m_matrix.d);
|
||||
}
|
||||
|
||||
bool Transform::parse(const char* data, size_t length)
|
||||
{
|
||||
return plutovg_matrix_parse(&m_matrix, data, length);
|
||||
}
|
||||
|
||||
Transform Transform::rotated(float angle, float cx, float cy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
if(cx == 0.f && cy == 0.f) {
|
||||
plutovg_matrix_init_rotate(&matrix, PLUTOVG_DEG2RAD(angle));
|
||||
} else {
|
||||
plutovg_matrix_init_translate(&matrix, cx, cy);
|
||||
plutovg_matrix_rotate(&matrix, PLUTOVG_DEG2RAD(angle));
|
||||
plutovg_matrix_translate(&matrix, -cx, -cy);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::scaled(float sx, float sy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_scale(&matrix, sx, sy);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::sheared(float shx, float shy)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_shear(&matrix, PLUTOVG_DEG2RAD(shx), PLUTOVG_DEG2RAD(shy));
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Transform Transform::translated(float tx, float ty)
|
||||
{
|
||||
plutovg_matrix_t matrix;
|
||||
plutovg_matrix_init_translate(&matrix, tx, ty);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Path::Path(const Path& path)
|
||||
: m_data(plutovg_path_reference(path.data()))
|
||||
{
|
||||
}
|
||||
|
||||
Path::Path(Path&& path)
|
||||
: m_data(path.release())
|
||||
{
|
||||
}
|
||||
|
||||
Path::~Path()
|
||||
{
|
||||
plutovg_path_destroy(m_data);
|
||||
}
|
||||
|
||||
Path& Path::operator=(const Path& path)
|
||||
{
|
||||
Path(path).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Path& Path::operator=(Path&& path)
|
||||
{
|
||||
Path(std::move(path)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Path::moveTo(float x, float y)
|
||||
{
|
||||
plutovg_path_move_to(ensure(), x, y);
|
||||
}
|
||||
|
||||
void Path::lineTo(float x, float y)
|
||||
{
|
||||
plutovg_path_line_to(ensure(), x, y);
|
||||
}
|
||||
|
||||
void Path::quadTo(float x1, float y1, float x2, float y2)
|
||||
{
|
||||
plutovg_path_quad_to(ensure(), x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void Path::cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
|
||||
{
|
||||
plutovg_path_cubic_to(ensure(), x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
void Path::arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y)
|
||||
{
|
||||
plutovg_path_arc_to(ensure(), rx, ry, PLUTOVG_DEG2RAD(xAxisRotation), largeArcFlag, sweepFlag, x, y);
|
||||
}
|
||||
|
||||
void Path::close()
|
||||
{
|
||||
plutovg_path_close(ensure());
|
||||
}
|
||||
|
||||
void Path::addEllipse(float cx, float cy, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_ellipse(ensure(), cx, cy, rx, ry);
|
||||
}
|
||||
|
||||
void Path::addRoundRect(float x, float y, float w, float h, float rx, float ry)
|
||||
{
|
||||
plutovg_path_add_round_rect(ensure(), x, y, w, h, rx, ry);
|
||||
}
|
||||
|
||||
void Path::addRect(float x, float y, float w, float h)
|
||||
{
|
||||
plutovg_path_add_rect(ensure(), x, y, w, h);
|
||||
}
|
||||
|
||||
void Path::addEllipse(const Point& center, const Size& radii)
|
||||
{
|
||||
addEllipse(center.x, center.y, radii.w, radii.h);
|
||||
}
|
||||
|
||||
void Path::addRoundRect(const Rect& rect, const Size& radii)
|
||||
{
|
||||
addRoundRect(rect.x, rect.y, rect.w, rect.h, radii.w, radii.h);
|
||||
}
|
||||
|
||||
void Path::addRect(const Rect& rect)
|
||||
{
|
||||
addRect(rect.x, rect.y, rect.w, rect.h);
|
||||
}
|
||||
|
||||
void Path::reset()
|
||||
{
|
||||
if(m_data == nullptr)
|
||||
return;
|
||||
if(isUnique()) {
|
||||
plutovg_path_reset(m_data);
|
||||
} else {
|
||||
plutovg_path_destroy(m_data);
|
||||
m_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Rect Path::boundingRect() const
|
||||
{
|
||||
if(m_data == nullptr)
|
||||
return Rect::Empty;
|
||||
plutovg_rect_t extents;
|
||||
plutovg_path_extents(m_data, &extents, false);
|
||||
return extents;
|
||||
}
|
||||
|
||||
bool Path::isEmpty() const
|
||||
{
|
||||
if(m_data)
|
||||
return plutovg_path_get_elements(m_data, nullptr) == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Path::isUnique() const
|
||||
{
|
||||
return plutovg_path_get_reference_count(m_data) == 1;
|
||||
}
|
||||
|
||||
bool Path::parse(const char* data, size_t length)
|
||||
{
|
||||
plutovg_path_reset(ensure());
|
||||
return plutovg_path_parse(m_data, data, length);
|
||||
}
|
||||
|
||||
plutovg_path_t* Path::ensure()
|
||||
{
|
||||
if(isNull()) {
|
||||
m_data = plutovg_path_create();
|
||||
} else if(!isUnique()) {
|
||||
plutovg_path_destroy(m_data);
|
||||
m_data = plutovg_path_clone(m_data);
|
||||
}
|
||||
|
||||
return m_data;
|
||||
}
|
||||
|
||||
PathIterator::PathIterator(const Path& path)
|
||||
: m_size(plutovg_path_get_elements(path.data(), &m_elements))
|
||||
, m_index(0)
|
||||
{
|
||||
}
|
||||
|
||||
PathCommand PathIterator::currentSegment(std::array<Point, 3>& points) const
|
||||
{
|
||||
auto command = m_elements[m_index].header.command;
|
||||
switch(command) {
|
||||
case PLUTOVG_PATH_COMMAND_MOVE_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_LINE_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
points[1] = m_elements[m_index + 2].point;
|
||||
points[2] = m_elements[m_index + 3].point;
|
||||
break;
|
||||
case PLUTOVG_PATH_COMMAND_CLOSE:
|
||||
points[0] = m_elements[m_index + 1].point;
|
||||
break;
|
||||
}
|
||||
|
||||
return PathCommand(command);
|
||||
}
|
||||
|
||||
void PathIterator::next()
|
||||
{
|
||||
m_index += m_elements[m_index].header.length;
|
||||
}
|
||||
|
||||
FontFace::FontFace(plutovg_font_face_t* face)
|
||||
: m_face(plutovg_font_face_reference(face))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure)
|
||||
: m_face(plutovg_font_face_load_from_data(data, length, 0, destroy_func, closure))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const char* filename)
|
||||
: m_face(plutovg_font_face_load_from_file(filename, 0))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(const FontFace& face)
|
||||
: m_face(plutovg_font_face_reference(face.get()))
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::FontFace(FontFace&& face)
|
||||
: m_face(face.release())
|
||||
{
|
||||
}
|
||||
|
||||
FontFace::~FontFace()
|
||||
{
|
||||
plutovg_font_face_destroy(m_face);
|
||||
}
|
||||
|
||||
FontFace& FontFace::operator=(const FontFace& face)
|
||||
{
|
||||
FontFace(face).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FontFace& FontFace::operator=(FontFace&& face)
|
||||
{
|
||||
FontFace(std::move(face)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void FontFace::swap(FontFace& face)
|
||||
{
|
||||
std::swap(m_face, face.m_face);
|
||||
}
|
||||
|
||||
plutovg_font_face_t* FontFace::release()
|
||||
{
|
||||
return std::exchange(m_face, nullptr);
|
||||
}
|
||||
|
||||
bool FontFaceCache::addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face)
|
||||
{
|
||||
if(!face.isNull())
|
||||
plutovg_font_face_cache_add(m_cache, family.data(), bold, italic, face.get());
|
||||
return !face.isNull();
|
||||
}
|
||||
|
||||
FontFace FontFaceCache::getFontFace(const std::string& family, bool bold, bool italic) const
|
||||
{
|
||||
if(auto face = plutovg_font_face_cache_get(m_cache, family.data(), bold, italic)) {
|
||||
return FontFace(face);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char* generic;
|
||||
const char* fallback;
|
||||
} generic_fallbacks[] = {
|
||||
#if defined(__linux__)
|
||||
{"sans-serif", "DejaVu Sans"},
|
||||
{"serif", "DejaVu Serif"},
|
||||
{"monospace", "DejaVu Sans Mono"},
|
||||
#else
|
||||
{"sans-serif", "Arial"},
|
||||
{"serif", "Times New Roman"},
|
||||
{"monospace", "Courier New"},
|
||||
#endif
|
||||
{"cursive", "Comic Sans MS"},
|
||||
{"fantasy", "Impact"}
|
||||
};
|
||||
|
||||
for(auto value : generic_fallbacks) {
|
||||
if(value.generic == family || family.empty()) {
|
||||
return FontFace(plutovg_font_face_cache_get(m_cache, value.fallback, bold, italic));
|
||||
}
|
||||
}
|
||||
|
||||
return FontFace();
|
||||
}
|
||||
|
||||
FontFaceCache::FontFaceCache()
|
||||
: m_cache(plutovg_font_face_cache_create())
|
||||
{
|
||||
#ifndef LUNASVG_DISABLE_LOAD_SYSTEM_FONTS
|
||||
plutovg_font_face_cache_load_sys(m_cache);
|
||||
#endif
|
||||
}
|
||||
|
||||
FontFaceCache* fontFaceCache()
|
||||
{
|
||||
static FontFaceCache cache;
|
||||
return &cache;
|
||||
}
|
||||
|
||||
Font::Font(const FontFace& face, float size)
|
||||
: m_face(face), m_size(size)
|
||||
{
|
||||
if(m_size > 0.f && !m_face.isNull()) {
|
||||
plutovg_font_face_get_metrics(m_face.get(), m_size, &m_ascent, &m_descent, &m_lineGap, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
float Font::xHeight() const
|
||||
{
|
||||
plutovg_rect_t extents = {0};
|
||||
if(m_size > 0.f && !m_face.isNull())
|
||||
plutovg_font_face_get_glyph_metrics(m_face.get(), m_size, 'x', nullptr, nullptr, &extents);
|
||||
return extents.h;
|
||||
}
|
||||
|
||||
float Font::measureText(const std::u32string_view& text) const
|
||||
{
|
||||
if(m_size > 0.f && !m_face.isNull())
|
||||
return plutovg_font_face_text_extents(m_face.get(), m_size, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(const Bitmap& bitmap)
|
||||
{
|
||||
return std::shared_ptr<Canvas>(new Canvas(bitmap));
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(float x, float y, float width, float height)
|
||||
{
|
||||
constexpr int kMaxSize = 1 << 15;
|
||||
if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize)
|
||||
return std::shared_ptr<Canvas>(new Canvas(0, 0, 1, 1));
|
||||
auto l = static_cast<int>(std::floor(x));
|
||||
auto t = static_cast<int>(std::floor(y));
|
||||
auto r = static_cast<int>(std::ceil(x + width));
|
||||
auto b = static_cast<int>(std::ceil(y + height));
|
||||
return std::shared_ptr<Canvas>(new Canvas(l, t, r - l, b - t));
|
||||
}
|
||||
|
||||
std::shared_ptr<Canvas> Canvas::create(const Rect& extents)
|
||||
{
|
||||
return create(extents.x, extents.y, extents.w, extents.h);
|
||||
}
|
||||
|
||||
void Canvas::setColor(const Color& color)
|
||||
{
|
||||
setColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
|
||||
}
|
||||
|
||||
void Canvas::setColor(float r, float g, float b, float a)
|
||||
{
|
||||
plutovg_canvas_set_rgba(m_canvas, r, g, b, a);
|
||||
}
|
||||
|
||||
void Canvas::setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_linear_gradient(m_canvas, x1, y1, x2, y2, static_cast<plutovg_spread_method_t>(spread), stops.data(), stops.size(), &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_radial_gradient(m_canvas, cx, cy, r, fx, fy, 0.f, static_cast<plutovg_spread_method_t>(spread), stops.data(), stops.size(), &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_texture(m_canvas, source.surface(), static_cast<plutovg_texture_type_t>(type), opacity, &transform.matrix());
|
||||
}
|
||||
|
||||
void Canvas::fillPath(const Path& path, FillRule fillRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(fillRule));
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_fill_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_line_width(m_canvas, strokeData.lineWidth());
|
||||
plutovg_canvas_set_miter_limit(m_canvas, strokeData.miterLimit());
|
||||
plutovg_canvas_set_line_cap(m_canvas, static_cast<plutovg_line_cap_t>(strokeData.lineCap()));
|
||||
plutovg_canvas_set_line_join(m_canvas, static_cast<plutovg_line_join_t>(strokeData.lineJoin()));
|
||||
plutovg_canvas_set_dash_offset(m_canvas, strokeData.dashOffset());
|
||||
plutovg_canvas_set_dash_array(m_canvas, strokeData.dashArray().data(), strokeData.dashArray().size());
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_stroke_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO);
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_set_font(m_canvas, font.face().get(), font.size());
|
||||
plutovg_canvas_fill_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y);
|
||||
}
|
||||
|
||||
void Canvas::strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_line_width(m_canvas, strokeWidth);
|
||||
plutovg_canvas_set_miter_limit(m_canvas, 4.f);
|
||||
plutovg_canvas_set_line_cap(m_canvas, PLUTOVG_LINE_CAP_BUTT);
|
||||
plutovg_canvas_set_line_join(m_canvas, PLUTOVG_LINE_JOIN_MITER);
|
||||
plutovg_canvas_set_dash_offset(m_canvas, 0.f);
|
||||
plutovg_canvas_set_dash_array(m_canvas, nullptr, 0);
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_set_font(m_canvas, font.face().get(), font.size());
|
||||
plutovg_canvas_stroke_text(m_canvas, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF32, origin.x, origin.y);
|
||||
}
|
||||
|
||||
void Canvas::clipPath(const Path& path, FillRule clipRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(clipRule));
|
||||
plutovg_canvas_clip_path(m_canvas, path.data());
|
||||
}
|
||||
|
||||
void Canvas::clipRect(const Rect& rect, FillRule clipRule, const Transform& transform)
|
||||
{
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_set_fill_rule(m_canvas, static_cast<plutovg_fill_rule_t>(clipRule));
|
||||
plutovg_canvas_clip_rect(m_canvas, rect.x, rect.y, rect.w, rect.h);
|
||||
}
|
||||
|
||||
void Canvas::drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform)
|
||||
{
|
||||
auto xScale = dstRect.w / srcRect.w;
|
||||
auto yScale = dstRect.h / srcRect.h;
|
||||
plutovg_matrix_t matrix = { xScale, 0, 0, yScale, -srcRect.x * xScale, -srcRect.y * yScale };
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_transform(m_canvas, &transform.matrix());
|
||||
plutovg_canvas_translate(m_canvas, dstRect.x, dstRect.y);
|
||||
plutovg_canvas_set_fill_rule(m_canvas, PLUTOVG_FILL_RULE_NON_ZERO);
|
||||
plutovg_canvas_set_operator(m_canvas, PLUTOVG_OPERATOR_SRC_OVER);
|
||||
plutovg_canvas_set_texture(m_canvas, image.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, 1.f, &matrix);
|
||||
plutovg_canvas_fill_rect(m_canvas, 0, 0, dstRect.w, dstRect.h);
|
||||
}
|
||||
|
||||
void Canvas::blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity)
|
||||
{
|
||||
plutovg_matrix_t matrix = { 1, 0, 0, 1, static_cast<float>(canvas.x()), static_cast<float>(canvas.y()) };
|
||||
plutovg_canvas_set_matrix(m_canvas, &m_translation);
|
||||
plutovg_canvas_set_operator(m_canvas, static_cast<plutovg_operator_t>(blendMode));
|
||||
plutovg_canvas_set_texture(m_canvas, canvas.surface(), PLUTOVG_TEXTURE_TYPE_PLAIN, opacity, &matrix);
|
||||
plutovg_canvas_paint(m_canvas);
|
||||
}
|
||||
|
||||
void Canvas::save()
|
||||
{
|
||||
plutovg_canvas_save(m_canvas);
|
||||
}
|
||||
|
||||
void Canvas::restore()
|
||||
{
|
||||
plutovg_canvas_restore(m_canvas);
|
||||
}
|
||||
|
||||
int Canvas::width() const
|
||||
{
|
||||
return plutovg_surface_get_width(m_surface);
|
||||
}
|
||||
|
||||
int Canvas::height() const
|
||||
{
|
||||
return plutovg_surface_get_height(m_surface);
|
||||
}
|
||||
|
||||
void Canvas::convertToLuminanceMask()
|
||||
{
|
||||
auto width = plutovg_surface_get_width(m_surface);
|
||||
auto height = plutovg_surface_get_height(m_surface);
|
||||
auto stride = plutovg_surface_get_stride(m_surface);
|
||||
auto data = plutovg_surface_get_data(m_surface);
|
||||
for(int y = 0; y < height; y++) {
|
||||
auto pixels = reinterpret_cast<uint32_t*>(data + stride * y);
|
||||
for(int x = 0; x < width; x++) {
|
||||
auto pixel = pixels[x];
|
||||
auto a = (pixel >> 24) & 0xFF;
|
||||
auto r = (pixel >> 16) & 0xFF;
|
||||
auto g = (pixel >> 8) & 0xFF;
|
||||
auto b = (pixel >> 0) & 0xFF;
|
||||
if(a) {
|
||||
r = (r * 255) / a;
|
||||
g = (g * 255) / a;
|
||||
b = (b * 255) / a;
|
||||
}
|
||||
|
||||
auto l = (r * 0.2125 + g * 0.7154 + b * 0.0721);
|
||||
pixels[x] = static_cast<uint32_t>(l * (a / 255.0)) << 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
plutovg_canvas_destroy(m_canvas);
|
||||
plutovg_surface_destroy(m_surface);
|
||||
}
|
||||
|
||||
Canvas::Canvas(const Bitmap& bitmap)
|
||||
: m_surface(plutovg_surface_reference(bitmap.surface()))
|
||||
, m_canvas(plutovg_canvas_create(m_surface))
|
||||
, m_translation({1, 0, 0, 1, 0, 0})
|
||||
, m_x(0), m_y(0)
|
||||
{
|
||||
}
|
||||
|
||||
Canvas::Canvas(int x, int y, int width, int height)
|
||||
: m_surface(plutovg_surface_create(width, height))
|
||||
, m_canvas(plutovg_canvas_create(m_surface))
|
||||
, m_translation({1, 0, 0, 1, -static_cast<float>(x), -static_cast<float>(y)})
|
||||
, m_x(x), m_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
562
vendor/lunasvg/source/graphics.h
vendored
Normal file
562
vendor/lunasvg/source/graphics.h
vendored
Normal file
@@ -0,0 +1,562 @@
|
||||
#ifndef LUNASVG_GRAPHICS_H
|
||||
#define LUNASVG_GRAPHICS_H
|
||||
|
||||
#include <plutovg.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class LineCap : uint8_t {
|
||||
Butt = PLUTOVG_LINE_CAP_BUTT,
|
||||
Round = PLUTOVG_LINE_CAP_ROUND,
|
||||
Square = PLUTOVG_LINE_CAP_SQUARE
|
||||
};
|
||||
|
||||
enum class LineJoin : uint8_t {
|
||||
Miter = PLUTOVG_LINE_JOIN_MITER,
|
||||
Round = PLUTOVG_LINE_JOIN_ROUND,
|
||||
Bevel = PLUTOVG_LINE_JOIN_BEVEL
|
||||
};
|
||||
|
||||
enum class FillRule : uint8_t {
|
||||
NonZero = PLUTOVG_FILL_RULE_NON_ZERO,
|
||||
EvenOdd = PLUTOVG_FILL_RULE_EVEN_ODD
|
||||
};
|
||||
|
||||
enum class SpreadMethod : uint8_t {
|
||||
Pad = PLUTOVG_SPREAD_METHOD_PAD,
|
||||
Reflect = PLUTOVG_SPREAD_METHOD_REFLECT,
|
||||
Repeat = PLUTOVG_SPREAD_METHOD_REPEAT
|
||||
};
|
||||
|
||||
class Color {
|
||||
public:
|
||||
constexpr Color() = default;
|
||||
constexpr explicit Color(uint32_t value) : m_value(value) {}
|
||||
constexpr Color(int r, int g, int b, int a = 255) : m_value(a << 24 | r << 16 | g << 8 | b) {}
|
||||
|
||||
constexpr uint8_t alpha() const { return (m_value >> 24) & 0xff; }
|
||||
constexpr uint8_t red() const { return (m_value >> 16) & 0xff; }
|
||||
constexpr uint8_t green() const { return (m_value >> 8) & 0xff; }
|
||||
constexpr uint8_t blue() const { return (m_value >> 0) & 0xff; }
|
||||
|
||||
constexpr float alphaF() const { return alpha() / 255.f; }
|
||||
constexpr float redF() const { return red() / 255.f; }
|
||||
constexpr float greenF() const { return green() / 255.f; }
|
||||
constexpr float blueF() const { return blue() / 255.f; }
|
||||
|
||||
constexpr uint32_t value() const { return m_value; }
|
||||
|
||||
constexpr bool isOpaque() const { return alpha() == 255; }
|
||||
constexpr bool isVisible() const { return alpha() > 0; }
|
||||
|
||||
constexpr Color opaqueColor() const { return Color(m_value | 0xFF000000); }
|
||||
constexpr Color colorWithAlpha(float opacity) const;
|
||||
|
||||
static const Color Transparent;
|
||||
static const Color Black;
|
||||
static const Color White;
|
||||
|
||||
private:
|
||||
uint32_t m_value = 0;
|
||||
};
|
||||
|
||||
constexpr Color Color::colorWithAlpha(float opacity) const
|
||||
{
|
||||
auto rgb = m_value & 0x00FFFFFF;
|
||||
auto a = static_cast<int>(alpha() * std::clamp(opacity, 0.f, 1.f));
|
||||
return Color(rgb | a << 24);
|
||||
}
|
||||
|
||||
class Point {
|
||||
public:
|
||||
constexpr Point() = default;
|
||||
constexpr Point(const plutovg_point_t& point) : Point(point.x, point.y) {}
|
||||
constexpr Point(float x, float y) : x(x), y(y) {}
|
||||
|
||||
constexpr void move(float dx, float dy) { x += dx; y += dy; }
|
||||
constexpr void move(float d) { move(d, d); }
|
||||
constexpr void move(const Point& p) { move(p.x, p.y); }
|
||||
|
||||
constexpr void scale(float sx, float sy) { x *= sx; y *= sy; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
constexpr float dot(const Point& p) const { return x * p.x + y * p.y; }
|
||||
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
};
|
||||
|
||||
constexpr Point operator+(const Point& a, const Point& b)
|
||||
{
|
||||
return Point(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
constexpr Point operator-(const Point& a, const Point& b)
|
||||
{
|
||||
return Point(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
constexpr Point operator-(const Point& a)
|
||||
{
|
||||
return Point(-a.x, -a.y);
|
||||
}
|
||||
|
||||
constexpr Point& operator+=(Point& a, const Point& b)
|
||||
{
|
||||
a.move(b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr Point& operator-=(Point& a, const Point& b)
|
||||
{
|
||||
a.move(-b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr float operator*(const Point& a, const Point& b)
|
||||
{
|
||||
return a.dot(b);
|
||||
}
|
||||
|
||||
class Size {
|
||||
public:
|
||||
constexpr Size() = default;
|
||||
constexpr Size(float w, float h) : w(w), h(h) {}
|
||||
|
||||
constexpr void expand(float dw, float dh) { w += dw; h += dh; }
|
||||
constexpr void expand(float d) { expand(d, d); }
|
||||
constexpr void expand(const Size& s) { expand(s.w, s.h); }
|
||||
|
||||
constexpr void scale(float sw, float sh) { w *= sw; h *= sh; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; }
|
||||
constexpr bool isZero() const { return w <= 0.f && h <= 0.f; }
|
||||
constexpr bool isValid() const { return w >= 0.f && h >= 0.f; }
|
||||
|
||||
public:
|
||||
float w{0};
|
||||
float h{0};
|
||||
};
|
||||
|
||||
constexpr Size operator+(const Size& a, const Size& b)
|
||||
{
|
||||
return Size(a.w + b.w, a.h + b.h);
|
||||
}
|
||||
|
||||
constexpr Size operator-(const Size& a, const Size& b)
|
||||
{
|
||||
return Size(a.w - b.w, a.h - b.h);
|
||||
}
|
||||
|
||||
constexpr Size operator-(const Size& a)
|
||||
{
|
||||
return Size(-a.w, -a.h);
|
||||
}
|
||||
|
||||
constexpr Size& operator+=(Size& a, const Size& b)
|
||||
{
|
||||
a.expand(b);
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr Size& operator-=(Size& a, const Size& b)
|
||||
{
|
||||
a.expand(-b);
|
||||
return a;
|
||||
}
|
||||
|
||||
class Box;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
constexpr Rect() = default;
|
||||
constexpr explicit Rect(const Size& size) : Rect(size.w, size.h) {}
|
||||
constexpr Rect(float width, float height) : Rect(0, 0, width, height) {}
|
||||
constexpr Rect(const Point& origin, const Size& size) : Rect(origin.x, origin.y, size.w, size.h) {}
|
||||
constexpr Rect(const plutovg_rect_t& rect) : Rect(rect.x, rect.y, rect.w, rect.h) {}
|
||||
constexpr Rect(float x, float y, float w, float h) : x(x), y(y), w(w), h(h) {}
|
||||
|
||||
Rect(const Box& box);
|
||||
|
||||
constexpr void move(float dx, float dy) { x += dx; y += dy; }
|
||||
constexpr void move(float d) { move(d, d); }
|
||||
constexpr void move(const Point& p) { move(p.x, p.y); }
|
||||
|
||||
constexpr void scale(float sx, float sy) { x *= sx; y *= sy; w *= sx; h *= sy; }
|
||||
constexpr void scale(float s) { scale(s, s); }
|
||||
|
||||
constexpr void inflate(float dx, float dy) { x -= dx; y -= dy; w += dx * 2.f; h += dy * 2.f; }
|
||||
constexpr void inflate(float d) { inflate(d, d); }
|
||||
|
||||
constexpr bool contains(float px, float py) const { return px >= x && px <= x + w && py >= y && py <= y + h; }
|
||||
constexpr bool contains(const Point& p) const { return contains(p.x, p.y); }
|
||||
|
||||
constexpr Rect intersected(const Rect& rect) const;
|
||||
constexpr Rect united(const Rect& rect) const;
|
||||
|
||||
constexpr Rect& intersect(const Rect& o);
|
||||
constexpr Rect& unite(const Rect& o);
|
||||
|
||||
constexpr Point origin() const { return Point(x, y); }
|
||||
constexpr Size size() const { return Size(w, h); }
|
||||
|
||||
constexpr float right() const { return x + w; }
|
||||
constexpr float bottom() const { return y + h; }
|
||||
|
||||
constexpr bool isEmpty() const { return w <= 0.f || h <= 0.f; }
|
||||
constexpr bool isZero() const { return w <= 0.f && h <= 0.f; }
|
||||
constexpr bool isValid() const { return w >= 0.f && h >= 0.f; }
|
||||
|
||||
static const Rect Empty;
|
||||
static const Rect Invalid;
|
||||
static const Rect Infinite;
|
||||
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
float w{0};
|
||||
float h{0};
|
||||
};
|
||||
|
||||
constexpr Rect Rect::intersected(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid())
|
||||
return *this;
|
||||
if(!isValid())
|
||||
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);
|
||||
if(l >= r || t >= b)
|
||||
return Rect::Empty;
|
||||
return Rect(l, t, r - l, b - t);
|
||||
}
|
||||
|
||||
constexpr Rect Rect::united(const Rect& rect) const
|
||||
{
|
||||
if(!rect.isValid())
|
||||
return *this;
|
||||
if(!isValid())
|
||||
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);
|
||||
}
|
||||
|
||||
constexpr Rect& Rect::intersect(const Rect& o)
|
||||
{
|
||||
*this = intersected(o);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Rect& Rect::unite(const Rect& o)
|
||||
{
|
||||
*this = united(o);
|
||||
return *this;
|
||||
}
|
||||
|
||||
class Matrix;
|
||||
|
||||
class Transform {
|
||||
public:
|
||||
Transform();
|
||||
Transform(const Matrix& matrix);
|
||||
Transform(float a, float b, float c, float d, float e, float f);
|
||||
Transform(const plutovg_matrix_t& matrix) : m_matrix(matrix) {}
|
||||
|
||||
Transform operator*(const Transform& transform) const;
|
||||
Transform& operator*=(const Transform& transform);
|
||||
|
||||
Transform& multiply(const Transform& transform);
|
||||
Transform& translate(float tx, float ty);
|
||||
Transform& scale(float sx, float sy);
|
||||
Transform& rotate(float angle, float cx = 0.f, float cy = 0.f);
|
||||
Transform& shear(float shx, float shy);
|
||||
|
||||
Transform& postMultiply(const Transform& transform);
|
||||
Transform& postTranslate(float tx, float ty);
|
||||
Transform& postScale(float sx, float sy);
|
||||
Transform& postRotate(float angle, float cx = 0.f, float cy = 0.f);
|
||||
Transform& postShear(float shx, float shy);
|
||||
|
||||
Transform inverse() const;
|
||||
Transform& invert();
|
||||
|
||||
void reset();
|
||||
|
||||
Point mapPoint(float x, float y) const;
|
||||
Point mapPoint(const Point& point) const;
|
||||
Rect mapRect(const Rect& rect) const;
|
||||
|
||||
float xScale() const;
|
||||
float yScale() const;
|
||||
|
||||
const plutovg_matrix_t& matrix() const { return m_matrix; }
|
||||
plutovg_matrix_t& matrix() { return m_matrix; }
|
||||
|
||||
bool parse(const char* data, size_t length);
|
||||
|
||||
static Transform translated(float tx, float ty);
|
||||
static Transform scaled(float sx, float sy);
|
||||
static Transform rotated(float angle, float cx, float cy);
|
||||
static Transform sheared(float shx, float shy);
|
||||
|
||||
static const Transform Identity;
|
||||
|
||||
private:
|
||||
plutovg_matrix_t m_matrix;
|
||||
};
|
||||
|
||||
enum class PathCommand {
|
||||
MoveTo = PLUTOVG_PATH_COMMAND_MOVE_TO,
|
||||
LineTo = PLUTOVG_PATH_COMMAND_LINE_TO,
|
||||
CubicTo = PLUTOVG_PATH_COMMAND_CUBIC_TO,
|
||||
Close = PLUTOVG_PATH_COMMAND_CLOSE
|
||||
};
|
||||
|
||||
class Path {
|
||||
public:
|
||||
Path() = default;
|
||||
Path(const Path& path);
|
||||
Path(Path&& path);
|
||||
~Path();
|
||||
|
||||
Path& operator=(const Path& path);
|
||||
Path& operator=(Path&& path);
|
||||
|
||||
void swap(Path& path);
|
||||
|
||||
void moveTo(float x, float y);
|
||||
void lineTo(float x, float y);
|
||||
void quadTo(float x1, float y1, float x2, float y2);
|
||||
void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3);
|
||||
void arcTo(float rx, float ry, float xAxisRotation, bool largeArcFlag, bool sweepFlag, float x, float y);
|
||||
void close();
|
||||
|
||||
void addEllipse(float cx, float cy, float rx, float ry);
|
||||
void addRoundRect(float x, float y, float w, float h, float rx, float ry);
|
||||
void addRect(float x, float y, float w, float h);
|
||||
|
||||
void addEllipse(const Point& center, const Size& radii);
|
||||
void addRoundRect(const Rect& rect, const Size& radii);
|
||||
void addRect(const Rect& rect);
|
||||
|
||||
void reset();
|
||||
|
||||
Rect boundingRect() const;
|
||||
bool isEmpty() const;
|
||||
bool isUnique() const;
|
||||
bool isNull() const { return m_data == nullptr; }
|
||||
plutovg_path_t* data() const { return m_data; }
|
||||
|
||||
bool parse(const char* data, size_t length);
|
||||
|
||||
private:
|
||||
plutovg_path_t* release();
|
||||
plutovg_path_t* ensure();
|
||||
plutovg_path_t* m_data = nullptr;
|
||||
};
|
||||
|
||||
inline void Path::swap(Path& path)
|
||||
{
|
||||
std::swap(m_data, path.m_data);
|
||||
}
|
||||
|
||||
inline plutovg_path_t* Path::release()
|
||||
{
|
||||
return std::exchange(m_data, nullptr);
|
||||
}
|
||||
|
||||
class PathIterator {
|
||||
public:
|
||||
PathIterator(const Path& path);
|
||||
|
||||
PathCommand currentSegment(std::array<Point, 3>& points) const;
|
||||
bool isDone() const { return m_index >= m_size; }
|
||||
void next();
|
||||
|
||||
private:
|
||||
const plutovg_path_element_t* m_elements;
|
||||
const int m_size;
|
||||
int m_index;
|
||||
};
|
||||
|
||||
class FontFace {
|
||||
public:
|
||||
FontFace() = default;
|
||||
explicit FontFace(plutovg_font_face_t* face);
|
||||
FontFace(const void* data, size_t length, plutovg_destroy_func_t destroy_func, void* closure);
|
||||
FontFace(const char* filename);
|
||||
FontFace(const FontFace& face);
|
||||
FontFace(FontFace&& face);
|
||||
~FontFace();
|
||||
|
||||
FontFace& operator=(const FontFace& face);
|
||||
FontFace& operator=(FontFace&& face);
|
||||
|
||||
void swap(FontFace& face);
|
||||
|
||||
bool isNull() const { return m_face == nullptr; }
|
||||
plutovg_font_face_t* get() const { return m_face; }
|
||||
|
||||
private:
|
||||
plutovg_font_face_t* release();
|
||||
plutovg_font_face_t* m_face = nullptr;
|
||||
};
|
||||
|
||||
class FontFaceCache {
|
||||
public:
|
||||
bool addFontFace(const std::string& family, bool bold, bool italic, const FontFace& face);
|
||||
FontFace getFontFace(const std::string& family, bool bold, bool italic) const;
|
||||
|
||||
private:
|
||||
FontFaceCache();
|
||||
plutovg_font_face_cache_t* m_cache;
|
||||
friend FontFaceCache* fontFaceCache();
|
||||
};
|
||||
|
||||
FontFaceCache* fontFaceCache();
|
||||
|
||||
class Font {
|
||||
public:
|
||||
Font() = default;
|
||||
Font(const FontFace& face, float size);
|
||||
|
||||
float ascent() const { return m_ascent; }
|
||||
float descent() const { return m_descent; }
|
||||
float height() const { return m_ascent - m_descent; }
|
||||
float lineGap() const { return m_lineGap; }
|
||||
float xHeight() const;
|
||||
|
||||
float measureText(const std::u32string_view& text) const;
|
||||
|
||||
const FontFace& face() const { return m_face; }
|
||||
float size() const { return m_size; }
|
||||
|
||||
bool isNull() const { return m_size <= 0.f || m_face.isNull(); }
|
||||
|
||||
private:
|
||||
FontFace m_face;
|
||||
float m_size = 0.f;
|
||||
float m_ascent = 0.f;
|
||||
float m_descent = 0.f;
|
||||
float m_lineGap = 0.f;
|
||||
};
|
||||
|
||||
enum class TextureType {
|
||||
Plain = PLUTOVG_TEXTURE_TYPE_PLAIN,
|
||||
Tiled = PLUTOVG_TEXTURE_TYPE_TILED
|
||||
};
|
||||
|
||||
enum class BlendMode {
|
||||
Src = PLUTOVG_OPERATOR_SRC,
|
||||
Src_Over = PLUTOVG_OPERATOR_SRC_OVER,
|
||||
Dst_In = PLUTOVG_OPERATOR_DST_IN,
|
||||
Dst_Out = PLUTOVG_OPERATOR_DST_OUT
|
||||
};
|
||||
|
||||
using DashArray = std::vector<float>;
|
||||
|
||||
class StrokeData {
|
||||
public:
|
||||
explicit StrokeData(float lineWidth = 1.f) : m_lineWidth(lineWidth) {}
|
||||
|
||||
void setLineWidth(float lineWidth) { m_lineWidth = lineWidth; }
|
||||
float lineWidth() const { return m_lineWidth; }
|
||||
|
||||
void setMiterLimit(float miterLimit) { m_miterLimit = miterLimit; }
|
||||
float miterLimit() const { return m_miterLimit; }
|
||||
|
||||
void setDashOffset(float dashOffset) { m_dashOffset = dashOffset; }
|
||||
float dashOffset() const { return m_dashOffset; }
|
||||
|
||||
void setDashArray(DashArray dashArray) { m_dashArray = std::move(dashArray); }
|
||||
const DashArray& dashArray() const { return m_dashArray; }
|
||||
|
||||
void setLineCap(LineCap lineCap) { m_lineCap = lineCap; }
|
||||
LineCap lineCap() const { return m_lineCap; }
|
||||
|
||||
void setLineJoin(LineJoin lineJoin) { m_lineJoin = lineJoin; }
|
||||
LineJoin lineJoin() const { return m_lineJoin; }
|
||||
|
||||
private:
|
||||
float m_lineWidth;
|
||||
float m_miterLimit{4.f};
|
||||
float m_dashOffset{0.f};
|
||||
LineCap m_lineCap{LineCap::Butt};
|
||||
LineJoin m_lineJoin{LineJoin::Miter};
|
||||
DashArray m_dashArray;
|
||||
};
|
||||
|
||||
using GradientStop = plutovg_gradient_stop_t;
|
||||
using GradientStops = std::vector<GradientStop>;
|
||||
|
||||
class Bitmap;
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
static std::shared_ptr<Canvas> create(const Bitmap& bitmap);
|
||||
static std::shared_ptr<Canvas> create(float x, float y, float width, float height);
|
||||
static std::shared_ptr<Canvas> create(const Rect& extents);
|
||||
|
||||
void setColor(const Color& color);
|
||||
void setColor(float r, float g, float b, float a);
|
||||
void setLinearGradient(float x1, float y1, float x2, float y2, SpreadMethod spread, const GradientStops& stops, const Transform& transform);
|
||||
void setRadialGradient(float cx, float cy, float r, float fx, float fy, SpreadMethod spread, const GradientStops& stops, const Transform& transform);
|
||||
void setTexture(const Canvas& source, TextureType type, float opacity, const Transform& transform);
|
||||
|
||||
void fillPath(const Path& path, FillRule fillRule, const Transform& transform);
|
||||
void strokePath(const Path& path, const StrokeData& strokeData, const Transform& transform);
|
||||
|
||||
void fillText(const std::u32string_view& text, const Font& font, const Point& origin, const Transform& transform);
|
||||
void strokeText(const std::u32string_view& text, float strokeWidth, const Font& font, const Point& origin, const Transform& transform);
|
||||
|
||||
void clipPath(const Path& path, FillRule clipRule, const Transform& transform);
|
||||
void clipRect(const Rect& rect, FillRule clipRule, const Transform& transform);
|
||||
|
||||
void drawImage(const Bitmap& image, const Rect& dstRect, const Rect& srcRect, const Transform& transform);
|
||||
void blendCanvas(const Canvas& canvas, BlendMode blendMode, float opacity);
|
||||
|
||||
void save();
|
||||
void restore();
|
||||
|
||||
void convertToLuminanceMask();
|
||||
|
||||
int x() const { return m_x; }
|
||||
int y() const { return m_y; }
|
||||
int width() const;
|
||||
int height() const;
|
||||
|
||||
Rect extents() const { return Rect(m_x, m_y, width(), height()); }
|
||||
|
||||
plutovg_surface_t* surface() const { return m_surface; }
|
||||
plutovg_canvas_t* canvas() const { return m_canvas; }
|
||||
|
||||
~Canvas();
|
||||
|
||||
private:
|
||||
Canvas(const Bitmap& bitmap);
|
||||
Canvas(int x, int y, int width, int height);
|
||||
plutovg_surface_t* m_surface;
|
||||
plutovg_canvas_t* m_canvas;
|
||||
plutovg_matrix_t m_translation;
|
||||
const int m_x;
|
||||
const int m_y;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_GRAPHICS_H
|
||||
536
vendor/lunasvg/source/lunasvg.cpp
vendored
Normal file
536
vendor/lunasvg/source/lunasvg.cpp
vendored
Normal file
@@ -0,0 +1,536 @@
|
||||
#include "lunasvg.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <cmath>
|
||||
|
||||
int lunasvg_version()
|
||||
{
|
||||
return LUNASVG_VERSION;
|
||||
}
|
||||
|
||||
const char* lunasvg_version_string()
|
||||
{
|
||||
return LUNASVG_VERSION_STRING;
|
||||
}
|
||||
|
||||
bool lunasvg_add_font_face_from_file(const char* family, bool bold, bool italic, const char* filename)
|
||||
{
|
||||
return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(filename));
|
||||
}
|
||||
|
||||
bool lunasvg_add_font_face_from_data(const char* family, bool bold, bool italic, const void* data, size_t length, lunasvg_destroy_func_t destroy_func, void* closure)
|
||||
{
|
||||
return lunasvg::fontFaceCache()->addFontFace(family, bold, italic, lunasvg::FontFace(data, length, destroy_func, closure));
|
||||
}
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
Bitmap::Bitmap(int width, int height)
|
||||
: m_surface(plutovg_surface_create(width, height))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(uint8_t* data, int width, int height, int stride)
|
||||
: m_surface(plutovg_surface_create_for_data(data, width, height, stride))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(const Bitmap& bitmap)
|
||||
: m_surface(plutovg_surface_reference(bitmap.surface()))
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(Bitmap&& bitmap)
|
||||
: m_surface(bitmap.release())
|
||||
{
|
||||
}
|
||||
|
||||
Bitmap::~Bitmap()
|
||||
{
|
||||
plutovg_surface_destroy(m_surface);
|
||||
}
|
||||
|
||||
Bitmap& Bitmap::operator=(const Bitmap& bitmap)
|
||||
{
|
||||
Bitmap(bitmap).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Bitmap::swap(Bitmap& bitmap)
|
||||
{
|
||||
std::swap(m_surface, bitmap.m_surface);
|
||||
}
|
||||
|
||||
uint8_t* Bitmap::data() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_data(m_surface);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Bitmap::width() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_width(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Bitmap::height() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_height(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Bitmap::stride() const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_get_stride(m_surface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Bitmap::clear(uint32_t value)
|
||||
{
|
||||
if(m_surface == nullptr)
|
||||
return;
|
||||
plutovg_color_t color;
|
||||
plutovg_color_init_rgba32(&color, value);
|
||||
plutovg_surface_clear(m_surface, &color);
|
||||
}
|
||||
|
||||
void Bitmap::convertToRGBA()
|
||||
{
|
||||
if(m_surface == nullptr)
|
||||
return;
|
||||
auto data = plutovg_surface_get_data(m_surface);
|
||||
auto width = plutovg_surface_get_width(m_surface);
|
||||
auto height = plutovg_surface_get_height(m_surface);
|
||||
auto stride = plutovg_surface_get_stride(m_surface);
|
||||
plutovg_convert_argb_to_rgba(data, data, width, height, stride);
|
||||
}
|
||||
|
||||
Bitmap& Bitmap::operator=(Bitmap&& bitmap)
|
||||
{
|
||||
Bitmap(std::move(bitmap)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Bitmap::writeToPng(const std::string& filename) const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_write_to_png(m_surface, filename.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bitmap::writeToPng(lunasvg_write_func_t callback, void* closure) const
|
||||
{
|
||||
if(m_surface)
|
||||
return plutovg_surface_write_to_png_stream(m_surface, callback, closure);
|
||||
return false;
|
||||
}
|
||||
|
||||
plutovg_surface_t* Bitmap::release()
|
||||
{
|
||||
return std::exchange(m_surface, nullptr);
|
||||
}
|
||||
|
||||
Box::Box(float x, float y, float w, float h)
|
||||
: x(x), y(y), w(w), h(h)
|
||||
{
|
||||
}
|
||||
|
||||
Box::Box(const Rect& rect)
|
||||
: x(rect.x), y(rect.y), w(rect.w), h(rect.h)
|
||||
{
|
||||
}
|
||||
|
||||
Box& Box::transform(const Matrix &matrix)
|
||||
{
|
||||
*this = transformed(matrix);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Box Box::transformed(const Matrix& matrix) const
|
||||
{
|
||||
return Transform(matrix).mapRect(*this);
|
||||
}
|
||||
|
||||
Matrix::Matrix(float a, float b, float c, float d, float e, float f)
|
||||
: a(a), b(b), c(c), d(d), e(e), f(f)
|
||||
{
|
||||
}
|
||||
|
||||
Matrix::Matrix(const plutovg_matrix_t& matrix)
|
||||
: a(matrix.a), b(matrix.b), c(matrix.c), d(matrix.d), e(matrix.e), f(matrix.f)
|
||||
{
|
||||
}
|
||||
|
||||
Matrix::Matrix(const Transform& transform)
|
||||
: Matrix(transform.matrix())
|
||||
{
|
||||
}
|
||||
|
||||
Matrix Matrix::operator*(const Matrix& matrix) const
|
||||
{
|
||||
return Transform(*this) * Transform(matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::operator*=(const Matrix &matrix)
|
||||
{
|
||||
return (*this = *this * matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::multiply(const Matrix& matrix)
|
||||
{
|
||||
return (*this *= matrix);
|
||||
}
|
||||
|
||||
Matrix& Matrix::scale(float sx, float sy)
|
||||
{
|
||||
return multiply(scaled(sx, sy));
|
||||
}
|
||||
|
||||
Matrix& Matrix::translate(float tx, float ty)
|
||||
{
|
||||
return multiply(translated(tx, ty));
|
||||
}
|
||||
|
||||
Matrix& Matrix::rotate(float angle, float cx, float cy)
|
||||
{
|
||||
return multiply(rotated(angle, cx, cy));
|
||||
}
|
||||
|
||||
Matrix& Matrix::shear(float shx, float shy)
|
||||
{
|
||||
return multiply(sheared(shx, shy));
|
||||
}
|
||||
|
||||
Matrix Matrix::inverse() const
|
||||
{
|
||||
return Transform(*this).inverse();
|
||||
}
|
||||
|
||||
Matrix& Matrix::invert()
|
||||
{
|
||||
return (*this = inverse());
|
||||
}
|
||||
|
||||
void Matrix::reset()
|
||||
{
|
||||
*this = Matrix(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
Matrix Matrix::translated(float tx, float ty)
|
||||
{
|
||||
return Transform::translated(tx, ty);
|
||||
}
|
||||
|
||||
Matrix Matrix::scaled(float sx, float sy)
|
||||
{
|
||||
return Transform::scaled(sx, sy);
|
||||
}
|
||||
|
||||
Matrix Matrix::rotated(float angle, float cx, float cy)
|
||||
{
|
||||
return Transform::rotated(angle, cx, cy);
|
||||
}
|
||||
|
||||
Matrix Matrix::sheared(float shx, float shy)
|
||||
{
|
||||
return Transform::sheared(shx, shy);
|
||||
}
|
||||
|
||||
Node::Node(SVGNode* node)
|
||||
: m_node(node)
|
||||
{
|
||||
}
|
||||
|
||||
bool Node::isTextNode() const
|
||||
{
|
||||
return m_node && m_node->isTextNode();
|
||||
}
|
||||
|
||||
bool Node::isElement() const
|
||||
{
|
||||
return m_node && m_node->isElement();
|
||||
}
|
||||
|
||||
TextNode Node::toTextNode() const
|
||||
{
|
||||
if(m_node && m_node->isTextNode())
|
||||
return static_cast<SVGTextNode*>(m_node);
|
||||
return TextNode();
|
||||
}
|
||||
|
||||
Element Node::toElement() const
|
||||
{
|
||||
if(m_node && m_node->isElement())
|
||||
return static_cast<SVGElement*>(m_node);
|
||||
return Element();
|
||||
}
|
||||
|
||||
Element Node::parentElement() const
|
||||
{
|
||||
if(m_node)
|
||||
return m_node->parentElement();
|
||||
return Element();
|
||||
}
|
||||
|
||||
TextNode::TextNode(SVGTextNode* text)
|
||||
: Node(text)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& TextNode::data() const
|
||||
{
|
||||
if(m_node)
|
||||
return text()->data();
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
void TextNode::setData(const std::string& data)
|
||||
{
|
||||
if(m_node) {
|
||||
text()->setData(data);
|
||||
}
|
||||
}
|
||||
|
||||
SVGTextNode* TextNode::text() const
|
||||
{
|
||||
return static_cast<SVGTextNode*>(m_node);
|
||||
}
|
||||
|
||||
Element::Element(SVGElement* element)
|
||||
: Node(element)
|
||||
{
|
||||
}
|
||||
|
||||
bool Element::hasAttribute(const std::string& name) const
|
||||
{
|
||||
if(m_node)
|
||||
return element()->hasAttribute(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& Element::getAttribute(const std::string& name) const
|
||||
{
|
||||
if(m_node)
|
||||
return element()->getAttribute(name);
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
void Element::setAttribute(const std::string& name, const std::string& value)
|
||||
{
|
||||
if(m_node) {
|
||||
element()->setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::render(Bitmap& bitmap, const Matrix& matrix) const
|
||||
{
|
||||
if(m_node == nullptr || bitmap.isNull())
|
||||
return;
|
||||
auto canvas = Canvas::create(bitmap);
|
||||
SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas);
|
||||
element(true)->render(state);
|
||||
}
|
||||
|
||||
Bitmap Element::renderToBitmap(int width, int height, uint32_t backgroundColor) const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return Bitmap();
|
||||
auto elementBounds = element(true)->localTransform().mapRect(element()->paintBoundingBox());
|
||||
if(elementBounds.isEmpty())
|
||||
return Bitmap();
|
||||
if(width <= 0 && height <= 0) {
|
||||
width = static_cast<int>(std::ceil(elementBounds.w));
|
||||
height = static_cast<int>(std::ceil(elementBounds.h));
|
||||
} else if(width > 0 && height <= 0) {
|
||||
height = static_cast<int>(std::ceil(width * elementBounds.h / elementBounds.w));
|
||||
} else if(height > 0 && width <= 0) {
|
||||
width = static_cast<int>(std::ceil(height * elementBounds.w / elementBounds.h));
|
||||
}
|
||||
|
||||
auto xScale = width / elementBounds.w;
|
||||
auto yScale = height / elementBounds.h;
|
||||
|
||||
Matrix matrix(xScale, 0, 0, yScale, -elementBounds.x * xScale, -elementBounds.y * yScale);
|
||||
Bitmap bitmap(width, height);
|
||||
if(backgroundColor) bitmap.clear(backgroundColor);
|
||||
render(bitmap, matrix);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
Matrix Element::getLocalMatrix() const
|
||||
{
|
||||
if(m_node)
|
||||
return element(true)->localTransform();
|
||||
return Matrix();
|
||||
}
|
||||
|
||||
Matrix Element::getGlobalMatrix() const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return Matrix();
|
||||
auto transform = element(true)->localTransform();
|
||||
for(auto parent = element()->parentElement(); parent; parent = parent->parentElement())
|
||||
transform.postMultiply(parent->localTransform());
|
||||
return transform;
|
||||
}
|
||||
|
||||
Box Element::getLocalBoundingBox() const
|
||||
{
|
||||
return getBoundingBox().transformed(getLocalMatrix());
|
||||
}
|
||||
|
||||
Box Element::getGlobalBoundingBox() const
|
||||
{
|
||||
return getBoundingBox().transformed(getGlobalMatrix());
|
||||
}
|
||||
|
||||
Box Element::getBoundingBox() const
|
||||
{
|
||||
if(m_node)
|
||||
return element(true)->paintBoundingBox();
|
||||
return Box();
|
||||
}
|
||||
|
||||
NodeList Element::children() const
|
||||
{
|
||||
if(m_node == nullptr)
|
||||
return NodeList();
|
||||
NodeList children;
|
||||
for(const auto& child : element()->children())
|
||||
children.push_back(child.get());
|
||||
return children;
|
||||
}
|
||||
|
||||
SVGElement* Element::element(bool layoutIfNeeded) const
|
||||
{
|
||||
auto element = static_cast<SVGElement*>(m_node);
|
||||
if(element && layoutIfNeeded)
|
||||
element->rootElement()->layoutIfNeeded();
|
||||
return element;
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromFile(const std::string& filename)
|
||||
{
|
||||
std::ifstream fs;
|
||||
fs.open(filename);
|
||||
if(!fs.is_open())
|
||||
return nullptr;
|
||||
std::string content;
|
||||
std::getline(fs, content, '\0');
|
||||
fs.close();
|
||||
return loadFromData(content);
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const std::string& string)
|
||||
{
|
||||
return loadFromData(string.data(), string.size());
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const char* data)
|
||||
{
|
||||
return loadFromData(data, std::strlen(data));
|
||||
}
|
||||
|
||||
std::unique_ptr<Document> Document::loadFromData(const char* data, size_t length)
|
||||
{
|
||||
std::unique_ptr<Document> document(new Document);
|
||||
if(!document->parse(data, length))
|
||||
return nullptr;
|
||||
return document;
|
||||
}
|
||||
|
||||
float Document::width() const
|
||||
{
|
||||
return rootElement(true)->intrinsicWidth();
|
||||
}
|
||||
|
||||
float Document::height() const
|
||||
{
|
||||
return rootElement(true)->intrinsicHeight();
|
||||
}
|
||||
|
||||
Box Document::boundingBox() const
|
||||
{
|
||||
return rootElement(true)->localTransform().mapRect(rootElement()->paintBoundingBox());
|
||||
}
|
||||
|
||||
void Document::updateLayout()
|
||||
{
|
||||
m_rootElement->layoutIfNeeded();
|
||||
}
|
||||
|
||||
void Document::forceLayout()
|
||||
{
|
||||
m_rootElement->forceLayout();
|
||||
}
|
||||
|
||||
void Document::render(Bitmap& bitmap, const Matrix& matrix) const
|
||||
{
|
||||
if(bitmap.isNull())
|
||||
return;
|
||||
auto canvas = Canvas::create(bitmap);
|
||||
SVGRenderState state(nullptr, nullptr, matrix, SVGRenderMode::Painting, canvas);
|
||||
rootElement(true)->render(state);
|
||||
}
|
||||
|
||||
Bitmap Document::renderToBitmap(int width, int height, uint32_t backgroundColor) const
|
||||
{
|
||||
auto intrinsicWidth = rootElement(true)->intrinsicWidth();
|
||||
auto intrinsicHeight = rootElement()->intrinsicHeight();
|
||||
if(intrinsicWidth == 0.f || intrinsicHeight == 0.f)
|
||||
return Bitmap();
|
||||
if(width <= 0 && height <= 0) {
|
||||
width = static_cast<int>(std::ceil(intrinsicWidth));
|
||||
height = static_cast<int>(std::ceil(intrinsicHeight));
|
||||
} else if(width > 0 && height <= 0) {
|
||||
height = static_cast<int>(std::ceil(width * intrinsicHeight / intrinsicWidth));
|
||||
} else if(height > 0 && width <= 0) {
|
||||
width = static_cast<int>(std::ceil(height * intrinsicWidth / intrinsicHeight));
|
||||
}
|
||||
|
||||
auto xScale = width / intrinsicWidth;
|
||||
auto yScale = height / intrinsicHeight;
|
||||
|
||||
Matrix matrix(xScale, 0, 0, yScale, 0, 0);
|
||||
Bitmap bitmap(width, height);
|
||||
if(backgroundColor) bitmap.clear(backgroundColor);
|
||||
render(bitmap, matrix);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
Element Document::elementFromPoint(float x, float y) const
|
||||
{
|
||||
return rootElement(true)->elementFromPoint(x, y);
|
||||
}
|
||||
|
||||
Element Document::getElementById(const std::string& id) const
|
||||
{
|
||||
return m_rootElement->getElementById(id);
|
||||
}
|
||||
|
||||
Element Document::documentElement() const
|
||||
{
|
||||
return m_rootElement.get();
|
||||
}
|
||||
|
||||
SVGRootElement* Document::rootElement(bool layoutIfNeeded) const
|
||||
{
|
||||
if(layoutIfNeeded)
|
||||
m_rootElement->layoutIfNeeded();
|
||||
return m_rootElement.get();
|
||||
}
|
||||
|
||||
Document::Document(Document&&) = default;
|
||||
Document& Document::operator=(Document&&) = default;
|
||||
|
||||
Document::Document() = default;
|
||||
Document::~Document() = default;
|
||||
|
||||
} // namespace lunasvg
|
||||
1225
vendor/lunasvg/source/svgelement.cpp
vendored
Normal file
1225
vendor/lunasvg/source/svgelement.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
498
vendor/lunasvg/source/svgelement.h
vendored
Normal file
498
vendor/lunasvg/source/svgelement.h
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
#ifndef LUNASVG_SVGELEMENT_H
|
||||
#define LUNASVG_SVGELEMENT_H
|
||||
|
||||
#include "lunasvg.h"
|
||||
#include "svgproperty.h"
|
||||
|
||||
#include <string>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class Document;
|
||||
class SVGElement;
|
||||
class SVGRootElement;
|
||||
|
||||
class SVGNode {
|
||||
public:
|
||||
SVGNode(Document* document)
|
||||
: m_document(document)
|
||||
{}
|
||||
|
||||
virtual ~SVGNode() = default;
|
||||
virtual bool isTextNode() const { return false; }
|
||||
virtual bool isElement() const { return false; }
|
||||
virtual bool isPaintElement() const { return false; }
|
||||
virtual bool isGraphicsElement() const { return false; }
|
||||
virtual bool isGeometryElement() const { return false; }
|
||||
virtual bool isTextPositioningElement() const { return false; }
|
||||
|
||||
Document* document() const { return m_document; }
|
||||
SVGRootElement* rootElement() const { return m_document->rootElement(); }
|
||||
|
||||
SVGElement* parentElement() const { return m_parentElement; }
|
||||
void setParentElement(SVGElement* parent) { m_parentElement = parent; }
|
||||
|
||||
bool isRootElement() const { return m_parentElement == nullptr; }
|
||||
|
||||
virtual std::unique_ptr<SVGNode> clone(bool deep) const = 0;
|
||||
|
||||
private:
|
||||
SVGNode(const SVGNode&) = delete;
|
||||
SVGNode& operator=(const SVGNode&) = delete;
|
||||
Document* m_document;
|
||||
SVGElement* m_parentElement = nullptr;
|
||||
};
|
||||
|
||||
class SVGTextNode final : public SVGNode {
|
||||
public:
|
||||
SVGTextNode(Document* document);
|
||||
|
||||
bool isTextNode() const final { return true; }
|
||||
|
||||
const std::string& data() const { return m_data; }
|
||||
void setData(const std::string& data);
|
||||
|
||||
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
};
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
Attribute() = default;
|
||||
Attribute(int specificity, PropertyID id, std::string value)
|
||||
: m_specificity(specificity), m_id(id), m_value(std::move(value))
|
||||
{}
|
||||
|
||||
int specificity() const { return m_specificity; }
|
||||
PropertyID id() const { return m_id; }
|
||||
const std::string& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
int m_specificity;
|
||||
PropertyID m_id;
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
using AttributeList = std::forward_list<Attribute>;
|
||||
|
||||
enum class ElementID : uint8_t {
|
||||
Unknown = 0,
|
||||
Star,
|
||||
Circle,
|
||||
ClipPath,
|
||||
Defs,
|
||||
Ellipse,
|
||||
G,
|
||||
Image,
|
||||
Line,
|
||||
LinearGradient,
|
||||
Marker,
|
||||
Mask,
|
||||
Path,
|
||||
Pattern,
|
||||
Polygon,
|
||||
Polyline,
|
||||
RadialGradient,
|
||||
Rect,
|
||||
Stop,
|
||||
Style,
|
||||
Svg,
|
||||
Symbol,
|
||||
Text,
|
||||
Tspan,
|
||||
Use
|
||||
};
|
||||
|
||||
ElementID elementid(std::string_view name);
|
||||
|
||||
using SVGNodeList = std::list<std::unique_ptr<SVGNode>>;
|
||||
using SVGPropertyList = std::forward_list<SVGProperty*>;
|
||||
|
||||
class SVGMarkerElement;
|
||||
class SVGClipPathElement;
|
||||
class SVGMaskElement;
|
||||
class SVGPaintElement;
|
||||
class SVGLayoutState;
|
||||
class SVGRenderState;
|
||||
|
||||
extern const std::string emptyString;
|
||||
|
||||
class SVGElement : public SVGNode {
|
||||
public:
|
||||
static std::unique_ptr<SVGElement> create(Document* document, ElementID id);
|
||||
|
||||
SVGElement(Document* document, ElementID id);
|
||||
virtual ~SVGElement() = default;
|
||||
|
||||
bool hasAttribute(std::string_view name) const;
|
||||
const std::string& getAttribute(std::string_view name) const;
|
||||
bool setAttribute(std::string_view name, const std::string& value);
|
||||
|
||||
const Attribute* findAttribute(PropertyID id) const;
|
||||
bool hasAttribute(PropertyID id) const;
|
||||
const std::string& getAttribute(PropertyID id) const;
|
||||
bool setAttribute(int specificity, PropertyID id, const std::string& value);
|
||||
void setAttributes(const AttributeList& attributes);
|
||||
bool setAttribute(const Attribute& attribute);
|
||||
|
||||
virtual void parseAttribute(PropertyID id, const std::string& value);
|
||||
|
||||
SVGElement* previousElement() const;
|
||||
SVGElement* nextElement() const;
|
||||
|
||||
SVGNode* addChild(std::unique_ptr<SVGNode> child);
|
||||
SVGNode* firstChild() const;
|
||||
SVGNode* lastChild() const;
|
||||
|
||||
ElementID id() const { return m_id; }
|
||||
const AttributeList& attributes() const { return m_attributes; }
|
||||
const SVGPropertyList& properties() const { return m_properties; }
|
||||
const SVGNodeList& children() const { return m_children; }
|
||||
|
||||
virtual Transform localTransform() const { return Transform::Identity; }
|
||||
virtual Rect fillBoundingBox() const;
|
||||
virtual Rect strokeBoundingBox() const;
|
||||
virtual Rect paintBoundingBox() const;
|
||||
|
||||
SVGMarkerElement* getMarker(std::string_view id) const;
|
||||
SVGClipPathElement* getClipper(std::string_view id) const;
|
||||
SVGMaskElement* getMasker(std::string_view id) const;
|
||||
SVGPaintElement* getPainter(std::string_view id) const;
|
||||
|
||||
SVGElement* elementFromPoint(float x, float y);
|
||||
|
||||
template<typename T>
|
||||
void transverse(T callback);
|
||||
|
||||
void addProperty(SVGProperty& value);
|
||||
SVGProperty* getProperty(PropertyID id) const;
|
||||
Size currentViewportSize() const;
|
||||
float font_size() const { return m_font_size; }
|
||||
|
||||
void cloneChildren(SVGElement* parentElement) const;
|
||||
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
||||
|
||||
virtual void build();
|
||||
|
||||
virtual void layoutElement(const SVGLayoutState& state);
|
||||
void layoutChildren(SVGLayoutState& state);
|
||||
virtual void layout(SVGLayoutState& state);
|
||||
|
||||
void renderChildren(SVGRenderState& state) const;
|
||||
virtual void render(SVGRenderState& state) const;
|
||||
|
||||
bool isDisplayNone() const { return m_display == Display::None; }
|
||||
bool isOverflowHidden() const { return m_overflow == Overflow::Hidden; }
|
||||
bool isVisibilityHidden() const { return m_visibility != Visibility::Visible; }
|
||||
|
||||
bool isHiddenElement() const;
|
||||
bool isPointableElement() const;
|
||||
|
||||
const SVGClipPathElement* clipper() const { return m_clipper; }
|
||||
const SVGMaskElement* masker() const { return m_masker; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
bool isElement() const final { return true; }
|
||||
|
||||
private:
|
||||
mutable Rect m_paintBoundingBox = Rect::Invalid;
|
||||
const SVGClipPathElement* m_clipper = nullptr;
|
||||
const SVGMaskElement* m_masker = nullptr;
|
||||
float m_opacity = 1.f;
|
||||
|
||||
float m_font_size = 12.f;
|
||||
Display m_display = Display::Inline;
|
||||
Overflow m_overflow = Overflow::Visible;
|
||||
Visibility m_visibility = Visibility::Visible;
|
||||
PointerEvents m_pointer_events = PointerEvents::Auto;
|
||||
|
||||
ElementID m_id;
|
||||
AttributeList m_attributes;
|
||||
SVGPropertyList m_properties;
|
||||
SVGNodeList m_children;
|
||||
};
|
||||
|
||||
inline const SVGElement* toSVGElement(const SVGNode* node)
|
||||
{
|
||||
if(node && node->isElement())
|
||||
return static_cast<const SVGElement*>(node);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline SVGElement* toSVGElement(SVGNode* node)
|
||||
{
|
||||
if(node && node->isElement())
|
||||
return static_cast<SVGElement*>(node);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline SVGElement* toSVGElement(const std::unique_ptr<SVGNode>& node)
|
||||
{
|
||||
return toSVGElement(node.get());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void SVGElement::transverse(T callback)
|
||||
{
|
||||
callback(this);
|
||||
for(const auto& child : m_children) {
|
||||
if(auto element = toSVGElement(child)) {
|
||||
element->transverse(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SVGStyleElement final : public SVGElement {
|
||||
public:
|
||||
SVGStyleElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGFitToViewBox {
|
||||
public:
|
||||
SVGFitToViewBox(SVGElement* element);
|
||||
|
||||
const SVGRect& viewBox() const { return m_viewBox; }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
Transform viewBoxToViewTransform(const Size& viewportSize) const;
|
||||
Rect getClipRect(const Size& viewportSize) const;
|
||||
|
||||
private:
|
||||
SVGRect m_viewBox;
|
||||
SVGPreserveAspectRatio m_preserveAspectRatio;
|
||||
};
|
||||
|
||||
class SVGURIReference {
|
||||
public:
|
||||
SVGURIReference(SVGElement* element);
|
||||
|
||||
const SVGString& href() const { return m_href; }
|
||||
const std::string& hrefString() const { return m_href.value(); }
|
||||
SVGElement* getTargetElement(const Document* document) const;
|
||||
|
||||
private:
|
||||
SVGString m_href;
|
||||
};
|
||||
|
||||
class SVGPaintServer {
|
||||
public:
|
||||
SVGPaintServer() = default;
|
||||
SVGPaintServer(const SVGPaintElement* element, const Color& color, float opacity)
|
||||
: m_element(element), m_color(color), m_opacity(opacity)
|
||||
{}
|
||||
|
||||
bool isRenderable() const { return m_opacity > 0.f && (m_element || m_color.alpha() > 0); }
|
||||
|
||||
const SVGPaintElement* element() const { return m_element; }
|
||||
const Color& color() const { return m_color; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state) const;
|
||||
|
||||
private:
|
||||
const SVGPaintElement* m_element = nullptr;
|
||||
Color m_color = Color::Transparent;
|
||||
float m_opacity = 0.f;
|
||||
};
|
||||
|
||||
class SVGGraphicsElement : public SVGElement {
|
||||
public:
|
||||
SVGGraphicsElement(Document* document, ElementID id);
|
||||
|
||||
bool isGraphicsElement() const final { return true; }
|
||||
|
||||
const SVGTransform& transform() const { return m_transform; }
|
||||
Transform localTransform() const override { return m_transform.value(); }
|
||||
|
||||
SVGPaintServer getPaintServer(const Paint& paint, float opacity) const;
|
||||
StrokeData getStrokeData(const SVGLayoutState& state) const;
|
||||
|
||||
private:
|
||||
SVGTransform m_transform;
|
||||
};
|
||||
|
||||
class SVGSVGElement : public SVGGraphicsElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGSVGElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
|
||||
Transform localTransform() const override;
|
||||
void render(SVGRenderState& state) const override;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
};
|
||||
|
||||
class SVGRootElement final : public SVGSVGElement {
|
||||
public:
|
||||
SVGRootElement(Document* document);
|
||||
|
||||
float intrinsicWidth() const { return m_intrinsicWidth; }
|
||||
float intrinsicHeight() const { return m_intrinsicHeight; }
|
||||
|
||||
void setNeedsLayout() { m_intrinsicWidth = -1.f; }
|
||||
bool needsLayout() const { return m_intrinsicWidth == -1.f; }
|
||||
|
||||
SVGRootElement* layoutIfNeeded();
|
||||
|
||||
SVGElement* getElementById(std::string_view id) const;
|
||||
void addElementById(const std::string& id, SVGElement* element);
|
||||
void layout(SVGLayoutState& state) final;
|
||||
|
||||
void forceLayout();
|
||||
|
||||
private:
|
||||
std::map<std::string, SVGElement*, std::less<>> m_idCache;
|
||||
float m_intrinsicWidth{-1.f};
|
||||
float m_intrinsicHeight{-1.f};
|
||||
};
|
||||
|
||||
class SVGUseElement final : public SVGGraphicsElement, public SVGURIReference {
|
||||
public:
|
||||
SVGUseElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
|
||||
Transform localTransform() const final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
void build() final;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SVGElement> cloneTargetElement(SVGElement* targetElement);
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
};
|
||||
|
||||
class SVGImageElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGImageElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
const Bitmap& image() const { return m_image; }
|
||||
|
||||
Rect fillBoundingBox() const final;
|
||||
Rect strokeBoundingBox() const final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
void parseAttribute(PropertyID id, const std::string& value) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGPreserveAspectRatio m_preserveAspectRatio;
|
||||
Bitmap m_image;
|
||||
};
|
||||
|
||||
class SVGSymbolElement final : public SVGGraphicsElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGSymbolElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGGElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGGElement(Document* document);
|
||||
|
||||
void render(SVGRenderState& state) const final;
|
||||
};
|
||||
|
||||
class SVGDefsElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGDefsElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGMarkerElement final : public SVGElement, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGMarkerElement(Document* document);
|
||||
|
||||
const SVGLength& refX() const { return m_refX; }
|
||||
const SVGLength& refY() const { return m_refY; }
|
||||
const SVGLength& markerWidth() const { return m_markerWidth; }
|
||||
const SVGLength& markerHeight() const { return m_markerHeight; }
|
||||
const SVGEnumeration<MarkerUnits>& markerUnits() const { return m_markerUnits; }
|
||||
const SVGAngle& orient() const { return m_orient; }
|
||||
|
||||
Point refPoint() const;
|
||||
Size markerSize() const;
|
||||
|
||||
Transform markerTransform(const Point& origin, float angle, float strokeWidth) const;
|
||||
Rect markerBoundingBox(const Point& origin, float angle, float strokeWidth) const;
|
||||
void renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const;
|
||||
|
||||
Transform localTransform() const final;
|
||||
|
||||
private:
|
||||
SVGLength m_refX;
|
||||
SVGLength m_refY;
|
||||
SVGLength m_markerWidth;
|
||||
SVGLength m_markerHeight;
|
||||
SVGEnumeration<MarkerUnits> m_markerUnits;
|
||||
SVGAngle m_orient;
|
||||
};
|
||||
|
||||
class SVGClipPathElement final : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGClipPathElement(Document* document);
|
||||
|
||||
const SVGEnumeration<Units>& clipPathUnits() const { return m_clipPathUnits; }
|
||||
Rect clipBoundingBox(const SVGElement* element) const;
|
||||
|
||||
void applyClipMask(SVGRenderState& state) const;
|
||||
void applyClipPath(SVGRenderState& state) const;
|
||||
|
||||
bool requiresMasking() const;
|
||||
|
||||
private:
|
||||
SVGEnumeration<Units> m_clipPathUnits;
|
||||
};
|
||||
|
||||
class SVGMaskElement final : public SVGElement {
|
||||
public:
|
||||
SVGMaskElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGEnumeration<Units>& maskUnits() const { return m_maskUnits; }
|
||||
const SVGEnumeration<Units>& maskContentUnits() const { return m_maskContentUnits; }
|
||||
|
||||
Rect maskRect(const SVGElement* element) const;
|
||||
Rect maskBoundingBox(const SVGElement* element) const;
|
||||
void applyMask(SVGRenderState& state) const;
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGEnumeration<Units> m_maskUnits;
|
||||
SVGEnumeration<Units> m_maskContentUnits;
|
||||
MaskType m_mask_type = MaskType::Luminance;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGELEMENT_H
|
||||
324
vendor/lunasvg/source/svggeometryelement.cpp
vendored
Normal file
324
vendor/lunasvg/source/svggeometryelement.cpp
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
#include "svggeometryelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
Rect SVGMarkerPosition::markerBoundingBox(float strokeWidth) const
|
||||
{
|
||||
return m_element->markerBoundingBox(m_origin, m_angle, strokeWidth);
|
||||
}
|
||||
|
||||
void SVGMarkerPosition::renderMarker(SVGRenderState& state, float strokeWidth) const
|
||||
{
|
||||
m_element->renderMarker(state, m_origin, m_angle, strokeWidth);
|
||||
}
|
||||
|
||||
SVGGeometryElement::SVGGeometryElement(Document* document, ElementID id)
|
||||
: SVGGraphicsElement(document, id)
|
||||
{
|
||||
}
|
||||
|
||||
Rect SVGGeometryElement::strokeBoundingBox() const
|
||||
{
|
||||
auto strokeBoundingBox = fillBoundingBox();
|
||||
if(m_stroke.isRenderable()) {
|
||||
float capLimit = m_strokeData.lineWidth() / 2.f;
|
||||
if(m_strokeData.lineCap() == LineCap::Square)
|
||||
capLimit *= PLUTOVG_SQRT2;
|
||||
float joinLimit = m_strokeData.lineWidth() / 2.f;
|
||||
if(m_strokeData.lineJoin() == LineJoin::Miter) {
|
||||
joinLimit *= m_strokeData.miterLimit();
|
||||
}
|
||||
|
||||
strokeBoundingBox.inflate(std::max(capLimit, joinLimit));
|
||||
}
|
||||
|
||||
for(const auto& markerPosition : m_markerPositions)
|
||||
strokeBoundingBox.unite(markerPosition.markerBoundingBox(m_strokeData.lineWidth()));
|
||||
return strokeBoundingBox;
|
||||
}
|
||||
|
||||
void SVGGeometryElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_fill_rule = state.fill_rule();
|
||||
m_clip_rule = state.clip_rule();
|
||||
m_fill = getPaintServer(state.fill(), state.fill_opacity());
|
||||
m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
|
||||
m_strokeData = getStrokeData(state);
|
||||
SVGGraphicsElement::layoutElement(state);
|
||||
|
||||
m_path.reset();
|
||||
m_markerPositions.clear();
|
||||
m_fillBoundingBox = updateShape(m_path);
|
||||
updateMarkerPositions(m_markerPositions, state);
|
||||
}
|
||||
|
||||
void SVGGeometryElement::updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state)
|
||||
{
|
||||
if(m_path.isEmpty())
|
||||
return;
|
||||
auto markerStart = getMarker(state.marker_start());
|
||||
auto markerMid = getMarker(state.marker_mid());
|
||||
auto markerEnd = getMarker(state.marker_end());
|
||||
if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point origin;
|
||||
Point startPoint;
|
||||
Point inslopePoints[2];
|
||||
Point outslopePoints[2];
|
||||
|
||||
int index = 0;
|
||||
std::array<Point, 3> points;
|
||||
PathIterator it(m_path);
|
||||
while(!it.isDone()) {
|
||||
switch(it.currentSegment(points)) {
|
||||
case PathCommand::MoveTo:
|
||||
startPoint = points[0];
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = points[0];
|
||||
break;
|
||||
case PathCommand::LineTo:
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = points[0];
|
||||
break;
|
||||
case PathCommand::CubicTo:
|
||||
inslopePoints[0] = points[1];
|
||||
inslopePoints[1] = points[2];
|
||||
origin = points[2];
|
||||
break;
|
||||
case PathCommand::Close:
|
||||
inslopePoints[0] = origin;
|
||||
inslopePoints[1] = points[0];
|
||||
origin = startPoint;
|
||||
startPoint = Point();
|
||||
break;
|
||||
}
|
||||
|
||||
it.next();
|
||||
|
||||
if(!it.isDone() && (markerStart || markerMid)) {
|
||||
it.currentSegment(points);
|
||||
outslopePoints[0] = origin;
|
||||
outslopePoints[1] = points[0];
|
||||
if(index == 0 && markerStart) {
|
||||
auto slope = outslopePoints[1] - outslopePoints[0];
|
||||
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
|
||||
const auto& orient = markerStart->orient();
|
||||
if(orient.orientType() == SVGAngle::OrientType::AutoStartReverse)
|
||||
angle -= 180.f;
|
||||
positions.emplace_back(markerStart, origin, angle);
|
||||
}
|
||||
|
||||
if(index > 0 && markerMid) {
|
||||
auto inslope = inslopePoints[1] - inslopePoints[0];
|
||||
auto outslope = outslopePoints[1] - outslopePoints[0];
|
||||
auto inangle = 180.f * std::atan2(inslope.y, inslope.x) / PLUTOVG_PI;
|
||||
auto outangle = 180.f * std::atan2(outslope.y, outslope.x) / PLUTOVG_PI;
|
||||
if(std::abs(inangle - outangle) > 180.f)
|
||||
inangle += 360.f;
|
||||
auto angle = (inangle + outangle) * 0.5f;
|
||||
positions.emplace_back(markerMid, origin, angle);
|
||||
}
|
||||
}
|
||||
|
||||
if(markerEnd && it.isDone()) {
|
||||
auto slope = inslopePoints[1] - inslopePoints[0];
|
||||
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
|
||||
positions.emplace_back(markerEnd, origin, angle);
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void SVGGeometryElement::render(SVGRenderState& state) const
|
||||
{
|
||||
if(!isRenderable())
|
||||
return;
|
||||
SVGBlendInfo blendInfo(this);
|
||||
SVGRenderState newState(this, state, localTransform());
|
||||
newState.beginGroup(blendInfo);
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->setColor(Color::White);
|
||||
newState->fillPath(m_path, m_clip_rule, newState.currentTransform());
|
||||
} else {
|
||||
if(m_fill.applyPaint(newState))
|
||||
newState->fillPath(m_path, m_fill_rule, newState.currentTransform());
|
||||
if(m_stroke.applyPaint(newState)) {
|
||||
newState->strokePath(m_path, m_strokeData, newState.currentTransform());
|
||||
}
|
||||
|
||||
for(const auto& markerPosition : m_markerPositions) {
|
||||
markerPosition.renderMarker(newState, m_strokeData.lineWidth());
|
||||
}
|
||||
}
|
||||
|
||||
newState.endGroup(blendInfo);
|
||||
}
|
||||
|
||||
SVGLineElement::SVGLineElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Line)
|
||||
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
{
|
||||
addProperty(m_x1);
|
||||
addProperty(m_y1);
|
||||
addProperty(m_x2);
|
||||
addProperty(m_y2);
|
||||
}
|
||||
|
||||
Rect SVGLineElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto x1 = lengthContext.valueForLength(m_x1);
|
||||
auto y1 = lengthContext.valueForLength(m_y1);
|
||||
auto x2 = lengthContext.valueForLength(m_x2);
|
||||
auto y2 = lengthContext.valueForLength(m_y2);
|
||||
|
||||
path.moveTo(x1, y1);
|
||||
path.lineTo(x2, y2);
|
||||
return Rect(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
SVGRectElement::SVGRectElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Rect)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
, m_rx(PropertyID::Rx, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_ry(PropertyID::Ry, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_width);
|
||||
addProperty(m_height);
|
||||
addProperty(m_rx);
|
||||
addProperty(m_ry);
|
||||
}
|
||||
|
||||
Rect SVGRectElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto width = lengthContext.valueForLength(m_width);
|
||||
auto height = lengthContext.valueForLength(m_height);
|
||||
if(width <= 0.f || height <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto x = lengthContext.valueForLength(m_x);
|
||||
auto y = lengthContext.valueForLength(m_y);
|
||||
|
||||
auto rx = lengthContext.valueForLength(m_rx);
|
||||
auto ry = lengthContext.valueForLength(m_ry);
|
||||
|
||||
if(rx <= 0.f) rx = ry;
|
||||
if(ry <= 0.f) ry = rx;
|
||||
|
||||
rx = std::min(rx, width / 2.f);
|
||||
ry = std::min(ry, height / 2.f);
|
||||
|
||||
path.addRoundRect(x, y, width, height, rx, ry);
|
||||
return Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
SVGEllipseElement::SVGEllipseElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Ellipse)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_rx(PropertyID::Rx, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
, m_ry(PropertyID::Ry, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_rx);
|
||||
addProperty(m_ry);
|
||||
}
|
||||
|
||||
Rect SVGEllipseElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto rx = lengthContext.valueForLength(m_rx);
|
||||
auto ry = lengthContext.valueForLength(m_ry);
|
||||
if(rx <= 0.f || ry <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto cx = lengthContext.valueForLength(m_cx);
|
||||
auto cy = lengthContext.valueForLength(m_cy);
|
||||
path.addEllipse(cx, cy, rx, ry);
|
||||
return Rect(cx - rx, cy - ry, rx + rx, ry + ry);
|
||||
}
|
||||
|
||||
SVGCircleElement::SVGCircleElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Circle)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_r);
|
||||
}
|
||||
|
||||
Rect SVGCircleElement::updateShape(Path& path)
|
||||
{
|
||||
LengthContext lengthContext(this);
|
||||
auto r = lengthContext.valueForLength(m_r);
|
||||
if(r <= 0.f) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
auto cx = lengthContext.valueForLength(m_cx);
|
||||
auto cy = lengthContext.valueForLength(m_cy);
|
||||
path.addEllipse(cx, cy, r, r);
|
||||
return Rect(cx - r, cy - r, r + r, r + r);
|
||||
}
|
||||
|
||||
SVGPolyElement::SVGPolyElement(Document* document, ElementID id)
|
||||
: SVGGeometryElement(document, id)
|
||||
, m_points(PropertyID::Points)
|
||||
{
|
||||
addProperty(m_points);
|
||||
}
|
||||
|
||||
Rect SVGPolyElement::updateShape(Path& path)
|
||||
{
|
||||
const auto& points = m_points.values();
|
||||
if(points.empty()) {
|
||||
return Rect::Empty;
|
||||
}
|
||||
|
||||
path.moveTo(points[0].x, points[0].y);
|
||||
for(size_t i = 1; i < points.size(); i++) {
|
||||
path.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
|
||||
if(id() == ElementID::Polygon)
|
||||
path.close();
|
||||
return path.boundingRect();
|
||||
}
|
||||
|
||||
SVGPathElement::SVGPathElement(Document* document)
|
||||
: SVGGeometryElement(document, ElementID::Path)
|
||||
, m_d(PropertyID::D)
|
||||
{
|
||||
addProperty(m_d);
|
||||
}
|
||||
|
||||
Rect SVGPathElement::updateShape(Path& path)
|
||||
{
|
||||
path = m_d.value();
|
||||
return path.boundingRect();
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
139
vendor/lunasvg/source/svggeometryelement.h
vendored
Normal file
139
vendor/lunasvg/source/svggeometryelement.h
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
#ifndef LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
#define LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGMarkerPosition {
|
||||
public:
|
||||
SVGMarkerPosition(const SVGMarkerElement* element, const Point& origin, float angle)
|
||||
: m_element(element), m_origin(origin), m_angle(angle)
|
||||
{}
|
||||
|
||||
const SVGMarkerElement* element() const { return m_element; }
|
||||
const Point& origin() const { return m_origin; }
|
||||
float angle() const { return m_angle; }
|
||||
|
||||
Rect markerBoundingBox(float strokeWidth) const;
|
||||
void renderMarker(SVGRenderState& state, float strokeWidth) const;
|
||||
|
||||
private:
|
||||
const SVGMarkerElement* m_element;
|
||||
Point m_origin;
|
||||
float m_angle;
|
||||
};
|
||||
|
||||
using SVGMarkerPositionList = std::vector<SVGMarkerPosition>;
|
||||
|
||||
class SVGGeometryElement : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGGeometryElement(Document* document, ElementID id);
|
||||
|
||||
bool isGeometryElement() const final { return true; }
|
||||
|
||||
Rect fillBoundingBox() const override { return m_fillBoundingBox; }
|
||||
Rect strokeBoundingBox() const override;
|
||||
void layoutElement(const SVGLayoutState& state) override;
|
||||
|
||||
bool isRenderable() const { return !m_path.isNull() && !isDisplayNone() && !isVisibilityHidden(); }
|
||||
|
||||
FillRule fill_rule() const { return m_fill_rule; }
|
||||
FillRule clip_rule() const { return m_clip_rule; }
|
||||
|
||||
virtual Rect updateShape(Path& path) = 0;
|
||||
|
||||
void updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state);
|
||||
void render(SVGRenderState& state) const override;
|
||||
|
||||
const Path& path() const { return m_path; }
|
||||
|
||||
private:
|
||||
Path m_path;
|
||||
Rect m_fillBoundingBox;
|
||||
StrokeData m_strokeData;
|
||||
|
||||
SVGPaintServer m_fill;
|
||||
SVGPaintServer m_stroke;
|
||||
SVGMarkerPositionList m_markerPositions;
|
||||
|
||||
FillRule m_fill_rule = FillRule::NonZero;
|
||||
FillRule m_clip_rule = FillRule::NonZero;
|
||||
};
|
||||
|
||||
class SVGLineElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGLineElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x1;
|
||||
SVGLength m_y1;
|
||||
SVGLength m_x2;
|
||||
SVGLength m_y2;
|
||||
};
|
||||
|
||||
class SVGRectElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGRectElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGLength m_rx;
|
||||
SVGLength m_ry;
|
||||
};
|
||||
|
||||
class SVGEllipseElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGEllipseElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_rx;
|
||||
SVGLength m_ry;
|
||||
};
|
||||
|
||||
class SVGCircleElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGCircleElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_r;
|
||||
};
|
||||
|
||||
class SVGPolyElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGPolyElement(Document* document, ElementID id);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGPointList m_points;
|
||||
};
|
||||
|
||||
class SVGPathElement final : public SVGGeometryElement {
|
||||
public:
|
||||
SVGPathElement(Document* document);
|
||||
|
||||
Rect updateShape(Path& path) final;
|
||||
|
||||
private:
|
||||
SVGPath m_d;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGGEOMETRYELEMENT_H
|
||||
607
vendor/lunasvg/source/svglayoutstate.cpp
vendored
Normal file
607
vendor/lunasvg/source/svglayoutstate.cpp
vendored
Normal file
@@ -0,0 +1,607 @@
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
static std::optional<Color> parseColorValue(std::string_view& input, const SVGLayoutState* state)
|
||||
{
|
||||
if(skipString(input, "currentColor")) {
|
||||
return state->color();
|
||||
}
|
||||
|
||||
plutovg_color_t color;
|
||||
int length = plutovg_color_parse(&color, input.data(), input.length());
|
||||
if(length == 0)
|
||||
return std::nullopt;
|
||||
input.remove_prefix(length);
|
||||
return Color(plutovg_color_to_argb32(&color));
|
||||
}
|
||||
|
||||
static Color parseColor(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
auto color = parseColorValue(input, state);
|
||||
if(!color || !input.empty())
|
||||
color = defaultValue;
|
||||
return color.value();
|
||||
}
|
||||
|
||||
static Color parseColorOrNone(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
if(input.compare("none") == 0)
|
||||
return Color::Transparent;
|
||||
return parseColor(input, state, defaultValue);
|
||||
}
|
||||
|
||||
static bool parseUrlValue(std::string_view& input, std::string& value)
|
||||
{
|
||||
if(!skipString(input, "url")
|
||||
|| !skipOptionalSpaces(input)
|
||||
|| !skipDelimiter(input, '(')
|
||||
|| !skipOptionalSpaces(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(input.front()) {
|
||||
case '\'':
|
||||
case '\"': {
|
||||
auto delim = input.front();
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '#'))
|
||||
return false;
|
||||
while(!input.empty() && input.front() != delim) {
|
||||
value += input.front();
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, delim))
|
||||
return false;
|
||||
break;
|
||||
} case '#': {
|
||||
input.remove_prefix(1);
|
||||
while(!input.empty() && input.front() != ')') {
|
||||
value += input.front();
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
break;
|
||||
} default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return skipOptionalSpaces(input) && skipDelimiter(input, ')');
|
||||
}
|
||||
|
||||
static std::string parseUrl(std::string_view input)
|
||||
{
|
||||
std::string value;
|
||||
if(!parseUrlValue(input, value) || !input.empty())
|
||||
value.clear();
|
||||
return value;
|
||||
}
|
||||
|
||||
static Paint parsePaint(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
|
||||
{
|
||||
std::string id;
|
||||
if(!parseUrlValue(input, id))
|
||||
return Paint(parseColorOrNone(input, state, defaultValue));
|
||||
if(skipOptionalSpaces(input))
|
||||
return Paint(id, parseColorOrNone(input, state, defaultValue));
|
||||
return Paint(id, Color::Transparent);
|
||||
}
|
||||
|
||||
static float parseNumberOrPercentage(std::string_view input, bool allowPercentage, float defaultValue)
|
||||
{
|
||||
float value;
|
||||
if(!parseNumber(input, value))
|
||||
return defaultValue;
|
||||
if(allowPercentage) {
|
||||
if(skipDelimiter(input, '%'))
|
||||
value /= 100.f;
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
if(!input.empty())
|
||||
return defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
static Length parseLength(std::string_view input, LengthNegativeMode mode, const Length& defaultValue)
|
||||
{
|
||||
Length value;
|
||||
if(!value.parse(input, mode))
|
||||
value = defaultValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
static BaselineShift parseBaselineShift(std::string_view input)
|
||||
{
|
||||
if(input.compare("baseline") == 0)
|
||||
return BaselineShift::Type::Baseline;
|
||||
if(input.compare("sub") == 0)
|
||||
return BaselineShift::Type::Sub;
|
||||
if(input.compare("super") == 0)
|
||||
return BaselineShift::Type::Super;
|
||||
return parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None));
|
||||
}
|
||||
|
||||
static LengthList parseDashArray(std::string_view input)
|
||||
{
|
||||
if(input.compare("none") == 0)
|
||||
return LengthList();
|
||||
LengthList values;
|
||||
do {
|
||||
size_t count = 0;
|
||||
while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
|
||||
++count;
|
||||
Length value(0, LengthUnits::None);
|
||||
if(!value.parse(input.substr(0, count), LengthNegativeMode::Forbid))
|
||||
return LengthList();
|
||||
input.remove_prefix(count);
|
||||
values.push_back(std::move(value));
|
||||
} while(skipOptionalSpacesOrComma(input));
|
||||
return values;
|
||||
}
|
||||
|
||||
static Length parseLengthOrNormal(std::string_view input)
|
||||
{
|
||||
if(input.compare("normal") == 0)
|
||||
return Length(0, LengthUnits::None);
|
||||
return parseLength(input, LengthNegativeMode::Allow, Length(0, LengthUnits::None));
|
||||
}
|
||||
|
||||
static float parseFontSize(std::string_view input, const SVGLayoutState* state)
|
||||
{
|
||||
auto length = parseLength(input, LengthNegativeMode::Forbid, Length(12, LengthUnits::None));
|
||||
if(length.units() == LengthUnits::Percent)
|
||||
return length.value() * state->font_size() / 100.f;
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * state->font_size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * state->font_size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
template<typename Enum, unsigned int N>
|
||||
static Enum parseEnumValue(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N], Enum defaultValue)
|
||||
{
|
||||
for(const auto& entry : entries) {
|
||||
if(input == entry.second) {
|
||||
return entry.first;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
static Display parseDisplay(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Display> entries[] = {
|
||||
{Display::Inline, "inline"},
|
||||
{Display::None, "none"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Display::Inline);
|
||||
}
|
||||
|
||||
static Visibility parseVisibility(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Visibility> entries[] = {
|
||||
{Visibility::Visible, "visible"},
|
||||
{Visibility::Hidden, "hidden"},
|
||||
{Visibility::Collapse, "collapse"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Visibility::Visible);
|
||||
}
|
||||
|
||||
static Overflow parseOverflow(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Overflow> entries[] = {
|
||||
{Overflow::Visible, "visible"},
|
||||
{Overflow::Hidden, "hidden"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Overflow::Visible);
|
||||
}
|
||||
|
||||
static PointerEvents parsePointerEvents(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<PointerEvents> entries[] = {
|
||||
{PointerEvents::None,"none"},
|
||||
{PointerEvents::Auto,"auto"},
|
||||
{PointerEvents::Stroke,"stroke"},
|
||||
{PointerEvents::Fill,"fill"},
|
||||
{PointerEvents::Painted,"painted"},
|
||||
{PointerEvents::Visible,"visible"},
|
||||
{PointerEvents::VisibleStroke,"visibleStroke"},
|
||||
{PointerEvents::VisibleFill,"visibleFill"},
|
||||
{PointerEvents::VisiblePainted,"visiblePainted"},
|
||||
{PointerEvents::BoundingBox,"bounding-box"},
|
||||
{PointerEvents::All,"all"},
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, PointerEvents::Auto);
|
||||
}
|
||||
|
||||
static FontWeight parseFontWeight(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FontWeight> entries[] = {
|
||||
{FontWeight::Normal, "normal"},
|
||||
{FontWeight::Bold, "bold"},
|
||||
{FontWeight::Bold, "bolder"},
|
||||
{FontWeight::Normal, "lighter"},
|
||||
{FontWeight::Normal, "100"},
|
||||
{FontWeight::Normal, "200"},
|
||||
{FontWeight::Normal, "300"},
|
||||
{FontWeight::Normal, "400"},
|
||||
{FontWeight::Normal, "500"},
|
||||
{FontWeight::Bold, "600"},
|
||||
{FontWeight::Bold, "700"},
|
||||
{FontWeight::Bold, "800"},
|
||||
{FontWeight::Bold, "900"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FontWeight::Normal);
|
||||
}
|
||||
|
||||
static FontStyle parseFontStyle(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FontStyle> entries[] = {
|
||||
{FontStyle::Normal, "normal"},
|
||||
{FontStyle::Italic, "italic"},
|
||||
{FontStyle::Italic, "oblique"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FontStyle::Normal);
|
||||
}
|
||||
|
||||
static AlignmentBaseline parseAlignmentBaseline(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<AlignmentBaseline> entries[] = {
|
||||
{AlignmentBaseline::Auto, "auto"},
|
||||
{AlignmentBaseline::Baseline, "baseline"},
|
||||
{AlignmentBaseline::BeforeEdge, "before-edge"},
|
||||
{AlignmentBaseline::TextBeforeEdge, "text-before-edge"},
|
||||
{AlignmentBaseline::Middle, "middle"},
|
||||
{AlignmentBaseline::Central, "central"},
|
||||
{AlignmentBaseline::AfterEdge, "after-edge"},
|
||||
{AlignmentBaseline::TextAfterEdge, "text-after-edge"},
|
||||
{AlignmentBaseline::Ideographic, "ideographic"},
|
||||
{AlignmentBaseline::Alphabetic, "alphabetic"},
|
||||
{AlignmentBaseline::Hanging, "hanging"},
|
||||
{AlignmentBaseline::Mathematical, "mathematical"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, AlignmentBaseline::Auto);
|
||||
}
|
||||
|
||||
static DominantBaseline parseDominantBaseline(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<DominantBaseline> entries[] = {
|
||||
{DominantBaseline::Auto, "auto"},
|
||||
{DominantBaseline::UseScript, "use-script"},
|
||||
{DominantBaseline::NoChange, "no-change"},
|
||||
{DominantBaseline::ResetSize, "reset-size"},
|
||||
{DominantBaseline::Ideographic, "ideographic"},
|
||||
{DominantBaseline::Alphabetic, "alphabetic"},
|
||||
{DominantBaseline::Hanging, "hanging"},
|
||||
{DominantBaseline::Mathematical, "mathematical"},
|
||||
{DominantBaseline::Central, "central"},
|
||||
{DominantBaseline::Middle, "middle"},
|
||||
{DominantBaseline::TextAfterEdge, "text-after-edge"},
|
||||
{DominantBaseline::TextBeforeEdge, "text-before-edge"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, DominantBaseline::Auto);
|
||||
}
|
||||
|
||||
static Direction parseDirection(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Direction> entries[] = {
|
||||
{Direction::Ltr, "ltr"},
|
||||
{Direction::Rtl, "rtl"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, Direction::Ltr);
|
||||
}
|
||||
|
||||
static WritingMode parseWritingMode(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<WritingMode> entries[] = {
|
||||
{WritingMode::Horizontal, "horizontal-tb"},
|
||||
{WritingMode::Vertical, "vertical-rl"},
|
||||
{WritingMode::Vertical, "vertical-lr"},
|
||||
{WritingMode::Horizontal, "lr-tb"},
|
||||
{WritingMode::Horizontal, "lr"},
|
||||
{WritingMode::Horizontal, "rl-tb"},
|
||||
{WritingMode::Horizontal, "rl"},
|
||||
{WritingMode::Vertical, "tb-rl"},
|
||||
{WritingMode::Vertical, "tb"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, WritingMode::Horizontal);
|
||||
}
|
||||
|
||||
static TextOrientation parseTextOrientation(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<TextOrientation> entries[] = {
|
||||
{TextOrientation::Mixed, "mixed"},
|
||||
{TextOrientation::Upright, "upright"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, TextOrientation::Mixed);
|
||||
}
|
||||
|
||||
static TextAnchor parseTextAnchor(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<TextAnchor> entries[] = {
|
||||
{TextAnchor::Start, "start"},
|
||||
{TextAnchor::Middle, "middle"},
|
||||
{TextAnchor::End, "end"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, TextAnchor::Start);
|
||||
}
|
||||
|
||||
static WhiteSpace parseWhiteSpace(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<WhiteSpace> entries[] = {
|
||||
{WhiteSpace::Default, "default"},
|
||||
{WhiteSpace::Preserve, "preserve"},
|
||||
{WhiteSpace::Default, "normal"},
|
||||
{WhiteSpace::Default, "nowrap"},
|
||||
{WhiteSpace::Default, "pre-line"},
|
||||
{WhiteSpace::Preserve, "pre-wrap"},
|
||||
{WhiteSpace::Preserve, "pre"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, WhiteSpace::Default);
|
||||
}
|
||||
|
||||
static MaskType parseMaskType(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<MaskType> entries[] = {
|
||||
{MaskType::Luminance, "luminance"},
|
||||
{MaskType::Alpha, "alpha"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, MaskType::Luminance);
|
||||
}
|
||||
|
||||
static FillRule parseFillRule(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<FillRule> entries[] = {
|
||||
{FillRule::NonZero, "nonzero"},
|
||||
{FillRule::EvenOdd, "evenodd"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, FillRule::NonZero);
|
||||
}
|
||||
|
||||
static LineCap parseLineCap(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LineCap> entries[] = {
|
||||
{LineCap::Butt, "butt"},
|
||||
{LineCap::Round, "round"},
|
||||
{LineCap::Square, "square"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, LineCap::Butt);
|
||||
}
|
||||
|
||||
static LineJoin parseLineJoin(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LineJoin> entries[] = {
|
||||
{LineJoin::Miter, "miter"},
|
||||
{LineJoin::Round, "round"},
|
||||
{LineJoin::Bevel, "bevel"}
|
||||
};
|
||||
|
||||
return parseEnumValue(input, entries, LineJoin::Miter);
|
||||
}
|
||||
|
||||
SVGLayoutState::SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element)
|
||||
: m_parent(&parent)
|
||||
, m_element(element)
|
||||
, m_fill(parent.fill())
|
||||
, m_stroke(parent.stroke())
|
||||
, m_color(parent.color())
|
||||
, m_fill_opacity(parent.fill_opacity())
|
||||
, m_stroke_opacity(parent.stroke_opacity())
|
||||
, m_stroke_miterlimit(parent.stroke_miterlimit())
|
||||
, m_font_size(parent.font_size())
|
||||
, m_letter_spacing(parent.letter_spacing())
|
||||
, m_word_spacing(parent.word_spacing())
|
||||
, m_stroke_width(parent.stroke_width())
|
||||
, m_stroke_dashoffset(parent.stroke_dashoffset())
|
||||
, m_stroke_dasharray(parent.stroke_dasharray())
|
||||
, m_stroke_linecap(parent.stroke_linecap())
|
||||
, m_stroke_linejoin(parent.stroke_linejoin())
|
||||
, m_fill_rule(parent.fill_rule())
|
||||
, m_clip_rule(parent.clip_rule())
|
||||
, m_font_weight(parent.font_weight())
|
||||
, m_font_style(parent.font_style())
|
||||
, m_dominant_baseline(parent.dominant_baseline())
|
||||
, m_text_anchor(parent.text_anchor())
|
||||
, m_white_space(parent.white_space())
|
||||
, m_writing_mode(parent.writing_mode())
|
||||
, m_text_orientation(parent.text_orientation())
|
||||
, m_direction(parent.direction())
|
||||
, m_visibility(parent.visibility())
|
||||
, m_overflow(element->isRootElement() ? Overflow::Visible : Overflow::Hidden)
|
||||
, m_pointer_events(parent.pointer_events())
|
||||
, m_marker_start(parent.marker_start())
|
||||
, m_marker_mid(parent.marker_mid())
|
||||
, m_marker_end(parent.marker_end())
|
||||
, m_font_family(parent.font_family())
|
||||
{
|
||||
for(const auto& attribute : element->attributes()) {
|
||||
std::string_view input(attribute.value());
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(input.empty() || input.compare("inherit") == 0)
|
||||
continue;
|
||||
switch(attribute.id()) {
|
||||
case PropertyID::Fill:
|
||||
m_fill = parsePaint(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Stroke:
|
||||
m_stroke = parsePaint(input, this, Color::Transparent);
|
||||
break;
|
||||
case PropertyID::Color:
|
||||
m_color = parseColor(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Stop_Color:
|
||||
m_stop_color = parseColor(input, this, Color::Black);
|
||||
break;
|
||||
case PropertyID::Opacity:
|
||||
m_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Fill_Opacity:
|
||||
m_fill_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stroke_Opacity:
|
||||
m_stroke_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stop_Opacity:
|
||||
m_stop_opacity = parseNumberOrPercentage(input, true, 1.f);
|
||||
break;
|
||||
case PropertyID::Stroke_Miterlimit:
|
||||
m_stroke_miterlimit = parseNumberOrPercentage(input, false, 4.f);
|
||||
break;
|
||||
case PropertyID::Font_Size:
|
||||
m_font_size = parseFontSize(input, this);
|
||||
break;
|
||||
case PropertyID::Letter_Spacing:
|
||||
m_letter_spacing = parseLengthOrNormal(input);
|
||||
break;
|
||||
case PropertyID::Word_Spacing:
|
||||
m_word_spacing = parseLengthOrNormal(input);
|
||||
break;
|
||||
case PropertyID::Baseline_Shift:
|
||||
m_baseline_shift = parseBaselineShift(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Width:
|
||||
m_stroke_width = parseLength(input, LengthNegativeMode::Forbid, Length(1.f, LengthUnits::None));
|
||||
break;
|
||||
case PropertyID::Stroke_Dashoffset:
|
||||
m_stroke_dashoffset = parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None));
|
||||
break;
|
||||
case PropertyID::Stroke_Dasharray:
|
||||
m_stroke_dasharray = parseDashArray(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Linecap:
|
||||
m_stroke_linecap = parseLineCap(input);
|
||||
break;
|
||||
case PropertyID::Stroke_Linejoin:
|
||||
m_stroke_linejoin = parseLineJoin(input);
|
||||
break;
|
||||
case PropertyID::Fill_Rule:
|
||||
m_fill_rule = parseFillRule(input);
|
||||
break;
|
||||
case PropertyID::Clip_Rule:
|
||||
m_clip_rule = parseFillRule(input);
|
||||
break;
|
||||
case PropertyID::Font_Weight:
|
||||
m_font_weight = parseFontWeight(input);
|
||||
break;
|
||||
case PropertyID::Font_Style:
|
||||
m_font_style = parseFontStyle(input);
|
||||
break;
|
||||
case PropertyID::Alignment_Baseline:
|
||||
m_alignment_baseline = parseAlignmentBaseline(input);
|
||||
break;
|
||||
case PropertyID::Dominant_Baseline:
|
||||
m_dominant_baseline = parseDominantBaseline(input);
|
||||
break;
|
||||
case PropertyID::Direction:
|
||||
m_direction = parseDirection(input);
|
||||
break;
|
||||
case PropertyID::Text_Anchor:
|
||||
m_text_anchor = parseTextAnchor(input);
|
||||
break;
|
||||
case PropertyID::White_Space:
|
||||
m_white_space = parseWhiteSpace(input);
|
||||
break;
|
||||
case PropertyID::Writing_Mode:
|
||||
m_writing_mode = parseWritingMode(input);
|
||||
break;
|
||||
case PropertyID::Text_Orientation:
|
||||
m_text_orientation = parseTextOrientation(input);
|
||||
break;
|
||||
case PropertyID::Display:
|
||||
m_display = parseDisplay(input);
|
||||
break;
|
||||
case PropertyID::Visibility:
|
||||
m_visibility = parseVisibility(input);
|
||||
break;
|
||||
case PropertyID::Overflow:
|
||||
m_overflow = parseOverflow(input);
|
||||
break;
|
||||
case PropertyID::Pointer_Events:
|
||||
m_pointer_events = parsePointerEvents(input);
|
||||
break;
|
||||
case PropertyID::Mask_Type:
|
||||
m_mask_type = parseMaskType(input);
|
||||
break;
|
||||
case PropertyID::Mask:
|
||||
m_mask = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Clip_Path:
|
||||
m_clip_path = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_Start:
|
||||
m_marker_start = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_Mid:
|
||||
m_marker_mid = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Marker_End:
|
||||
m_marker_end = parseUrl(input);
|
||||
break;
|
||||
case PropertyID::Font_Family:
|
||||
m_font_family.assign(input);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Font SVGLayoutState::font() const
|
||||
{
|
||||
auto bold = m_font_weight == FontWeight::Bold;
|
||||
auto italic = m_font_style == FontStyle::Italic;
|
||||
|
||||
FontFace face;
|
||||
std::string_view input(m_font_family);
|
||||
while(!input.empty() && face.isNull()) {
|
||||
auto family = input.substr(0, input.find(','));
|
||||
input.remove_prefix(family.length());
|
||||
if(!input.empty() && input.front() == ',')
|
||||
input.remove_prefix(1);
|
||||
stripLeadingAndTrailingSpaces(family);
|
||||
if(!family.empty() && (family.front() == '\'' || family.front() == '"')) {
|
||||
auto quote = family.front();
|
||||
family.remove_prefix(1);
|
||||
if(!family.empty() && family.back() == quote)
|
||||
family.remove_suffix(1);
|
||||
stripLeadingAndTrailingSpaces(family);
|
||||
}
|
||||
|
||||
std::string font_family(family);
|
||||
if(!font_family.empty()) {
|
||||
face = fontFaceCache()->getFontFace(font_family, bold, italic);
|
||||
}
|
||||
}
|
||||
|
||||
if(face.isNull())
|
||||
face = fontFaceCache()->getFontFace(emptyString, bold, italic);
|
||||
return Font(face, m_font_size);
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
129
vendor/lunasvg/source/svglayoutstate.h
vendored
Normal file
129
vendor/lunasvg/source/svglayoutstate.h
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef LUNASVG_SVGLAYOUTSTATE_H
|
||||
#define LUNASVG_SVGLAYOUTSTATE_H
|
||||
|
||||
#include "svgproperty.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGLayoutState {
|
||||
public:
|
||||
SVGLayoutState() = default;
|
||||
SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element);
|
||||
|
||||
const SVGLayoutState* parent() const { return m_parent; }
|
||||
const SVGElement* element() const { return m_element; }
|
||||
|
||||
const Paint& fill() const { return m_fill; }
|
||||
const Paint& stroke() const { return m_stroke; }
|
||||
|
||||
const Color& color() const { return m_color; }
|
||||
const Color& stop_color() const { return m_stop_color; }
|
||||
|
||||
float opacity() const { return m_opacity; }
|
||||
float stop_opacity() const { return m_stop_opacity; }
|
||||
float fill_opacity() const { return m_fill_opacity; }
|
||||
float stroke_opacity() const { return m_stroke_opacity; }
|
||||
float stroke_miterlimit() const { return m_stroke_miterlimit; }
|
||||
float font_size() const { return m_font_size; }
|
||||
|
||||
const Length& letter_spacing() const { return m_letter_spacing; }
|
||||
const Length& word_spacing() const { return m_word_spacing; }
|
||||
|
||||
const BaselineShift& baseline_shift() const { return m_baseline_shift; }
|
||||
const Length& stroke_width() const { return m_stroke_width; }
|
||||
const Length& stroke_dashoffset() const { return m_stroke_dashoffset; }
|
||||
const LengthList& stroke_dasharray() const { return m_stroke_dasharray; }
|
||||
|
||||
LineCap stroke_linecap() const { return m_stroke_linecap; }
|
||||
LineJoin stroke_linejoin() const { return m_stroke_linejoin; }
|
||||
|
||||
FillRule fill_rule() const { return m_fill_rule; }
|
||||
FillRule clip_rule() const { return m_clip_rule; }
|
||||
|
||||
FontWeight font_weight() const { return m_font_weight; }
|
||||
FontStyle font_style() const { return m_font_style; }
|
||||
|
||||
AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; }
|
||||
DominantBaseline dominant_baseline() const { return m_dominant_baseline; }
|
||||
|
||||
TextAnchor text_anchor() const { return m_text_anchor; }
|
||||
WhiteSpace white_space() const { return m_white_space; }
|
||||
WritingMode writing_mode() const { return m_writing_mode; }
|
||||
TextOrientation text_orientation() const { return m_text_orientation; }
|
||||
Direction direction() const { return m_direction; }
|
||||
|
||||
Display display() const { return m_display; }
|
||||
Visibility visibility() const { return m_visibility; }
|
||||
Overflow overflow() const { return m_overflow; }
|
||||
PointerEvents pointer_events() const { return m_pointer_events; }
|
||||
MaskType mask_type() const { return m_mask_type; }
|
||||
|
||||
const std::string& mask() const { return m_mask; }
|
||||
const std::string& clip_path() const { return m_clip_path; }
|
||||
const std::string& marker_start() const { return m_marker_start; }
|
||||
const std::string& marker_mid() const { return m_marker_mid; }
|
||||
const std::string& marker_end() const { return m_marker_end; }
|
||||
const std::string& font_family() const { return m_font_family; }
|
||||
|
||||
Font font() const;
|
||||
|
||||
private:
|
||||
const SVGLayoutState* m_parent = nullptr;
|
||||
const SVGElement* m_element = nullptr;
|
||||
|
||||
Paint m_fill{Color::Black};
|
||||
Paint m_stroke{Color::Transparent};
|
||||
|
||||
Color m_color = Color::Black;
|
||||
Color m_stop_color = Color::Black;
|
||||
|
||||
float m_opacity = 1.f;
|
||||
float m_fill_opacity = 1.f;
|
||||
float m_stroke_opacity = 1.f;
|
||||
float m_stop_opacity = 1.f;
|
||||
float m_stroke_miterlimit = 4.f;
|
||||
float m_font_size = 12.f;
|
||||
|
||||
Length m_letter_spacing{0.f, LengthUnits::None};
|
||||
Length m_word_spacing{0.f, LengthUnits::None};
|
||||
|
||||
BaselineShift m_baseline_shift;
|
||||
Length m_stroke_width{1.f, LengthUnits::None};
|
||||
Length m_stroke_dashoffset{0.f, LengthUnits::None};
|
||||
LengthList m_stroke_dasharray;
|
||||
|
||||
LineCap m_stroke_linecap = LineCap::Butt;
|
||||
LineJoin m_stroke_linejoin = LineJoin::Miter;
|
||||
|
||||
FillRule m_fill_rule = FillRule::NonZero;
|
||||
FillRule m_clip_rule = FillRule::NonZero;
|
||||
|
||||
FontWeight m_font_weight = FontWeight::Normal;
|
||||
FontStyle m_font_style = FontStyle::Normal;
|
||||
|
||||
AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto;
|
||||
DominantBaseline m_dominant_baseline = DominantBaseline::Auto;
|
||||
|
||||
TextAnchor m_text_anchor = TextAnchor::Start;
|
||||
WhiteSpace m_white_space = WhiteSpace::Default;
|
||||
WritingMode m_writing_mode = WritingMode::Horizontal;
|
||||
TextOrientation m_text_orientation = TextOrientation::Mixed;
|
||||
Direction m_direction = Direction::Ltr;
|
||||
|
||||
Display m_display = Display::Inline;
|
||||
Visibility m_visibility = Visibility::Visible;
|
||||
Overflow m_overflow = Overflow::Visible;
|
||||
PointerEvents m_pointer_events = PointerEvents::Auto;
|
||||
MaskType m_mask_type = MaskType::Luminance;
|
||||
|
||||
std::string m_mask;
|
||||
std::string m_clip_path;
|
||||
std::string m_marker_start;
|
||||
std::string m_marker_mid;
|
||||
std::string m_marker_end;
|
||||
std::string m_font_family;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGLAYOUTSTATE_H
|
||||
364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
Normal file
364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "svgpaintelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
SVGPaintElement::SVGPaintElement(Document* document, ElementID id)
|
||||
: SVGElement(document, id)
|
||||
{
|
||||
}
|
||||
|
||||
SVGStopElement::SVGStopElement(Document* document)
|
||||
: SVGElement(document, ElementID::Stop)
|
||||
, m_offset(PropertyID::Offset, 0.f)
|
||||
{
|
||||
addProperty(m_offset);
|
||||
}
|
||||
|
||||
void SVGStopElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_stop_color = state.stop_color();
|
||||
m_stop_opacity = state.stop_opacity();
|
||||
SVGElement::layoutElement(state);
|
||||
}
|
||||
|
||||
GradientStop SVGStopElement::gradientStop(float opacity) const
|
||||
{
|
||||
Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity);
|
||||
GradientStop gradientStop = {
|
||||
m_offset.value(), { stopColor.redF(), stopColor.greenF(), stopColor.blueF(), stopColor.alphaF() }
|
||||
};
|
||||
|
||||
return gradientStop;
|
||||
}
|
||||
|
||||
SVGGradientElement::SVGGradientElement(Document* document, ElementID id)
|
||||
: SVGPaintElement(document, id)
|
||||
, SVGURIReference(this)
|
||||
, m_gradientTransform(PropertyID::GradientTransform)
|
||||
, m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox)
|
||||
, m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad)
|
||||
{
|
||||
addProperty(m_gradientTransform);
|
||||
addProperty(m_gradientUnits);
|
||||
addProperty(m_spreadMethod);
|
||||
}
|
||||
|
||||
void SVGGradientElement::collectGradientAttributes(SVGGradientAttributes& attributes) const
|
||||
{
|
||||
if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform))
|
||||
attributes.setGradientTransform(this);
|
||||
if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod))
|
||||
attributes.setSpreadMethod(this);
|
||||
if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits))
|
||||
attributes.setGradientUnits(this);
|
||||
if(!attributes.hasGradientContentElement()) {
|
||||
for(const auto& child : children()) {
|
||||
if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) {
|
||||
attributes.setGradientContentElement(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVGLinearGradientElement::SVGLinearGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::LinearGradient)
|
||||
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow, 100.f, LengthUnits::Percent)
|
||||
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
{
|
||||
addProperty(m_x1);
|
||||
addProperty(m_y1);
|
||||
addProperty(m_x2);
|
||||
addProperty(m_y2);
|
||||
}
|
||||
|
||||
SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGLinearGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::LinearGradient) {
|
||||
auto element = static_cast<const SVGLinearGradientElement*>(current);
|
||||
if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1))
|
||||
attributes.setX1(element);
|
||||
if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1))
|
||||
attributes.setY1(element);
|
||||
if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2))
|
||||
attributes.setX2(element);
|
||||
if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) {
|
||||
attributes.setY2(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity)
|
||||
{
|
||||
GradientStops gradientStops;
|
||||
|
||||
const auto& children = element->children();
|
||||
gradientStops.reserve(children.size());
|
||||
for(const auto& child : children) {
|
||||
auto childElement = toSVGElement(child);
|
||||
if(childElement && childElement->id() == ElementID::Stop) {
|
||||
auto stopElement = static_cast<SVGStopElement*>(childElement);
|
||||
gradientStops.push_back(stopElement->gradientStop(opacity));
|
||||
}
|
||||
}
|
||||
|
||||
return gradientStops;
|
||||
}
|
||||
|
||||
bool SVGLinearGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto x1 = lengthContext.valueForLength(attributes.x1());
|
||||
auto y1 = lengthContext.valueForLength(attributes.y1());
|
||||
auto x2 = lengthContext.valueForLength(attributes.x2());
|
||||
auto y2 = lengthContext.valueForLength(attributes.y2());
|
||||
if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientElement::SVGRadialGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::RadialGradient)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid, 50.f, LengthUnits::Percent)
|
||||
, m_fx(PropertyID::Fx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
, m_fy(PropertyID::Fy, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_r);
|
||||
addProperty(m_fx);
|
||||
addProperty(m_fy);
|
||||
}
|
||||
|
||||
bool SVGRadialGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto r = lengthContext.valueForLength(attributes.r());
|
||||
if(r == 0.f || gradientStops.size() == 1) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto fx = lengthContext.valueForLength(attributes.fx());
|
||||
auto fy = lengthContext.valueForLength(attributes.fy());
|
||||
auto cx = lengthContext.valueForLength(attributes.cx());
|
||||
auto cy = lengthContext.valueForLength(attributes.cy());
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGRadialGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::RadialGradient) {
|
||||
auto element = static_cast<const SVGRadialGradientElement*>(current);
|
||||
if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx))
|
||||
attributes.setCx(element);
|
||||
if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy))
|
||||
attributes.setCy(element);
|
||||
if(!attributes.hasR() && element->hasAttribute(PropertyID::R))
|
||||
attributes.setR(element);
|
||||
if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx))
|
||||
attributes.setFx(element);
|
||||
if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) {
|
||||
attributes.setFy(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
SVGPatternElement::SVGPatternElement(Document* document)
|
||||
: SVGPaintElement(document, ElementID::Pattern)
|
||||
, SVGURIReference(this)
|
||||
, SVGFitToViewBox(this)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
|
||||
, m_patternTransform(PropertyID::PatternTransform)
|
||||
, m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox)
|
||||
, m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_width);
|
||||
addProperty(m_height);
|
||||
addProperty(m_patternTransform);
|
||||
addProperty(m_patternUnits);
|
||||
addProperty(m_patternContentUnits);
|
||||
}
|
||||
|
||||
bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
if(state.hasCycleReference(this))
|
||||
return false;
|
||||
auto attributes = collectPatternAttributes();
|
||||
auto patternContentElement = attributes.patternContentElement();
|
||||
if(patternContentElement == nullptr)
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.patternUnits());
|
||||
Rect patternRect = {
|
||||
lengthContext.valueForLength(attributes.x()),
|
||||
lengthContext.valueForLength(attributes.y()),
|
||||
lengthContext.valueForLength(attributes.width()),
|
||||
lengthContext.valueForLength(attributes.height())
|
||||
};
|
||||
|
||||
if(attributes.patternUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternRect.x = patternRect.x * bbox.w + bbox.x;
|
||||
patternRect.y = patternRect.y * bbox.h + bbox.y;
|
||||
patternRect.w = patternRect.w * bbox.w;
|
||||
patternRect.h = patternRect.h * bbox.h;
|
||||
}
|
||||
|
||||
auto currentTransform = attributes.patternTransform() * state.currentTransform();
|
||||
auto xScale = currentTransform.xScale();
|
||||
auto yScale = currentTransform.yScale();
|
||||
|
||||
auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale);
|
||||
auto patternImageTransform = Transform::scaled(xScale, yScale);
|
||||
|
||||
const auto& viewBoxRect = attributes.viewBox();
|
||||
if(viewBoxRect.isValid()) {
|
||||
const auto& preserveAspectRatio = attributes.preserveAspectRatio();
|
||||
patternImageTransform.multiply(preserveAspectRatio.getTransform(viewBoxRect, patternRect.size()));
|
||||
} else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternImageTransform.scale(bbox.w, bbox.h);
|
||||
}
|
||||
|
||||
SVGRenderState newState(this, &state, patternImageTransform, SVGRenderMode::Painting, patternImage);
|
||||
patternContentElement->renderChildren(newState);
|
||||
|
||||
auto patternTransform = attributes.patternTransform();
|
||||
patternTransform.translate(patternRect.x, patternRect.y);
|
||||
patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height());
|
||||
state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const
|
||||
{
|
||||
SVGPatternAttributes attributes;
|
||||
std::set<const SVGPatternElement*> processedPatterns;
|
||||
const SVGPatternElement* current = this;
|
||||
while(true) {
|
||||
if(!attributes.hasX() && current->hasAttribute(PropertyID::X))
|
||||
attributes.setX(current);
|
||||
if(!attributes.hasY() && current->hasAttribute(PropertyID::Y))
|
||||
attributes.setY(current);
|
||||
if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width))
|
||||
attributes.setWidth(current);
|
||||
if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height))
|
||||
attributes.setHeight(current);
|
||||
if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform))
|
||||
attributes.setPatternTransform(current);
|
||||
if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits))
|
||||
attributes.setPatternUnits(current);
|
||||
if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits))
|
||||
attributes.setPatternContentUnits(current);
|
||||
if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox))
|
||||
attributes.setViewBox(current);
|
||||
if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio))
|
||||
attributes.setPreserveAspectRatio(current);
|
||||
if(!attributes.hasPatternContentElement()) {
|
||||
for(const auto& child : current->children()) {
|
||||
if(child->isElement()) {
|
||||
attributes.setPatternContentElement(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || targetElement->id() != ElementID::Pattern)
|
||||
break;
|
||||
processedPatterns.insert(current);
|
||||
current = static_cast<const SVGPatternElement*>(targetElement);
|
||||
if(processedPatterns.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
288
vendor/lunasvg/source/svgpaintelement.h
vendored
Normal file
288
vendor/lunasvg/source/svgpaintelement.h
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
#ifndef LUNASVG_SVGPAINTELEMENT_H
|
||||
#define LUNASVG_SVGPAINTELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGPaintElement : public SVGElement {
|
||||
public:
|
||||
SVGPaintElement(Document* document, ElementID id);
|
||||
|
||||
bool isPaintElement() const final { return true; }
|
||||
|
||||
virtual bool applyPaint(SVGRenderState& state, float opacity) const = 0;
|
||||
};
|
||||
|
||||
class SVGStopElement final : public SVGElement {
|
||||
public:
|
||||
SVGStopElement(Document* document);
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) final;
|
||||
const SVGNumberPercentage& offset() const { return m_offset; }
|
||||
GradientStop gradientStop(float opacity) const;
|
||||
|
||||
private:
|
||||
SVGNumberPercentage m_offset;
|
||||
Color m_stop_color = Color::Black;
|
||||
float m_stop_opacity = 1.f;
|
||||
};
|
||||
|
||||
class SVGGradientAttributes;
|
||||
|
||||
class SVGGradientElement : public SVGPaintElement, public SVGURIReference {
|
||||
public:
|
||||
SVGGradientElement(Document* document, ElementID id);
|
||||
|
||||
const SVGTransform& gradientTransform() const { return m_gradientTransform; }
|
||||
const SVGEnumeration<Units>& gradientUnits() const { return m_gradientUnits; }
|
||||
const SVGEnumeration<SpreadMethod>& spreadMethod() const { return m_spreadMethod; }
|
||||
void collectGradientAttributes(SVGGradientAttributes& attributes) const;
|
||||
|
||||
private:
|
||||
SVGTransform m_gradientTransform;
|
||||
SVGEnumeration<Units> m_gradientUnits;
|
||||
SVGEnumeration<SpreadMethod> m_spreadMethod;
|
||||
};
|
||||
|
||||
class SVGGradientAttributes {
|
||||
public:
|
||||
SVGGradientAttributes() = default;
|
||||
|
||||
const Transform& gradientTransform() const { return m_gradientTransform->gradientTransform().value(); }
|
||||
SpreadMethod spreadMethod() const { return m_spreadMethod->spreadMethod().value(); }
|
||||
Units gradientUnits() const { return m_gradientUnits->gradientUnits().value(); }
|
||||
const SVGGradientElement* gradientContentElement() const { return m_gradientContentElement; }
|
||||
|
||||
bool hasGradientTransform() const { return m_gradientTransform; }
|
||||
bool hasSpreadMethod() const { return m_spreadMethod; }
|
||||
bool hasGradientUnits() const { return m_gradientUnits; }
|
||||
bool hasGradientContentElement() const { return m_gradientContentElement; }
|
||||
|
||||
void setGradientTransform(const SVGGradientElement* value) { m_gradientTransform = value; }
|
||||
void setSpreadMethod(const SVGGradientElement* value) { m_spreadMethod = value; }
|
||||
void setGradientUnits(const SVGGradientElement* value) { m_gradientUnits = value; }
|
||||
void setGradientContentElement(const SVGGradientElement* value) { m_gradientContentElement = value; }
|
||||
|
||||
void setDefaultValues(const SVGGradientElement* element) {
|
||||
if(!m_gradientTransform) { m_gradientTransform = element; }
|
||||
if(!m_spreadMethod) { m_spreadMethod = element; }
|
||||
if(!m_gradientUnits) { m_gradientUnits = element; }
|
||||
if(!m_gradientContentElement) { m_gradientContentElement = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGGradientElement* m_gradientTransform{nullptr};
|
||||
const SVGGradientElement* m_spreadMethod{nullptr};
|
||||
const SVGGradientElement* m_gradientUnits{nullptr};
|
||||
const SVGGradientElement* m_gradientContentElement{nullptr};
|
||||
};
|
||||
|
||||
class SVGLinearGradientAttributes;
|
||||
|
||||
class SVGLinearGradientElement final : public SVGGradientElement {
|
||||
public:
|
||||
SVGLinearGradientElement(Document* document);
|
||||
|
||||
const SVGLength& x1() const { return m_x1; }
|
||||
const SVGLength& y1() const { return m_y1; }
|
||||
const SVGLength& x2() const { return m_x2; }
|
||||
const SVGLength& y2() const { return m_y2; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGLinearGradientAttributes collectGradientAttributes() const;
|
||||
SVGLength m_x1;
|
||||
SVGLength m_y1;
|
||||
SVGLength m_x2;
|
||||
SVGLength m_y2;
|
||||
};
|
||||
|
||||
class SVGLinearGradientAttributes : public SVGGradientAttributes {
|
||||
public:
|
||||
SVGLinearGradientAttributes() = default;
|
||||
|
||||
const SVGLength& x1() const { return m_x1->x1(); }
|
||||
const SVGLength& y1() const { return m_y1->y1(); }
|
||||
const SVGLength& x2() const { return m_x2->x2(); }
|
||||
const SVGLength& y2() const { return m_y2->y2(); }
|
||||
|
||||
bool hasX1() const { return m_x1; }
|
||||
bool hasY1() const { return m_y1; }
|
||||
bool hasX2() const { return m_x2; }
|
||||
bool hasY2() const { return m_y2; }
|
||||
|
||||
void setX1(const SVGLinearGradientElement* value) { m_x1 = value; }
|
||||
void setY1(const SVGLinearGradientElement* value) { m_y1 = value; }
|
||||
void setX2(const SVGLinearGradientElement* value) { m_x2 = value; }
|
||||
void setY2(const SVGLinearGradientElement* value) { m_y2 = value; }
|
||||
|
||||
void setDefaultValues(const SVGLinearGradientElement* element) {
|
||||
SVGGradientAttributes::setDefaultValues(element);
|
||||
if(!m_x1) { m_x1 = element; }
|
||||
if(!m_y1) { m_y1 = element; }
|
||||
if(!m_x2) { m_x2 = element; }
|
||||
if(!m_y2) { m_y2 = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGLinearGradientElement* m_x1{nullptr};
|
||||
const SVGLinearGradientElement* m_y1{nullptr};
|
||||
const SVGLinearGradientElement* m_x2{nullptr};
|
||||
const SVGLinearGradientElement* m_y2{nullptr};
|
||||
};
|
||||
|
||||
class SVGRadialGradientAttributes;
|
||||
|
||||
class SVGRadialGradientElement final : public SVGGradientElement {
|
||||
public:
|
||||
SVGRadialGradientElement(Document* document);
|
||||
|
||||
const SVGLength& cx() const { return m_cx; }
|
||||
const SVGLength& cy() const { return m_cy; }
|
||||
const SVGLength& r() const { return m_r; }
|
||||
const SVGLength& fx() const { return m_fx; }
|
||||
const SVGLength& fy() const { return m_fy; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGRadialGradientAttributes collectGradientAttributes() const;
|
||||
SVGLength m_cx;
|
||||
SVGLength m_cy;
|
||||
SVGLength m_r;
|
||||
SVGLength m_fx;
|
||||
SVGLength m_fy;
|
||||
};
|
||||
|
||||
class SVGRadialGradientAttributes : public SVGGradientAttributes {
|
||||
public:
|
||||
SVGRadialGradientAttributes() = default;
|
||||
|
||||
const SVGLength& cx() const { return m_cx->cx(); }
|
||||
const SVGLength& cy() const { return m_cy->cy(); }
|
||||
const SVGLength& r() const { return m_r->r(); }
|
||||
const SVGLength& fx() const { return m_fx ? m_fx->fx() : m_cx->cx(); }
|
||||
const SVGLength& fy() const { return m_fy ? m_fy->fy() : m_cy->cy(); }
|
||||
|
||||
bool hasCx() const { return m_cx; }
|
||||
bool hasCy() const { return m_cy; }
|
||||
bool hasR() const { return m_r; }
|
||||
bool hasFx() const { return m_fx; }
|
||||
bool hasFy() const { return m_fy; }
|
||||
|
||||
void setCx(const SVGRadialGradientElement* value) { m_cx = value; }
|
||||
void setCy(const SVGRadialGradientElement* value) { m_cy = value; }
|
||||
void setR(const SVGRadialGradientElement* value) { m_r = value; }
|
||||
void setFx(const SVGRadialGradientElement* value) { m_fx = value; }
|
||||
void setFy(const SVGRadialGradientElement* value) { m_fy = value; }
|
||||
|
||||
void setDefaultValues(const SVGRadialGradientElement* element) {
|
||||
SVGGradientAttributes::setDefaultValues(element);
|
||||
if(!m_cx) { m_cx = element; }
|
||||
if(!m_cy) { m_cy = element; }
|
||||
if(!m_r) { m_r = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGRadialGradientElement* m_cx{nullptr};
|
||||
const SVGRadialGradientElement* m_cy{nullptr};
|
||||
const SVGRadialGradientElement* m_r{nullptr};
|
||||
const SVGRadialGradientElement* m_fx{nullptr};
|
||||
const SVGRadialGradientElement* m_fy{nullptr};
|
||||
};
|
||||
|
||||
class SVGPatternAttributes;
|
||||
|
||||
class SVGPatternElement final : public SVGPaintElement, public SVGURIReference, public SVGFitToViewBox {
|
||||
public:
|
||||
SVGPatternElement(Document* document);
|
||||
|
||||
const SVGLength& x() const { return m_x; }
|
||||
const SVGLength& y() const { return m_y; }
|
||||
const SVGLength& width() const { return m_width; }
|
||||
const SVGLength& height() const { return m_height; }
|
||||
const SVGTransform& patternTransform() const { return m_patternTransform; }
|
||||
const SVGEnumeration<Units>& patternUnits() const { return m_patternUnits; }
|
||||
const SVGEnumeration<Units>& patternContentUnits() const { return m_patternContentUnits; }
|
||||
|
||||
bool applyPaint(SVGRenderState& state, float opacity) const final;
|
||||
|
||||
private:
|
||||
SVGPatternAttributes collectPatternAttributes() const;
|
||||
SVGLength m_x;
|
||||
SVGLength m_y;
|
||||
SVGLength m_width;
|
||||
SVGLength m_height;
|
||||
SVGTransform m_patternTransform;
|
||||
SVGEnumeration<Units> m_patternUnits;
|
||||
SVGEnumeration<Units> m_patternContentUnits;
|
||||
};
|
||||
|
||||
class SVGPatternAttributes {
|
||||
public:
|
||||
SVGPatternAttributes() = default;
|
||||
|
||||
const SVGLength& x() const { return m_x->x(); }
|
||||
const SVGLength& y() const { return m_y->y(); }
|
||||
const SVGLength& width() const { return m_width->width(); }
|
||||
const SVGLength& height() const { return m_height->height(); }
|
||||
const Transform& patternTransform() const { return m_patternTransform->patternTransform().value(); }
|
||||
Units patternUnits() const { return m_patternUnits->patternUnits().value(); }
|
||||
Units patternContentUnits() const { return m_patternContentUnits->patternContentUnits().value(); }
|
||||
const Rect& viewBox() const { return m_viewBox->viewBox().value(); }
|
||||
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio->preserveAspectRatio(); }
|
||||
const SVGPatternElement* patternContentElement() const { return m_patternContentElement; }
|
||||
|
||||
bool hasX() const { return m_x; }
|
||||
bool hasY() const { return m_y; }
|
||||
bool hasWidth() const { return m_width; }
|
||||
bool hasHeight() const { return m_height; }
|
||||
bool hasPatternTransform() const { return m_patternTransform; }
|
||||
bool hasPatternUnits() const { return m_patternUnits; }
|
||||
bool hasPatternContentUnits() const { return m_patternContentUnits; }
|
||||
bool hasViewBox() const { return m_viewBox; }
|
||||
bool hasPreserveAspectRatio() const { return m_preserveAspectRatio; }
|
||||
bool hasPatternContentElement() const { return m_patternContentElement; }
|
||||
|
||||
void setX(const SVGPatternElement* value) { m_x = value; }
|
||||
void setY(const SVGPatternElement* value) { m_y = value; }
|
||||
void setWidth(const SVGPatternElement* value) { m_width = value; }
|
||||
void setHeight(const SVGPatternElement* value) { m_height = value; }
|
||||
void setPatternTransform(const SVGPatternElement* value) { m_patternTransform = value; }
|
||||
void setPatternUnits(const SVGPatternElement* value) { m_patternUnits = value; }
|
||||
void setPatternContentUnits(const SVGPatternElement* value) { m_patternContentUnits = value; }
|
||||
void setViewBox(const SVGPatternElement* value) { m_viewBox = value; }
|
||||
void setPreserveAspectRatio(const SVGPatternElement* value) { m_preserveAspectRatio = value; }
|
||||
void setPatternContentElement(const SVGPatternElement* value) { m_patternContentElement = value; }
|
||||
|
||||
void setDefaultValues(const SVGPatternElement* element) {
|
||||
if(!m_x) { m_x = element; }
|
||||
if(!m_y) { m_y = element; }
|
||||
if(!m_width) { m_width = element; }
|
||||
if(!m_height) { m_height = element; }
|
||||
if(!m_patternTransform) { m_patternTransform = element; }
|
||||
if(!m_patternUnits) { m_patternUnits = element; }
|
||||
if(!m_patternContentUnits) { m_patternContentUnits = element; }
|
||||
if(!m_viewBox) { m_viewBox = element; }
|
||||
if(!m_preserveAspectRatio) { m_preserveAspectRatio = element; }
|
||||
if(!m_patternContentElement) { m_patternContentElement = element; }
|
||||
}
|
||||
|
||||
private:
|
||||
const SVGPatternElement* m_x{nullptr};
|
||||
const SVGPatternElement* m_y{nullptr};
|
||||
const SVGPatternElement* m_width{nullptr};
|
||||
const SVGPatternElement* m_height{nullptr};
|
||||
const SVGPatternElement* m_patternTransform{nullptr};
|
||||
const SVGPatternElement* m_patternUnits{nullptr};
|
||||
const SVGPatternElement* m_patternContentUnits{nullptr};
|
||||
const SVGPatternElement* m_viewBox{nullptr};
|
||||
const SVGPatternElement* m_preserveAspectRatio{nullptr};
|
||||
const SVGPatternElement* m_patternContentElement{nullptr};
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPAINTELEMENT_H
|
||||
956
vendor/lunasvg/source/svgparser.cpp
vendored
Normal file
956
vendor/lunasvg/source/svgparser.cpp
vendored
Normal file
@@ -0,0 +1,956 @@
|
||||
#include "lunasvg.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
struct SimpleSelector;
|
||||
|
||||
using Selector = std::vector<SimpleSelector>;
|
||||
using SelectorList = std::vector<Selector>;
|
||||
|
||||
struct AttributeSelector {
|
||||
enum class MatchType {
|
||||
None,
|
||||
Equals,
|
||||
Contains,
|
||||
Includes,
|
||||
StartsWith,
|
||||
EndsWith,
|
||||
DashEquals
|
||||
};
|
||||
|
||||
MatchType matchType{MatchType::None};
|
||||
PropertyID id{PropertyID::Unknown};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct PseudoClassSelector {
|
||||
enum class Type {
|
||||
Unknown,
|
||||
Empty,
|
||||
Root,
|
||||
Is,
|
||||
Not,
|
||||
FirstChild,
|
||||
LastChild,
|
||||
OnlyChild,
|
||||
FirstOfType,
|
||||
LastOfType,
|
||||
OnlyOfType
|
||||
};
|
||||
|
||||
Type type{Type::Unknown};
|
||||
SelectorList subSelectors;
|
||||
};
|
||||
|
||||
struct SimpleSelector {
|
||||
enum class Combinator {
|
||||
None,
|
||||
Descendant,
|
||||
Child,
|
||||
DirectAdjacent,
|
||||
InDirectAdjacent
|
||||
};
|
||||
|
||||
explicit SimpleSelector(Combinator combinator) : combinator(combinator) {}
|
||||
|
||||
Combinator combinator{Combinator::Descendant};
|
||||
ElementID id{ElementID::Star};
|
||||
std::vector<AttributeSelector> attributeSelectors;
|
||||
std::vector<PseudoClassSelector> pseudoClassSelectors;
|
||||
};
|
||||
|
||||
struct Declaration {
|
||||
int specificity;
|
||||
PropertyID id;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
using DeclarationList = std::vector<Declaration>;
|
||||
|
||||
struct Rule {
|
||||
SelectorList selectors;
|
||||
DeclarationList declarations;
|
||||
};
|
||||
|
||||
class RuleData {
|
||||
public:
|
||||
RuleData(const Selector& selector, const DeclarationList& declarations, size_t specificity, size_t position)
|
||||
: m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position)
|
||||
{}
|
||||
|
||||
bool isLessThan(const RuleData& rule) const { return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); }
|
||||
|
||||
const Selector& selector() const { return m_selector; }
|
||||
const DeclarationList& declarations() const { return m_declarations; }
|
||||
size_t specificity() const { return m_specificity; }
|
||||
size_t position() const { return m_position; }
|
||||
|
||||
bool match(const SVGElement* element) const;
|
||||
|
||||
private:
|
||||
Selector m_selector;
|
||||
DeclarationList m_declarations;
|
||||
size_t m_specificity;
|
||||
size_t m_position;
|
||||
};
|
||||
|
||||
inline bool operator<(const RuleData& a, const RuleData& b) { return a.isLessThan(b); }
|
||||
|
||||
using RuleDataList = std::vector<RuleData>;
|
||||
|
||||
constexpr bool equals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.compare(subvalue) == 0;
|
||||
}
|
||||
|
||||
constexpr bool contains(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
return value.find(subvalue) != std::string_view::npos;
|
||||
}
|
||||
|
||||
constexpr bool includes(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
std::string_view input(value);
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view start(input);
|
||||
while(!input.empty() && !IS_WS(input.front()))
|
||||
input.remove_prefix(1);
|
||||
if(subvalue == start.substr(0, start.length() - input.length())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool startswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(0, subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool endswith(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(subvalue.empty() || subvalue.length() > value.length())
|
||||
return false;
|
||||
return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size());
|
||||
}
|
||||
|
||||
constexpr bool dashequals(std::string_view value, std::string_view subvalue)
|
||||
{
|
||||
if(startswith(value, subvalue))
|
||||
return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-');
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchAttributeSelector(const AttributeSelector& selector, const SVGElement* element)
|
||||
{
|
||||
const auto& value = element->getAttribute(selector.id);
|
||||
if(selector.matchType == AttributeSelector::MatchType::None)
|
||||
return !value.empty();
|
||||
if(selector.matchType == AttributeSelector::MatchType::Equals)
|
||||
return equals(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Contains)
|
||||
return contains(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::Includes)
|
||||
return includes(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::StartsWith)
|
||||
return startswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::EndsWith)
|
||||
return endswith(value, selector.value);
|
||||
if(selector.matchType == AttributeSelector::MatchType::DashEquals)
|
||||
return dashequals(value, selector.value);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element);
|
||||
|
||||
static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.type == PseudoClassSelector::Type::Empty)
|
||||
return element->children().empty();
|
||||
if(selector.type == PseudoClassSelector::Type::Root)
|
||||
return element->isRootElement();
|
||||
if(selector.type == PseudoClassSelector::Type::Is) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(!matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::Not) {
|
||||
for(const auto& subSelector : selector.subSelectors) {
|
||||
for(const auto& simpleSelector : subSelector) {
|
||||
if(matchSimpleSelector(simpleSelector, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::FirstChild)
|
||||
return !element->previousElement();
|
||||
if(selector.type == PseudoClassSelector::Type::LastChild)
|
||||
return !element->nextElement();
|
||||
if(selector.type == PseudoClassSelector::Type::OnlyChild)
|
||||
return !(element->previousElement() || element->nextElement());
|
||||
if(selector.type == PseudoClassSelector::Type::FirstOfType) {
|
||||
auto sibling = element->previousElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->previousElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selector.type == PseudoClassSelector::Type::LastOfType) {
|
||||
auto sibling = element->nextElement();
|
||||
while(sibling) {
|
||||
if(sibling->id() == element->id())
|
||||
return false;
|
||||
sibling = sibling->nextElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.id != ElementID::Star && selector.id != element->id())
|
||||
return false;
|
||||
for(const auto& sel : selector.attributeSelectors) {
|
||||
if(!matchAttributeSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& sel : selector.pseudoClassSelectors) {
|
||||
if(!matchPseudoClassSelector(sel, element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool matchSelector(const Selector& selector, const SVGElement* element)
|
||||
{
|
||||
if(selector.empty())
|
||||
return false;
|
||||
auto it = selector.rbegin();
|
||||
auto end = selector.rend();
|
||||
if(!matchSimpleSelector(*it, element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto combinator = it->combinator;
|
||||
++it;
|
||||
|
||||
while(it != end) {
|
||||
switch(combinator) {
|
||||
case SimpleSelector::Combinator::Child:
|
||||
case SimpleSelector::Combinator::Descendant:
|
||||
element = element->parentElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::DirectAdjacent:
|
||||
case SimpleSelector::Combinator::InDirectAdjacent:
|
||||
element = element->previousElement();
|
||||
break;
|
||||
case SimpleSelector::Combinator::None:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if(element == nullptr)
|
||||
return false;
|
||||
if(matchSimpleSelector(*it, element)) {
|
||||
combinator = it->combinator;
|
||||
++it;
|
||||
} else if(combinator != SimpleSelector::Combinator::Descendant
|
||||
&& combinator != SimpleSelector::Combinator::InDirectAdjacent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuleData::match(const SVGElement* element) const
|
||||
{
|
||||
return matchSelector(m_selector, element);
|
||||
}
|
||||
|
||||
constexpr bool IS_CSS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == '-'; }
|
||||
constexpr bool IS_CSS_NAMECHAR(int c) { return IS_CSS_STARTNAMECHAR(c) || IS_NUM(c); }
|
||||
|
||||
inline bool readCSSIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_CSS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_CSS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(skipDelimiter(input, '*'))
|
||||
simpleSelector.id = ElementID::Star;
|
||||
else if(readCSSIdentifier(input, name))
|
||||
simpleSelector.id = elementid(name);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Id;
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
AttributeSelector a;
|
||||
a.id = PropertyID::Class;
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
if(!readCSSIdentifier(input, a.value))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
AttributeSelector a;
|
||||
a.id = propertyid(name);
|
||||
a.matchType = AttributeSelector::MatchType::None;
|
||||
if(skipDelimiter(input, '='))
|
||||
a.matchType = AttributeSelector::MatchType::Equals;
|
||||
else if(skipString(input, "*="))
|
||||
a.matchType = AttributeSelector::MatchType::Contains;
|
||||
else if(skipString(input, "~="))
|
||||
a.matchType = AttributeSelector::MatchType::Includes;
|
||||
else if(skipString(input, "^="))
|
||||
a.matchType = AttributeSelector::MatchType::StartsWith;
|
||||
else if(skipString(input, "$="))
|
||||
a.matchType = AttributeSelector::MatchType::EndsWith;
|
||||
else if(skipString(input, "|="))
|
||||
a.matchType = AttributeSelector::MatchType::DashEquals;
|
||||
if(a.matchType != AttributeSelector::MatchType::None) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!readCSSIdentifier(input, a.value)) {
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
a.value.assign(input.substr(0, n));
|
||||
input.remove_prefix(n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ']'))
|
||||
return false;
|
||||
simpleSelector.attributeSelectors.push_back(std::move(a));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors);
|
||||
|
||||
static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
|
||||
{
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
PseudoClassSelector selector;
|
||||
if(name.compare("empty") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Empty;
|
||||
else if(name.compare("root") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Root;
|
||||
else if(name.compare("not") == 0)
|
||||
selector.type = PseudoClassSelector::Type::Not;
|
||||
else if(name.compare("first-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstChild;
|
||||
else if(name.compare("last-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastChild;
|
||||
else if(name.compare("only-child") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyChild;
|
||||
else if(name.compare("first-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::FirstOfType;
|
||||
else if(name.compare("last-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::LastOfType;
|
||||
else if(name.compare("only-of-type") == 0)
|
||||
selector.type = PseudoClassSelector::Type::OnlyOfType;
|
||||
if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '('))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!parseSelectors(input, selector.subSelectors))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ')')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
simpleSelector.pseudoClassSelectors.push_back(std::move(selector));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector, bool& failed)
|
||||
{
|
||||
auto consumed = parseTagSelector(input, simpleSelector);
|
||||
do {
|
||||
if(skipDelimiter(input, '#'))
|
||||
failed = !parseIdSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '.'))
|
||||
failed = !parseClassSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, '['))
|
||||
failed = !parseAttributeSelector(input, simpleSelector);
|
||||
else if(skipDelimiter(input, ':'))
|
||||
failed = !parsePseudoClassSelector(input, simpleSelector);
|
||||
else
|
||||
break;
|
||||
consumed = true;
|
||||
} while(!failed);
|
||||
return consumed && !failed;
|
||||
}
|
||||
|
||||
static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator& combinator)
|
||||
{
|
||||
combinator = SimpleSelector::Combinator::None;
|
||||
while(!input.empty() && IS_WS(input.front())) {
|
||||
combinator = SimpleSelector::Combinator::Descendant;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
if(skipDelimiterAndOptionalSpaces(input, '>'))
|
||||
combinator = SimpleSelector::Combinator::Child;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '+'))
|
||||
combinator = SimpleSelector::Combinator::DirectAdjacent;
|
||||
else if(skipDelimiterAndOptionalSpaces(input, '~'))
|
||||
combinator = SimpleSelector::Combinator::InDirectAdjacent;
|
||||
return combinator != SimpleSelector::Combinator::None;
|
||||
}
|
||||
|
||||
static bool parseSelector(std::string_view& input, Selector& selector)
|
||||
{
|
||||
auto combinator = SimpleSelector::Combinator::None;
|
||||
do {
|
||||
bool failed = false;
|
||||
SimpleSelector simpleSelector(combinator);
|
||||
if(!parseSimpleSelector(input, simpleSelector, failed))
|
||||
return !failed && (combinator == SimpleSelector::Combinator::Descendant);
|
||||
selector.push_back(std::move(simpleSelector));
|
||||
} while(parseCombinator(input, combinator));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSelectors(std::string_view& input, SelectorList& selectors)
|
||||
{
|
||||
do {
|
||||
Selector selector;
|
||||
if(!parseSelector(input, selector))
|
||||
return false;
|
||||
selectors.push_back(std::move(selector));
|
||||
} while(skipDelimiterAndOptionalSpaces(input, ','));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseDeclarations(std::string_view& input, DeclarationList& declarations)
|
||||
{
|
||||
if(!skipDelimiter(input, '{'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
do {
|
||||
std::string name;
|
||||
if(!readCSSIdentifier(input, name))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
std::string_view value(input);
|
||||
while(!input.empty() && !(input.front() == '!' || input.front() == ';' || input.front() == '}'))
|
||||
input.remove_prefix(1);
|
||||
value.remove_suffix(input.length());
|
||||
stripTrailingSpaces(value);
|
||||
|
||||
Declaration declaration;
|
||||
declaration.specificity = 0x10;
|
||||
declaration.id = csspropertyid(name);
|
||||
declaration.value.assign(value);
|
||||
if(skipDelimiter(input, '!')) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipString(input, "important"))
|
||||
return false;
|
||||
declaration.specificity = 0x1000;
|
||||
}
|
||||
|
||||
if(declaration.id != PropertyID::Unknown)
|
||||
declarations.push_back(std::move(declaration));
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
} while(!input.empty() && input.front() != '}');
|
||||
return skipDelimiter(input, '}');
|
||||
}
|
||||
|
||||
static bool parseRule(std::string_view& input, Rule& rule)
|
||||
{
|
||||
if(!parseSelectors(input, rule.selectors))
|
||||
return false;
|
||||
return parseDeclarations(input, rule.declarations);
|
||||
}
|
||||
|
||||
static RuleDataList parseStyleSheet(std::string_view input)
|
||||
{
|
||||
RuleDataList rules;
|
||||
while(!input.empty()) {
|
||||
skipOptionalSpaces(input);
|
||||
if(skipDelimiter(input, '@')) {
|
||||
int depth = 0;
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch == ';' && depth == 0)
|
||||
break;
|
||||
if(ch == '{') ++depth;
|
||||
else if(ch == '}' && depth > 0) {
|
||||
if(depth == 1)
|
||||
break;
|
||||
--depth;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Rule rule;
|
||||
if(!parseRule(input, rule))
|
||||
break;
|
||||
for(const auto& selector : rule.selectors) {
|
||||
size_t specificity = 0;
|
||||
for(const auto& simpleSelector : selector) {
|
||||
specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1;
|
||||
for(const auto& attributeSelector : simpleSelector.attributeSelectors) {
|
||||
specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100;
|
||||
}
|
||||
for(const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) {
|
||||
specificity += 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
rules.emplace_back(selector, rule.declarations, specificity, rules.size());
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
static SelectorList parseQuerySelectors(std::string_view input)
|
||||
{
|
||||
SelectorList selectors;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseSelectors(input, selectors)
|
||||
|| !input.empty()) {
|
||||
return SelectorList();
|
||||
}
|
||||
|
||||
return selectors;
|
||||
}
|
||||
|
||||
inline void parseInlineStyle(std::string_view input, SVGElement* element)
|
||||
{
|
||||
std::string name;
|
||||
skipOptionalSpaces(input);
|
||||
while(readCSSIdentifier(input, name)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, ':'))
|
||||
return;
|
||||
std::string value;
|
||||
while(!input.empty() && input.front() != ';') {
|
||||
value.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto id = csspropertyid(name);
|
||||
if(id != PropertyID::Unknown)
|
||||
element->setAttribute(0x100, id, value);
|
||||
skipOptionalSpacesOrDelimiter(input, ';');
|
||||
}
|
||||
}
|
||||
|
||||
inline void removeStyleComments(std::string& value)
|
||||
{
|
||||
auto start = value.find("/*");
|
||||
while(start != std::string::npos) {
|
||||
auto end = value.find("*/", start + 2);
|
||||
value.erase(start, end - start + 2);
|
||||
start = value.find("/*");
|
||||
}
|
||||
}
|
||||
|
||||
inline bool decodeText(std::string_view input, std::string& output)
|
||||
{
|
||||
output.clear();
|
||||
while(!input.empty()) {
|
||||
auto ch = input.front();
|
||||
input.remove_prefix(1);
|
||||
if(ch != '&') {
|
||||
output.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '#')) {
|
||||
int base = 10;
|
||||
if(skipDelimiter(input, 'x'))
|
||||
base = 16;
|
||||
unsigned int cp;
|
||||
if(!parseInteger(input, cp, base))
|
||||
return false;
|
||||
char c[5] = {0, 0, 0, 0, 0};
|
||||
if(cp < 0x80) {
|
||||
c[1] = 0;
|
||||
c[0] = char(cp);
|
||||
} else if(cp < 0x800) {
|
||||
c[2] = 0;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xC0);
|
||||
} else if(cp < 0x10000) {
|
||||
c[3] = 0;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xE0);
|
||||
} else if(cp < 0x200000) {
|
||||
c[4] = 0;
|
||||
c[3] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[2] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[1] = char((cp & 0x3F) | 0x80);
|
||||
cp >>= 6;
|
||||
c[0] = char(cp | 0xF0);
|
||||
}
|
||||
|
||||
output.append(c);
|
||||
} else {
|
||||
if(skipString(input, "amp")) {
|
||||
output.push_back('&');
|
||||
} else if(skipString(input, "lt")) {
|
||||
output.push_back('<');
|
||||
} else if(skipString(input, "gt")) {
|
||||
output.push_back('>');
|
||||
} else if(skipString(input, "quot")) {
|
||||
output.push_back('\"');
|
||||
} else if(skipString(input, "apos")) {
|
||||
output.push_back('\'');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, ';')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool IS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == ':'; }
|
||||
constexpr bool IS_NAMECHAR(int c) { return IS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-' || c == '.'; }
|
||||
|
||||
inline bool readIdentifier(std::string_view& input, std::string& output)
|
||||
{
|
||||
if(input.empty() || !IS_STARTNAMECHAR(input.front()))
|
||||
return false;
|
||||
output.clear();
|
||||
do {
|
||||
output.push_back(input.front());
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NAMECHAR(input.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Document::parse(const char* data, size_t length)
|
||||
{
|
||||
std::string buffer;
|
||||
std::string styleSheet;
|
||||
SVGElement* currentElement = nullptr;
|
||||
int ignoring = 0;
|
||||
auto handleText = [&](std::string_view text, bool in_cdata) {
|
||||
if(text.empty() || currentElement == nullptr || ignoring > 0)
|
||||
return;
|
||||
if(currentElement->id() != ElementID::Text && currentElement->id() != ElementID::Tspan && currentElement->id() != ElementID::Style) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_cdata) {
|
||||
buffer.assign(text);
|
||||
} else {
|
||||
decodeText(text, buffer);
|
||||
}
|
||||
|
||||
if(currentElement->id() == ElementID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
styleSheet.append(buffer);
|
||||
} else {
|
||||
auto node = std::make_unique<SVGTextNode>(this);
|
||||
node->setData(buffer);
|
||||
currentElement->addChild(std::move(node));
|
||||
}
|
||||
};
|
||||
|
||||
std::string_view input(data, length);
|
||||
if(length >= 3) {
|
||||
auto buffer = (const uint8_t*)(data);
|
||||
|
||||
const auto c1 = buffer[0];
|
||||
const auto c2 = buffer[1];
|
||||
const auto c3 = buffer[2];
|
||||
if(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
|
||||
input.remove_prefix(3);
|
||||
}
|
||||
}
|
||||
|
||||
while(!input.empty()) {
|
||||
if(currentElement) {
|
||||
auto text = input.substr(0, input.find('<'));
|
||||
handleText(text, false);
|
||||
input.remove_prefix(text.length());
|
||||
} else {
|
||||
if(!skipOptionalSpaces(input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '<'))
|
||||
return false;
|
||||
if(skipDelimiter(input, '?')) {
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
auto n = input.find("?>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
input.remove_prefix(n + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '!')) {
|
||||
if(skipString(input, "--")) {
|
||||
auto n = input.find("-->");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), false);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "[CDATA[")) {
|
||||
auto n = input.find("]]>");
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
handleText(input.substr(0, n), true);
|
||||
input.remove_prefix(n + 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipString(input, "DOCTYPE")) {
|
||||
while(!input.empty() && input.front() != '>') {
|
||||
if(input.front() == '[') {
|
||||
int depth = 1;
|
||||
input.remove_prefix(1);
|
||||
while(!input.empty() && depth > 0) {
|
||||
if(input.front() == '[') ++depth;
|
||||
else if(input.front() == ']') --depth;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
} else {
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(currentElement == nullptr && ignoring == 0)
|
||||
return false;
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
if(ignoring == 0) {
|
||||
auto id = elementid(buffer);
|
||||
if(id != currentElement->id())
|
||||
return false;
|
||||
currentElement = currentElement->parentElement();
|
||||
} else {
|
||||
--ignoring;
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!readIdentifier(input, buffer))
|
||||
return false;
|
||||
SVGElement* element = nullptr;
|
||||
if(ignoring > 0) {
|
||||
++ignoring;
|
||||
} else {
|
||||
auto id = elementid(buffer);
|
||||
if(id == ElementID::Unknown) {
|
||||
ignoring = 1;
|
||||
} else {
|
||||
if(m_rootElement && currentElement == nullptr)
|
||||
return false;
|
||||
if(m_rootElement == nullptr) {
|
||||
if(id != ElementID::Svg)
|
||||
return false;
|
||||
m_rootElement = std::make_unique<SVGRootElement>(this);
|
||||
element = m_rootElement.get();
|
||||
} else {
|
||||
auto child = SVGElement::create(this, id);
|
||||
element = child.get();
|
||||
currentElement->addChild(std::move(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skipOptionalSpaces(input);
|
||||
while(readIdentifier(input, buffer)) {
|
||||
skipOptionalSpaces(input);
|
||||
if(!skipDelimiter(input, '='))
|
||||
return false;
|
||||
skipOptionalSpaces(input);
|
||||
if(input.empty() || !(input.front() == '\"' || input.front() == '\''))
|
||||
return false;
|
||||
auto quote = input.front();
|
||||
input.remove_prefix(1);
|
||||
auto n = input.find(quote);
|
||||
if(n == std::string_view::npos)
|
||||
return false;
|
||||
auto id = PropertyID::Unknown;
|
||||
if(element != nullptr)
|
||||
id = propertyid(buffer);
|
||||
if(id != PropertyID::Unknown) {
|
||||
decodeText(input.substr(0, n), buffer);
|
||||
if(id == PropertyID::Style) {
|
||||
removeStyleComments(buffer);
|
||||
parseInlineStyle(buffer, element);
|
||||
} else {
|
||||
if(id == PropertyID::Id)
|
||||
m_rootElement->addElementById(buffer, element);
|
||||
element->setAttribute(0x1, id, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
input.remove_prefix(n + 1);
|
||||
skipOptionalSpaces(input);
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '>')) {
|
||||
if(element != nullptr)
|
||||
currentElement = element;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipDelimiter(input, '/')) {
|
||||
if(!skipDelimiter(input, '>'))
|
||||
return false;
|
||||
if(ignoring > 0)
|
||||
--ignoring;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_rootElement == nullptr || ignoring > 0 || !input.empty())
|
||||
return false;
|
||||
applyStyleSheet(styleSheet);
|
||||
m_rootElement->build();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Document::applyStyleSheet(const std::string& content)
|
||||
{
|
||||
auto rules = parseStyleSheet(content);
|
||||
if(!rules.empty()) {
|
||||
std::sort(rules.begin(), rules.end());
|
||||
m_rootElement->transverse([&rules](SVGElement* element) {
|
||||
for(const auto& rule : rules) {
|
||||
if(rule.match(element)) {
|
||||
for(const auto& declaration : rule.declarations()) {
|
||||
element->setAttribute(declaration.specificity, declaration.id, declaration.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ElementList Document::querySelectorAll(const std::string& content) const
|
||||
{
|
||||
auto selectors = parseQuerySelectors(content);
|
||||
if(selectors.empty())
|
||||
return ElementList();
|
||||
ElementList elements;
|
||||
m_rootElement->transverse([&](SVGElement* element) {
|
||||
for(const auto& selector : selectors) {
|
||||
if(matchSelector(selector, element)) {
|
||||
elements.push_back(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
210
vendor/lunasvg/source/svgparserutils.h
vendored
Normal file
210
vendor/lunasvg/source/svgparserutils.h
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
#ifndef LUNASVG_SVGPARSERUTILS_H
|
||||
#define LUNASVG_SVGPARSERUTILS_H
|
||||
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
#include <limits>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
constexpr bool IS_NUM(int cc) { return cc >= '0' && cc <= '9'; }
|
||||
constexpr bool IS_ALPHA(int cc) { return (cc >= 'a' && cc <= 'z') || (cc >= 'A' && cc <= 'Z'); }
|
||||
constexpr bool IS_WS(int cc) { return cc == ' ' || cc == '\t' || cc == '\n' || cc == '\r'; }
|
||||
|
||||
constexpr void stripLeadingSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.front())) {
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void stripTrailingSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.back())) {
|
||||
input.remove_suffix(1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void stripLeadingAndTrailingSpaces(std::string_view& input)
|
||||
{
|
||||
stripLeadingSpaces(input);
|
||||
stripTrailingSpaces(input);
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpaces(std::string_view& input)
|
||||
{
|
||||
while(!input.empty() && IS_WS(input.front()))
|
||||
input.remove_prefix(1);
|
||||
return !input.empty();
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpacesOrDelimiter(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && !IS_WS(input.front()) && delimiter != input.front())
|
||||
return false;
|
||||
if(skipOptionalSpaces(input)) {
|
||||
if(delimiter == input.front()) {
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
}
|
||||
}
|
||||
|
||||
return !input.empty();
|
||||
}
|
||||
|
||||
constexpr bool skipOptionalSpacesOrComma(std::string_view& input)
|
||||
{
|
||||
return skipOptionalSpacesOrDelimiter(input, ',');
|
||||
}
|
||||
|
||||
constexpr bool skipDelimiterAndOptionalSpaces(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && input.front() == delimiter) {
|
||||
input.remove_prefix(1);
|
||||
skipOptionalSpaces(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool skipDelimiter(std::string_view& input, char delimiter)
|
||||
{
|
||||
if(!input.empty() && input.front() == delimiter) {
|
||||
input.remove_prefix(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool skipString(std::string_view& input, std::string_view value)
|
||||
{
|
||||
if(input.size() >= value.size() && value == input.substr(0, value.size())) {
|
||||
input.remove_prefix(value.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool isIntegralDigit(char ch, int base)
|
||||
{
|
||||
if(IS_NUM(ch))
|
||||
return ch - '0' < base;
|
||||
if(IS_ALPHA(ch))
|
||||
return (ch >= 'a' && ch < 'a' + base - 10) || (ch >= 'A' && ch < 'A' + base - 10);
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int toIntegralDigit(char ch)
|
||||
{
|
||||
if(IS_NUM(ch))
|
||||
return ch - '0';
|
||||
if(ch >= 'a')
|
||||
return ch - 'a' + 10;
|
||||
return ch - 'A' + 10;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool parseInteger(std::string_view& input, T& integer, int base = 10)
|
||||
{
|
||||
constexpr bool isSigned = std::numeric_limits<T>::is_signed;
|
||||
constexpr T intMax = std::numeric_limits<T>::max();
|
||||
const T maxMultiplier = intMax / static_cast<T>(base);
|
||||
|
||||
T value = 0;
|
||||
bool isNegative = false;
|
||||
|
||||
if(!input.empty() && input.front() == '+') {
|
||||
input.remove_prefix(1);
|
||||
} else if(!input.empty() && isSigned && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
isNegative = true;
|
||||
}
|
||||
|
||||
if(input.empty() || !isIntegralDigit(input.front(), base))
|
||||
return false;
|
||||
do {
|
||||
const int digitValue = toIntegralDigit(input.front());
|
||||
if(value > maxMultiplier || (value == maxMultiplier && static_cast<T>(digitValue) > (intMax % static_cast<T>(base)) + isNegative))
|
||||
return false;
|
||||
value = static_cast<T>(base) * value + static_cast<T>(digitValue);
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && isIntegralDigit(input.front(), base));
|
||||
|
||||
using SignedType = typename std::make_signed<T>::type;
|
||||
if(isNegative)
|
||||
integer = -static_cast<SignedType>(value);
|
||||
else
|
||||
integer = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool parseNumber(std::string_view& input, T& number)
|
||||
{
|
||||
constexpr T maxValue = std::numeric_limits<T>::max();
|
||||
T integer = 0;
|
||||
T fraction = 0;
|
||||
int exponent = 0;
|
||||
int sign = 1;
|
||||
int expsign = 1;
|
||||
|
||||
if(!input.empty() && input.front() == '+') {
|
||||
input.remove_prefix(1);
|
||||
} else if(!input.empty() && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
if(input.empty() || (!IS_NUM(input.front()) && input.front() != '.'))
|
||||
return false;
|
||||
if(IS_NUM(input.front())) {
|
||||
do {
|
||||
integer = static_cast<T>(10) * integer + (input.front() - '0');
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
}
|
||||
|
||||
if(!input.empty() && input.front() == '.') {
|
||||
input.remove_prefix(1);
|
||||
if(input.empty() || !IS_NUM(input.front()))
|
||||
return false;
|
||||
T divisor = static_cast<T>(1);
|
||||
do {
|
||||
fraction = static_cast<T>(10) * fraction + (input.front() - '0');
|
||||
divisor *= static_cast<T>(10);
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
fraction /= divisor;
|
||||
}
|
||||
|
||||
if(input.size() > 1 && (input[0] == 'e' || input[0] == 'E')
|
||||
&& (input[1] != 'x' && input[1] != 'm'))
|
||||
{
|
||||
input.remove_prefix(1);
|
||||
if(!input.empty() && input.front() == '+')
|
||||
input.remove_prefix(1);
|
||||
else if(!input.empty() && input.front() == '-') {
|
||||
input.remove_prefix(1);
|
||||
expsign = -1;
|
||||
}
|
||||
|
||||
if(input.empty() || !IS_NUM(input.front()))
|
||||
return false;
|
||||
do {
|
||||
exponent = 10 * exponent + (input.front() - '0');
|
||||
input.remove_prefix(1);
|
||||
} while(!input.empty() && IS_NUM(input.front()));
|
||||
}
|
||||
|
||||
number = sign * (integer + fraction);
|
||||
if(exponent)
|
||||
number *= static_cast<T>(std::pow(10.0, expsign * exponent));
|
||||
return number >= -maxValue && number <= maxValue;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPARSERUTILS_H
|
||||
705
vendor/lunasvg/source/svgproperty.cpp
vendored
Normal file
705
vendor/lunasvg/source/svgproperty.cpp
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
#include "svgproperty.h"
|
||||
#include "svgelement.h"
|
||||
#include "svgparserutils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
PropertyID propertyid(std::string_view name)
|
||||
{
|
||||
static const struct {
|
||||
std::string_view name;
|
||||
PropertyID value;
|
||||
} table[] = {
|
||||
{"class", PropertyID::Class},
|
||||
{"clipPathUnits", PropertyID::ClipPathUnits},
|
||||
{"cx", PropertyID::Cx},
|
||||
{"cy", PropertyID::Cy},
|
||||
{"d", PropertyID::D},
|
||||
{"dx", PropertyID::Dx},
|
||||
{"dy", PropertyID::Dy},
|
||||
{"fx", PropertyID::Fx},
|
||||
{"fy", PropertyID::Fy},
|
||||
{"gradientTransform", PropertyID::GradientTransform},
|
||||
{"gradientUnits", PropertyID::GradientUnits},
|
||||
{"height", PropertyID::Height},
|
||||
{"href", PropertyID::Href},
|
||||
{"id", PropertyID::Id},
|
||||
{"lengthAdjust", PropertyID::LengthAdjust},
|
||||
{"markerHeight", PropertyID::MarkerHeight},
|
||||
{"markerUnits", PropertyID::MarkerUnits},
|
||||
{"markerWidth", PropertyID::MarkerWidth},
|
||||
{"maskContentUnits", PropertyID::MaskContentUnits},
|
||||
{"maskUnits", PropertyID::MaskUnits},
|
||||
{"offset", PropertyID::Offset},
|
||||
{"orient", PropertyID::Orient},
|
||||
{"patternContentUnits", PropertyID::PatternContentUnits},
|
||||
{"patternTransform", PropertyID::PatternTransform},
|
||||
{"patternUnits", PropertyID::PatternUnits},
|
||||
{"points", PropertyID::Points},
|
||||
{"preserveAspectRatio", PropertyID::PreserveAspectRatio},
|
||||
{"r", PropertyID::R},
|
||||
{"refX", PropertyID::RefX},
|
||||
{"refY", PropertyID::RefY},
|
||||
{"rotate", PropertyID::Rotate},
|
||||
{"rx", PropertyID::Rx},
|
||||
{"ry", PropertyID::Ry},
|
||||
{"spreadMethod", PropertyID::SpreadMethod},
|
||||
{"style", PropertyID::Style},
|
||||
{"textLength", PropertyID::TextLength},
|
||||
{"transform", PropertyID::Transform},
|
||||
{"viewBox", PropertyID::ViewBox},
|
||||
{"width", PropertyID::Width},
|
||||
{"x", PropertyID::X},
|
||||
{"x1", PropertyID::X1},
|
||||
{"x2", PropertyID::X2},
|
||||
{"xlink:href", PropertyID::Href},
|
||||
{"xml:space", PropertyID::White_Space},
|
||||
{"y", PropertyID::Y},
|
||||
{"y1", PropertyID::Y1},
|
||||
{"y2", PropertyID::Y2}
|
||||
};
|
||||
|
||||
auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
|
||||
if(it == std::end(table) || it->name != name)
|
||||
return csspropertyid(name);
|
||||
return it->value;
|
||||
}
|
||||
|
||||
PropertyID csspropertyid(std::string_view name)
|
||||
{
|
||||
static const struct {
|
||||
std::string_view name;
|
||||
PropertyID value;
|
||||
} table[] = {
|
||||
{"alignment-baseline", PropertyID::Alignment_Baseline},
|
||||
{"baseline-shift", PropertyID::Baseline_Shift},
|
||||
{"clip-path", PropertyID::Clip_Path},
|
||||
{"clip-rule", PropertyID::Clip_Rule},
|
||||
{"color", PropertyID::Color},
|
||||
{"direction", PropertyID::Direction},
|
||||
{"display", PropertyID::Display},
|
||||
{"dominant-baseline", PropertyID::Dominant_Baseline},
|
||||
{"fill", PropertyID::Fill},
|
||||
{"fill-opacity", PropertyID::Fill_Opacity},
|
||||
{"fill-rule", PropertyID::Fill_Rule},
|
||||
{"font-family", PropertyID::Font_Family},
|
||||
{"font-size", PropertyID::Font_Size},
|
||||
{"font-style", PropertyID::Font_Style},
|
||||
{"font-weight", PropertyID::Font_Weight},
|
||||
{"letter-spacing", PropertyID::Letter_Spacing},
|
||||
{"marker-end", PropertyID::Marker_End},
|
||||
{"marker-mid", PropertyID::Marker_Mid},
|
||||
{"marker-start", PropertyID::Marker_Start},
|
||||
{"mask", PropertyID::Mask},
|
||||
{"mask-type", PropertyID::Mask_Type},
|
||||
{"opacity", PropertyID::Opacity},
|
||||
{"overflow", PropertyID::Overflow},
|
||||
{"pointer-events", PropertyID::Pointer_Events},
|
||||
{"stop-color", PropertyID::Stop_Color},
|
||||
{"stop-opacity", PropertyID::Stop_Opacity},
|
||||
{"stroke", PropertyID::Stroke},
|
||||
{"stroke-dasharray", PropertyID::Stroke_Dasharray},
|
||||
{"stroke-dashoffset", PropertyID::Stroke_Dashoffset},
|
||||
{"stroke-linecap", PropertyID::Stroke_Linecap},
|
||||
{"stroke-linejoin", PropertyID::Stroke_Linejoin},
|
||||
{"stroke-miterlimit", PropertyID::Stroke_Miterlimit},
|
||||
{"stroke-opacity", PropertyID::Stroke_Opacity},
|
||||
{"stroke-width", PropertyID::Stroke_Width},
|
||||
{"text-anchor", PropertyID::Text_Anchor},
|
||||
{"text-orientation", PropertyID::Text_Orientation},
|
||||
{"visibility", PropertyID::Visibility},
|
||||
{"white-space", PropertyID::White_Space},
|
||||
{"word-spacing", PropertyID::Word_Spacing},
|
||||
{"writing-mode", PropertyID::Writing_Mode}
|
||||
};
|
||||
|
||||
auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
|
||||
if(it == std::end(table) || it->name != name)
|
||||
return PropertyID::Unknown;
|
||||
return it->value;
|
||||
}
|
||||
|
||||
SVGProperty::SVGProperty(PropertyID id)
|
||||
: m_id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool SVGString::parse(std::string_view input)
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
m_value.assign(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<SpreadMethod>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<SpreadMethod> entries[] = {
|
||||
{SpreadMethod::Pad, "pad"},
|
||||
{SpreadMethod::Reflect, "reflect"},
|
||||
{SpreadMethod::Repeat, "repeat"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<Units>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<Units> entries[] = {
|
||||
{Units::UserSpaceOnUse, "userSpaceOnUse"},
|
||||
{Units::ObjectBoundingBox, "objectBoundingBox"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<MarkerUnits>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<MarkerUnits> entries[] = {
|
||||
{MarkerUnits::StrokeWidth, "strokeWidth"},
|
||||
{MarkerUnits::UserSpaceOnUse, "userSpaceOnUse"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool SVGEnumeration<LengthAdjust>::parse(std::string_view input)
|
||||
{
|
||||
static const SVGEnumerationEntry<LengthAdjust> entries[] = {
|
||||
{LengthAdjust::Spacing, "spacing"},
|
||||
{LengthAdjust::SpacingAndGlyphs, "spacingAndGlyphs"}
|
||||
};
|
||||
|
||||
return parseEnum(input, entries);
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
template<unsigned int N>
|
||||
bool SVGEnumeration<Enum>::parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N])
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
for(const auto& entry : entries) {
|
||||
if(input == entry.second) {
|
||||
m_value = entry.first;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SVGAngle::parse(std::string_view input)
|
||||
{
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(input == "auto") {
|
||||
m_value = 0.f;
|
||||
m_orientType = OrientType::Auto;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(input == "auto-start-reverse") {
|
||||
m_value = 0.f;
|
||||
m_orientType = OrientType::AutoStartReverse;
|
||||
return true;
|
||||
}
|
||||
|
||||
float value = 0.f;
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty()) {
|
||||
if(input == "rad")
|
||||
value *= 180.f / PLUTOVG_PI;
|
||||
else if(input == "grad")
|
||||
value *= 360.f / 400.f;
|
||||
else if(input == "turn")
|
||||
value *= 360.f;
|
||||
else if(input != "deg") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_value = value;
|
||||
m_orientType = OrientType::Angle;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Length::parse(std::string_view input, LengthNegativeMode mode)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(value < 0.f && mode == LengthNegativeMode::Forbid)
|
||||
return false;
|
||||
if(input.empty()) {
|
||||
m_value = value;
|
||||
m_units = LengthUnits::None;
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr auto dpi = 96.f;
|
||||
switch(input.front()) {
|
||||
case '%':
|
||||
m_value = value;
|
||||
m_units = LengthUnits::Percent;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'p':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'x')
|
||||
m_value = value;
|
||||
else if(input.front() == 'c')
|
||||
m_value = value * dpi / 6.f;
|
||||
else if(input.front() == 't')
|
||||
m_value = value * dpi / 72.f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'i':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'n')
|
||||
m_value = value * dpi;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'c':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_value = value * dpi / 2.54f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'm':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_value = value * dpi / 25.4f;
|
||||
else
|
||||
return false;
|
||||
m_units = LengthUnits::Px;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
case 'e':
|
||||
input.remove_prefix(1);
|
||||
if(input.empty())
|
||||
return false;
|
||||
else if(input.front() == 'm')
|
||||
m_units = LengthUnits::Em;
|
||||
else if(input.front() == 'x')
|
||||
m_units = LengthUnits::Ex;
|
||||
else
|
||||
return false;
|
||||
m_value = value;
|
||||
input.remove_prefix(1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return input.empty();
|
||||
}
|
||||
|
||||
float LengthContext::valueForLength(const Length& length, LengthDirection direction) const
|
||||
{
|
||||
if(length.units() == LengthUnits::Percent) {
|
||||
if(m_units == Units::UserSpaceOnUse)
|
||||
return length.value() * viewportDimension(direction) / 100.f;
|
||||
return length.value() / 100.f;
|
||||
}
|
||||
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * m_element->font_size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * m_element->font_size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
float LengthContext::viewportDimension(LengthDirection direction) const
|
||||
{
|
||||
auto viewportSize = m_element->currentViewportSize();
|
||||
switch(direction) {
|
||||
case LengthDirection::Horizontal:
|
||||
return viewportSize.w;
|
||||
case LengthDirection::Vertical:
|
||||
return viewportSize.h;
|
||||
default:
|
||||
return std::sqrt(viewportSize.w * viewportSize.w + viewportSize.h * viewportSize.h) / PLUTOVG_SQRT2;
|
||||
}
|
||||
}
|
||||
|
||||
bool SVGLength::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input, m_negativeMode);
|
||||
}
|
||||
|
||||
bool SVGLengthList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
while(!input.empty()) {
|
||||
size_t count = 0;
|
||||
while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
|
||||
++count;
|
||||
if(count == 0)
|
||||
break;
|
||||
Length value(0, LengthUnits::None);
|
||||
if(!value.parse(input.substr(0, count), m_negativeMode))
|
||||
return false;
|
||||
input.remove_prefix(count);
|
||||
skipOptionalSpacesOrComma(input);
|
||||
m_values.push_back(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumber::parse(std::string_view input)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumberPercentage::parse(std::string_view input)
|
||||
{
|
||||
float value = 0.f;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
if(!input.empty() && input.front() == '%') {
|
||||
value /= 100.f;
|
||||
input.remove_prefix(1);
|
||||
}
|
||||
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_value = std::clamp(value, 0.f, 1.f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGNumberList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
stripLeadingSpaces(input);
|
||||
while(!input.empty()) {
|
||||
float value = 0.f;
|
||||
if(!parseNumber(input, value))
|
||||
return false;
|
||||
skipOptionalSpacesOrComma(input);
|
||||
m_values.push_back(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGPath::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input.data(), input.length());
|
||||
}
|
||||
|
||||
bool SVGPoint::parse(std::string_view input)
|
||||
{
|
||||
Point value;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpaces(input)
|
||||
|| !parseNumber(input, value.y)
|
||||
|| !input.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGPointList::parse(std::string_view input)
|
||||
{
|
||||
m_values.clear();
|
||||
stripLeadingSpaces(input);
|
||||
while(!input.empty()) {
|
||||
Point value;
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_values.push_back(value);
|
||||
skipOptionalSpacesOrComma(input);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGRect::parse(std::string_view input)
|
||||
{
|
||||
Rect value;
|
||||
stripLeadingAndTrailingSpaces(input);
|
||||
if(!parseNumber(input, value.x)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.y)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.w)
|
||||
|| !skipOptionalSpacesOrComma(input)
|
||||
|| !parseNumber(input, value.h)
|
||||
|| !input.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(value.w < 0.f || value.h < 0.f)
|
||||
return false;
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SVGTransform::parse(std::string_view input)
|
||||
{
|
||||
return m_value.parse(input.data(), input.length());
|
||||
}
|
||||
|
||||
bool SVGPreserveAspectRatio::parse(std::string_view input)
|
||||
{
|
||||
auto alignType = AlignType::xMidYMid;
|
||||
stripLeadingSpaces(input);
|
||||
if(skipString(input, "none"))
|
||||
alignType = AlignType::None;
|
||||
else if(skipString(input, "xMinYMin"))
|
||||
alignType = AlignType::xMinYMin;
|
||||
else if(skipString(input, "xMidYMin"))
|
||||
alignType = AlignType::xMidYMin;
|
||||
else if(skipString(input, "xMaxYMin"))
|
||||
alignType = AlignType::xMaxYMin;
|
||||
else if(skipString(input, "xMinYMid"))
|
||||
alignType = AlignType::xMinYMid;
|
||||
else if(skipString(input, "xMidYMid"))
|
||||
alignType = AlignType::xMidYMid;
|
||||
else if(skipString(input, "xMaxYMid"))
|
||||
alignType = AlignType::xMaxYMid;
|
||||
else if(skipString(input, "xMinYMax"))
|
||||
alignType = AlignType::xMinYMax;
|
||||
else if(skipString(input, "xMidYMax"))
|
||||
alignType = AlignType::xMidYMax;
|
||||
else if(skipString(input, "xMaxYMax"))
|
||||
alignType = AlignType::xMaxYMax;
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto meetOrSlice = MeetOrSlice::Meet;
|
||||
skipOptionalSpaces(input);
|
||||
if(skipString(input, "meet")) {
|
||||
meetOrSlice = MeetOrSlice::Meet;
|
||||
} else if(skipString(input, "slice")) {
|
||||
meetOrSlice = MeetOrSlice::Slice;
|
||||
}
|
||||
|
||||
if(alignType == AlignType::None)
|
||||
meetOrSlice = MeetOrSlice::Meet;
|
||||
skipOptionalSpaces(input);
|
||||
if(!input.empty())
|
||||
return false;
|
||||
m_alignType = alignType;
|
||||
m_meetOrSlice = meetOrSlice;
|
||||
return true;
|
||||
}
|
||||
|
||||
Rect SVGPreserveAspectRatio::getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const
|
||||
{
|
||||
assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
|
||||
auto xScale = viewportSize.w / viewBoxRect.w;
|
||||
auto yScale = viewportSize.h / viewBoxRect.h;
|
||||
if(m_alignType == AlignType::None) {
|
||||
return Rect(viewBoxRect.x, viewBoxRect.y, viewportSize.w / xScale, viewportSize.h / yScale);
|
||||
}
|
||||
|
||||
auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale);
|
||||
auto xOffset = -viewBoxRect.x * scale;
|
||||
auto yOffset = -viewBoxRect.y * scale;
|
||||
auto viewWidth = viewBoxRect.w * scale;
|
||||
auto viewHeight = viewBoxRect.h * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
xOffset += (viewportSize.w - viewWidth) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
xOffset += (viewportSize.w - viewWidth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
yOffset += (viewportSize.h - viewHeight) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
yOffset += (viewportSize.h - viewHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Rect(-xOffset / scale, -yOffset / scale, viewportSize.w / scale, viewportSize.h / scale);
|
||||
}
|
||||
|
||||
Transform SVGPreserveAspectRatio::getTransform(const Rect& viewBoxRect, const Size& viewportSize) const
|
||||
{
|
||||
assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
|
||||
auto xScale = viewportSize.w / viewBoxRect.w;
|
||||
auto yScale = viewportSize.h / viewBoxRect.h;
|
||||
if(m_alignType == AlignType::None) {
|
||||
return Transform(xScale, 0, 0, yScale, -viewBoxRect.x * xScale, -viewBoxRect.y * yScale);
|
||||
}
|
||||
|
||||
auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? std::min(xScale, yScale) : std::max(xScale, yScale);
|
||||
auto xOffset = -viewBoxRect.x * scale;
|
||||
auto yOffset = -viewBoxRect.y * scale;
|
||||
auto viewWidth = viewBoxRect.w * scale;
|
||||
auto viewHeight = viewBoxRect.h * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
xOffset += (viewportSize.w - viewWidth) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
xOffset += (viewportSize.w - viewWidth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
yOffset += (viewportSize.h - viewHeight) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
yOffset += (viewportSize.h - viewHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Transform(scale, 0, 0, scale, xOffset, yOffset);
|
||||
}
|
||||
|
||||
void SVGPreserveAspectRatio::transformRect(Rect& dstRect, Rect& srcRect) const
|
||||
{
|
||||
if(m_alignType == AlignType::None)
|
||||
return;
|
||||
auto viewSize = dstRect.size();
|
||||
auto imageSize = srcRect.size();
|
||||
if(m_meetOrSlice == MeetOrSlice::Meet) {
|
||||
auto scale = imageSize.h / imageSize.w;
|
||||
if(viewSize.h > viewSize.w * scale) {
|
||||
dstRect.h = viewSize.w * scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
dstRect.y += (viewSize.h - dstRect.h) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
dstRect.y += viewSize.h - dstRect.h;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(viewSize.w > viewSize.h / scale) {
|
||||
dstRect.w = viewSize.h / scale;
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
dstRect.x += (viewSize.w - dstRect.w) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
dstRect.x += viewSize.w - dstRect.w;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(m_meetOrSlice == MeetOrSlice::Slice) {
|
||||
auto scale = imageSize.h / imageSize.w;
|
||||
if(viewSize.h < viewSize.w * scale) {
|
||||
srcRect.h = viewSize.h * (imageSize.w / viewSize.w);
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMinYMid:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMaxYMid:
|
||||
srcRect.y += (imageSize.h - srcRect.h) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMinYMax:
|
||||
case AlignType::xMidYMax:
|
||||
case AlignType::xMaxYMax:
|
||||
srcRect.y += imageSize.h - srcRect.h;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(viewSize.w < viewSize.h / scale) {
|
||||
srcRect.w = viewSize.w * (imageSize.h / viewSize.h);
|
||||
switch(m_alignType) {
|
||||
case AlignType::xMidYMin:
|
||||
case AlignType::xMidYMid:
|
||||
case AlignType::xMidYMax:
|
||||
srcRect.x += (imageSize.w - srcRect.w) * 0.5f;
|
||||
break;
|
||||
case AlignType::xMaxYMin:
|
||||
case AlignType::xMaxYMid:
|
||||
case AlignType::xMaxYMax:
|
||||
srcRect.x += imageSize.w - srcRect.w;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
570
vendor/lunasvg/source/svgproperty.h
vendored
Normal file
570
vendor/lunasvg/source/svgproperty.h
vendored
Normal file
@@ -0,0 +1,570 @@
|
||||
#ifndef LUNASVG_SVGPROPERTY_H
|
||||
#define LUNASVG_SVGPROPERTY_H
|
||||
|
||||
#include "graphics.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class PropertyID : uint8_t {
|
||||
Unknown = 0,
|
||||
Alignment_Baseline,
|
||||
Baseline_Shift,
|
||||
Class,
|
||||
Clip_Path,
|
||||
Clip_Rule,
|
||||
ClipPathUnits,
|
||||
Color,
|
||||
Cx,
|
||||
Cy,
|
||||
D,
|
||||
Direction,
|
||||
Display,
|
||||
Dominant_Baseline,
|
||||
Dx,
|
||||
Dy,
|
||||
Fill,
|
||||
Fill_Opacity,
|
||||
Fill_Rule,
|
||||
Font_Family,
|
||||
Font_Size,
|
||||
Font_Style,
|
||||
Font_Weight,
|
||||
Fx,
|
||||
Fy,
|
||||
GradientTransform,
|
||||
GradientUnits,
|
||||
Height,
|
||||
Href,
|
||||
Id,
|
||||
LengthAdjust,
|
||||
Letter_Spacing,
|
||||
Marker_End,
|
||||
Marker_Mid,
|
||||
Marker_Start,
|
||||
MarkerHeight,
|
||||
MarkerUnits,
|
||||
MarkerWidth,
|
||||
Mask,
|
||||
Mask_Type,
|
||||
MaskContentUnits,
|
||||
MaskUnits,
|
||||
Offset,
|
||||
Opacity,
|
||||
Orient,
|
||||
Overflow,
|
||||
PatternContentUnits,
|
||||
PatternTransform,
|
||||
PatternUnits,
|
||||
Pointer_Events,
|
||||
Points,
|
||||
PreserveAspectRatio,
|
||||
R,
|
||||
RefX,
|
||||
RefY,
|
||||
Rotate,
|
||||
Rx,
|
||||
Ry,
|
||||
SpreadMethod,
|
||||
Stop_Color,
|
||||
Stop_Opacity,
|
||||
Stroke,
|
||||
Stroke_Dasharray,
|
||||
Stroke_Dashoffset,
|
||||
Stroke_Linecap,
|
||||
Stroke_Linejoin,
|
||||
Stroke_Miterlimit,
|
||||
Stroke_Opacity,
|
||||
Stroke_Width,
|
||||
Style,
|
||||
Text_Anchor,
|
||||
Text_Orientation,
|
||||
TextLength,
|
||||
Transform,
|
||||
ViewBox,
|
||||
Visibility,
|
||||
White_Space,
|
||||
Width,
|
||||
Word_Spacing,
|
||||
Writing_Mode,
|
||||
X,
|
||||
X1,
|
||||
X2,
|
||||
Y,
|
||||
Y1,
|
||||
Y2
|
||||
};
|
||||
|
||||
PropertyID propertyid(std::string_view name);
|
||||
PropertyID csspropertyid(std::string_view name);
|
||||
|
||||
class SVGElement;
|
||||
|
||||
class SVGProperty {
|
||||
public:
|
||||
SVGProperty(PropertyID id);
|
||||
virtual ~SVGProperty() = default;
|
||||
PropertyID id() const { return m_id; }
|
||||
|
||||
virtual bool parse(std::string_view input) = 0;
|
||||
|
||||
private:
|
||||
SVGProperty(const SVGProperty&) = delete;
|
||||
SVGProperty& operator=(const SVGProperty&) = delete;
|
||||
PropertyID m_id;
|
||||
};
|
||||
|
||||
class SVGString final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGString(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const std::string& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
class Paint {
|
||||
public:
|
||||
Paint() = default;
|
||||
explicit Paint(const Color& color) : m_color(color) {}
|
||||
Paint(const std::string& id, const Color& color)
|
||||
: m_id(id), m_color(color)
|
||||
{}
|
||||
|
||||
const Color& color() const { return m_color; }
|
||||
const std::string& id() const { return m_id; }
|
||||
bool isNone() const { return m_id.empty() && !m_color.isVisible(); }
|
||||
|
||||
private:
|
||||
std::string m_id;
|
||||
Color m_color = Color::Transparent;
|
||||
};
|
||||
|
||||
enum class Display : uint8_t {
|
||||
Inline,
|
||||
None
|
||||
};
|
||||
|
||||
enum class Visibility : uint8_t {
|
||||
Visible,
|
||||
Hidden,
|
||||
Collapse
|
||||
};
|
||||
|
||||
enum class Overflow : uint8_t {
|
||||
Visible,
|
||||
Hidden
|
||||
};
|
||||
|
||||
enum class PointerEvents : uint8_t {
|
||||
None,
|
||||
Auto,
|
||||
Stroke,
|
||||
Fill,
|
||||
Painted,
|
||||
Visible,
|
||||
VisibleStroke,
|
||||
VisibleFill,
|
||||
VisiblePainted,
|
||||
BoundingBox,
|
||||
All
|
||||
};
|
||||
|
||||
enum class FontStyle : uint8_t {
|
||||
Normal,
|
||||
Italic
|
||||
};
|
||||
|
||||
enum class FontWeight : uint8_t {
|
||||
Normal,
|
||||
Bold
|
||||
};
|
||||
|
||||
enum class AlignmentBaseline : uint8_t {
|
||||
Auto,
|
||||
Baseline,
|
||||
BeforeEdge,
|
||||
TextBeforeEdge,
|
||||
Middle,
|
||||
Central,
|
||||
AfterEdge,
|
||||
TextAfterEdge,
|
||||
Ideographic,
|
||||
Alphabetic,
|
||||
Hanging,
|
||||
Mathematical
|
||||
};
|
||||
|
||||
enum class DominantBaseline : uint8_t {
|
||||
Auto,
|
||||
UseScript,
|
||||
NoChange,
|
||||
ResetSize,
|
||||
Ideographic,
|
||||
Alphabetic,
|
||||
Hanging,
|
||||
Mathematical,
|
||||
Central,
|
||||
Middle,
|
||||
TextAfterEdge,
|
||||
TextBeforeEdge
|
||||
};
|
||||
|
||||
enum class TextAnchor : uint8_t {
|
||||
Start,
|
||||
Middle,
|
||||
End
|
||||
};
|
||||
|
||||
enum class WhiteSpace : uint8_t {
|
||||
Default,
|
||||
Preserve
|
||||
};
|
||||
|
||||
enum class WritingMode : uint8_t {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
enum class TextOrientation : uint8_t {
|
||||
Mixed,
|
||||
Upright
|
||||
};
|
||||
|
||||
enum class Direction : uint8_t {
|
||||
Ltr,
|
||||
Rtl
|
||||
};
|
||||
|
||||
enum class MaskType : uint8_t {
|
||||
Luminance,
|
||||
Alpha
|
||||
};
|
||||
|
||||
enum class Units : uint8_t {
|
||||
UserSpaceOnUse,
|
||||
ObjectBoundingBox
|
||||
};
|
||||
|
||||
enum class MarkerUnits : uint8_t {
|
||||
StrokeWidth,
|
||||
UserSpaceOnUse
|
||||
};
|
||||
|
||||
enum class LengthAdjust : uint8_t {
|
||||
Spacing,
|
||||
SpacingAndGlyphs
|
||||
};
|
||||
|
||||
template<typename Enum>
|
||||
using SVGEnumerationEntry = std::pair<Enum, std::string_view>;
|
||||
|
||||
template<typename Enum>
|
||||
class SVGEnumeration final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGEnumeration(PropertyID id, Enum value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
Enum value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
template<unsigned int N>
|
||||
bool parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N]);
|
||||
Enum m_value;
|
||||
};
|
||||
|
||||
class SVGAngle final : public SVGProperty {
|
||||
public:
|
||||
enum class OrientType {
|
||||
Auto,
|
||||
AutoStartReverse,
|
||||
Angle
|
||||
};
|
||||
|
||||
explicit SVGAngle(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
OrientType orientType() const { return m_orientType; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
float m_value = 0;
|
||||
OrientType m_orientType = OrientType::Angle;
|
||||
};
|
||||
|
||||
enum class LengthUnits : uint8_t {
|
||||
None,
|
||||
Percent,
|
||||
Px,
|
||||
Em,
|
||||
Ex
|
||||
};
|
||||
|
||||
enum class LengthDirection : uint8_t {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Diagonal
|
||||
};
|
||||
|
||||
enum class LengthNegativeMode : uint8_t {
|
||||
Allow,
|
||||
Forbid
|
||||
};
|
||||
|
||||
class Length {
|
||||
public:
|
||||
Length() = default;
|
||||
Length(float value, LengthUnits units)
|
||||
: m_value(value), m_units(units)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
LengthUnits units() const { return m_units; }
|
||||
|
||||
bool parse(std::string_view input, LengthNegativeMode mode);
|
||||
|
||||
private:
|
||||
float m_value = 0.f;
|
||||
LengthUnits m_units = LengthUnits::None;
|
||||
};
|
||||
|
||||
class SVGLength final : public SVGProperty {
|
||||
public:
|
||||
SVGLength(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode, float value = 0, LengthUnits units = LengthUnits::None)
|
||||
: SVGProperty(id)
|
||||
, m_direction(direction)
|
||||
, m_negativeMode(negativeMode)
|
||||
, m_value(value, units)
|
||||
{}
|
||||
|
||||
bool isPercent() const { return m_value.units() == LengthUnits::Percent; }
|
||||
|
||||
LengthDirection direction() const { return m_direction; }
|
||||
LengthNegativeMode negativeMode() const { return m_negativeMode; }
|
||||
const Length& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
const LengthDirection m_direction;
|
||||
const LengthNegativeMode m_negativeMode;
|
||||
Length m_value;
|
||||
};
|
||||
|
||||
class LengthContext {
|
||||
public:
|
||||
LengthContext(const SVGElement* element, Units units = Units::UserSpaceOnUse)
|
||||
: m_element(element), m_units(units)
|
||||
{}
|
||||
|
||||
float valueForLength(const Length& length, LengthDirection direction) const;
|
||||
float valueForLength(const SVGLength& length) const { return valueForLength(length.value(), length.direction()); }
|
||||
|
||||
private:
|
||||
float viewportDimension(LengthDirection direction) const;
|
||||
const SVGElement* m_element;
|
||||
const Units m_units;
|
||||
};
|
||||
|
||||
using LengthList = std::vector<Length>;
|
||||
|
||||
class SVGLengthList final : public SVGProperty {
|
||||
public:
|
||||
SVGLengthList(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode)
|
||||
: SVGProperty(id)
|
||||
, m_direction(direction)
|
||||
, m_negativeMode(negativeMode)
|
||||
{}
|
||||
|
||||
LengthDirection direction() const { return m_direction; }
|
||||
LengthNegativeMode negativeMode() const { return m_negativeMode; }
|
||||
const LengthList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
const LengthDirection m_direction;
|
||||
const LengthNegativeMode m_negativeMode;
|
||||
LengthList m_values;
|
||||
};
|
||||
|
||||
class BaselineShift {
|
||||
public:
|
||||
enum class Type {
|
||||
Baseline,
|
||||
Sub,
|
||||
Super,
|
||||
Length
|
||||
};
|
||||
|
||||
BaselineShift() = default;
|
||||
BaselineShift(Type type) : m_type(type) {}
|
||||
BaselineShift(const Length& length) : m_type(Type::Length), m_length(length) {}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
const Length& length() const { return m_length; }
|
||||
|
||||
private:
|
||||
Type m_type{Type::Baseline};
|
||||
Length m_length;
|
||||
};
|
||||
|
||||
class SVGNumber : public SVGProperty {
|
||||
public:
|
||||
SVGNumber(PropertyID id, float value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
bool parse(std::string_view input) override;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
};
|
||||
|
||||
class SVGNumberPercentage final : public SVGProperty {
|
||||
public:
|
||||
SVGNumberPercentage(PropertyID id, float value)
|
||||
: SVGProperty(id)
|
||||
, m_value(value)
|
||||
{}
|
||||
|
||||
float value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
};
|
||||
|
||||
using NumberList = std::vector<float>;
|
||||
|
||||
class SVGNumberList final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGNumberList(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const NumberList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
NumberList m_values;
|
||||
};
|
||||
|
||||
class SVGPath final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPath(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Path& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Path m_value;
|
||||
};
|
||||
|
||||
class SVGPoint final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPoint(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Point& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Point m_value;
|
||||
};
|
||||
|
||||
using PointList = std::vector<Point>;
|
||||
|
||||
class SVGPointList final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGPointList(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const PointList& values() const { return m_values; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
PointList m_values;
|
||||
};
|
||||
|
||||
class SVGRect final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGRect(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
, m_value(Rect::Invalid)
|
||||
{}
|
||||
|
||||
const Rect& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Rect m_value;
|
||||
};
|
||||
|
||||
class SVGTransform final : public SVGProperty {
|
||||
public:
|
||||
explicit SVGTransform(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
const Transform& value() const { return m_value; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
private:
|
||||
Transform m_value;
|
||||
};
|
||||
|
||||
class SVGPreserveAspectRatio final : public SVGProperty {
|
||||
public:
|
||||
enum class AlignType {
|
||||
None,
|
||||
xMinYMin,
|
||||
xMidYMin,
|
||||
xMaxYMin,
|
||||
xMinYMid,
|
||||
xMidYMid,
|
||||
xMaxYMid,
|
||||
xMinYMax,
|
||||
xMidYMax,
|
||||
xMaxYMax
|
||||
};
|
||||
|
||||
enum class MeetOrSlice {
|
||||
Meet,
|
||||
Slice
|
||||
};
|
||||
|
||||
explicit SVGPreserveAspectRatio(PropertyID id)
|
||||
: SVGProperty(id)
|
||||
{}
|
||||
|
||||
AlignType alignType() const { return m_alignType; }
|
||||
MeetOrSlice meetOrSlice() const { return m_meetOrSlice; }
|
||||
bool parse(std::string_view input) final;
|
||||
|
||||
Rect getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const;
|
||||
Transform getTransform(const Rect& viewBoxRect, const Size& viewportSize) const;
|
||||
void transformRect(Rect& dstRect, Rect& srcRect) const;
|
||||
|
||||
private:
|
||||
AlignType m_alignType = AlignType::xMidYMid;
|
||||
MeetOrSlice m_meetOrSlice = MeetOrSlice::Meet;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGPROPERTY_H
|
||||
61
vendor/lunasvg/source/svgrenderstate.cpp
vendored
Normal file
61
vendor/lunasvg/source/svgrenderstate.cpp
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
SVGBlendInfo::SVGBlendInfo(const SVGElement* element)
|
||||
: m_clipper(element->clipper())
|
||||
, m_masker(element->masker())
|
||||
, m_opacity(element->opacity())
|
||||
{
|
||||
}
|
||||
|
||||
bool SVGBlendInfo::requiresCompositing(SVGRenderMode mode) const
|
||||
{
|
||||
return (m_clipper && m_clipper->requiresMasking()) || (mode == SVGRenderMode::Painting && (m_masker || m_opacity < 1.f));
|
||||
}
|
||||
|
||||
bool SVGRenderState::hasCycleReference(const SVGElement* element) const
|
||||
{
|
||||
auto current = this;
|
||||
do {
|
||||
if(element == current->element())
|
||||
return true;
|
||||
current = current->parent();
|
||||
} while(current);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SVGRenderState::beginGroup(const SVGBlendInfo& blendInfo)
|
||||
{
|
||||
auto requiresCompositing = blendInfo.requiresCompositing(m_mode);
|
||||
if(requiresCompositing) {
|
||||
auto boundingBox = m_currentTransform.mapRect(m_element->paintBoundingBox());
|
||||
boundingBox.intersect(m_canvas->extents());
|
||||
m_canvas = Canvas::create(boundingBox);
|
||||
} else {
|
||||
m_canvas->save();
|
||||
}
|
||||
|
||||
if(!requiresCompositing && blendInfo.clipper()) {
|
||||
blendInfo.clipper()->applyClipPath(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void SVGRenderState::endGroup(const SVGBlendInfo& blendInfo)
|
||||
{
|
||||
if(m_canvas == m_parent->canvas()) {
|
||||
m_canvas->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
auto opacity = m_mode == SVGRenderMode::Clipping ? 1.f : blendInfo.opacity();
|
||||
if(blendInfo.clipper())
|
||||
blendInfo.clipper()->applyClipMask(*this);
|
||||
if(m_mode == SVGRenderMode::Painting && blendInfo.masker()) {
|
||||
blendInfo.masker()->applyMask(*this);
|
||||
}
|
||||
|
||||
m_parent->m_canvas->blendCanvas(*m_canvas, BlendMode::Src_Over, opacity);
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
69
vendor/lunasvg/source/svgrenderstate.h
vendored
Normal file
69
vendor/lunasvg/source/svgrenderstate.h
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef LUNASVG_SVGRENDERSTATE_H
|
||||
#define LUNASVG_SVGRENDERSTATE_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
enum class SVGRenderMode {
|
||||
Painting,
|
||||
Clipping
|
||||
};
|
||||
|
||||
class SVGBlendInfo {
|
||||
public:
|
||||
explicit SVGBlendInfo(const SVGElement* element);
|
||||
SVGBlendInfo(const SVGClipPathElement* clipper, const SVGMaskElement* masker, float opacity)
|
||||
: m_clipper(clipper), m_masker(masker), m_opacity(opacity)
|
||||
{}
|
||||
|
||||
bool requiresCompositing(SVGRenderMode mode) const;
|
||||
const SVGClipPathElement* clipper() const { return m_clipper; }
|
||||
const SVGMaskElement* masker() const { return m_masker; }
|
||||
float opacity() const { return m_opacity; }
|
||||
|
||||
private:
|
||||
const SVGClipPathElement* m_clipper;
|
||||
const SVGMaskElement* m_masker;
|
||||
const float m_opacity;
|
||||
};
|
||||
|
||||
class SVGRenderState {
|
||||
public:
|
||||
SVGRenderState(const SVGElement* element, const SVGRenderState& parent, const Transform& localTransform)
|
||||
: m_element(element), m_parent(&parent), m_currentTransform(parent.currentTransform() * localTransform)
|
||||
, m_mode(parent.mode()), m_canvas(parent.canvas())
|
||||
{}
|
||||
|
||||
SVGRenderState(const SVGElement* element, const SVGRenderState* parent, const Transform& currentTransform, SVGRenderMode mode, std::shared_ptr<Canvas> canvas)
|
||||
: m_element(element), m_parent(parent), m_currentTransform(currentTransform), m_mode(mode), m_canvas(std::move(canvas))
|
||||
{}
|
||||
|
||||
Canvas& operator*() const { return *m_canvas; }
|
||||
Canvas* operator->() const { return &*m_canvas; }
|
||||
|
||||
const SVGElement* element() const { return m_element; }
|
||||
const SVGRenderState* parent() const { return m_parent; }
|
||||
const Transform& currentTransform() const { return m_currentTransform; }
|
||||
const SVGRenderMode mode() const { return m_mode; }
|
||||
const std::shared_ptr<Canvas>& canvas() const { return m_canvas; }
|
||||
|
||||
Rect fillBoundingBox() const { return m_element->fillBoundingBox(); }
|
||||
Rect paintBoundingBox() const { return m_element->paintBoundingBox(); }
|
||||
|
||||
bool hasCycleReference(const SVGElement* element) const;
|
||||
|
||||
void beginGroup(const SVGBlendInfo& blendInfo);
|
||||
void endGroup(const SVGBlendInfo& blendInfo);
|
||||
|
||||
private:
|
||||
const SVGElement* m_element;
|
||||
const SVGRenderState* m_parent;
|
||||
const Transform m_currentTransform;
|
||||
const SVGRenderMode m_mode;
|
||||
std::shared_ptr<Canvas> m_canvas;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGRENDERSTATE_H
|
||||
579
vendor/lunasvg/source/svgtextelement.cpp
vendored
Normal file
579
vendor/lunasvg/source/svgtextelement.cpp
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
#include "svgtextelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
inline const SVGTextNode* toSVGTextNode(const SVGNode* node)
|
||||
{
|
||||
assert(node && node->isTextNode());
|
||||
return static_cast<const SVGTextNode*>(node);
|
||||
}
|
||||
|
||||
inline const SVGTextPositioningElement* toSVGTextPositioningElement(const SVGNode* node)
|
||||
{
|
||||
assert(node && node->isTextPositioningElement());
|
||||
return static_cast<const SVGTextPositioningElement*>(node);
|
||||
}
|
||||
|
||||
static AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement* element)
|
||||
{
|
||||
switch(element->dominant_baseline()) {
|
||||
case DominantBaseline::Auto:
|
||||
if(element->isVerticalWritingMode())
|
||||
return AlignmentBaseline::Central;
|
||||
return AlignmentBaseline::Alphabetic;
|
||||
case DominantBaseline::UseScript:
|
||||
case DominantBaseline::NoChange:
|
||||
case DominantBaseline::ResetSize:
|
||||
return AlignmentBaseline::Auto;
|
||||
case DominantBaseline::Ideographic:
|
||||
return AlignmentBaseline::Ideographic;
|
||||
case DominantBaseline::Alphabetic:
|
||||
return AlignmentBaseline::Alphabetic;
|
||||
case DominantBaseline::Hanging:
|
||||
return AlignmentBaseline::Hanging;
|
||||
case DominantBaseline::Mathematical:
|
||||
return AlignmentBaseline::Mathematical;
|
||||
case DominantBaseline::Central:
|
||||
return AlignmentBaseline::Central;
|
||||
case DominantBaseline::Middle:
|
||||
return AlignmentBaseline::Middle;
|
||||
case DominantBaseline::TextAfterEdge:
|
||||
return AlignmentBaseline::TextAfterEdge;
|
||||
case DominantBaseline::TextBeforeEdge:
|
||||
return AlignmentBaseline::TextBeforeEdge;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return AlignmentBaseline::Auto;
|
||||
}
|
||||
|
||||
static float calculateBaselineOffset(const SVGTextPositioningElement* element)
|
||||
{
|
||||
auto offset = element->baseline_offset();
|
||||
auto parent = element->parentElement();
|
||||
while(parent->isTextPositioningElement()) {
|
||||
offset += toSVGTextPositioningElement(parent)->baseline_offset();
|
||||
parent = parent->parentElement();
|
||||
}
|
||||
|
||||
auto baseline = element->alignment_baseline();
|
||||
if(baseline == AlignmentBaseline::Auto || baseline == AlignmentBaseline::Baseline) {
|
||||
baseline = resolveDominantBaseline(element);
|
||||
}
|
||||
|
||||
const auto& font = element->font();
|
||||
switch(baseline) {
|
||||
case AlignmentBaseline::BeforeEdge:
|
||||
case AlignmentBaseline::TextBeforeEdge:
|
||||
offset -= font.ascent();
|
||||
break;
|
||||
case AlignmentBaseline::Middle:
|
||||
offset -= font.xHeight() / 2.f;
|
||||
break;
|
||||
case AlignmentBaseline::Central:
|
||||
offset -= (font.ascent() + font.descent()) / 2.f;
|
||||
break;
|
||||
case AlignmentBaseline::AfterEdge:
|
||||
case AlignmentBaseline::TextAfterEdge:
|
||||
case AlignmentBaseline::Ideographic:
|
||||
offset -= font.descent();
|
||||
break;
|
||||
case AlignmentBaseline::Hanging:
|
||||
offset -= font.ascent() * 8.f / 10.f;
|
||||
break;
|
||||
case AlignmentBaseline::Mathematical:
|
||||
offset -= font.ascent() / 2.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool needsTextAnchorAdjustment(const SVGTextPositioningElement* element)
|
||||
{
|
||||
auto direction = element->direction();
|
||||
switch(element->text_anchor()) {
|
||||
case TextAnchor::Start:
|
||||
return direction == Direction::Rtl;
|
||||
case TextAnchor::Middle:
|
||||
return true;
|
||||
case TextAnchor::End:
|
||||
return direction == Direction::Ltr;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static float calculateTextAnchorOffset(const SVGTextPositioningElement* element, float width)
|
||||
{
|
||||
auto direction = element->direction();
|
||||
switch(element->text_anchor()) {
|
||||
case TextAnchor::Start:
|
||||
if(direction == Direction::Ltr)
|
||||
return 0.f;
|
||||
return -width;
|
||||
case TextAnchor::Middle:
|
||||
return -width / 2.f;
|
||||
case TextAnchor::End:
|
||||
if(direction == Direction::Ltr)
|
||||
return -width;
|
||||
return 0.f;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
using SVGTextFragmentIterator = SVGTextFragmentList::iterator;
|
||||
|
||||
static float calculateTextChunkLength(SVGTextFragmentIterator begin, SVGTextFragmentIterator end, bool isVerticalText)
|
||||
{
|
||||
float chunkLength = 0;
|
||||
const SVGTextFragment* lastFragment = nullptr;
|
||||
for(auto it = begin; it != end; ++it) {
|
||||
const SVGTextFragment& fragment = *it;
|
||||
chunkLength += isVerticalText ? fragment.height : fragment.width;
|
||||
if(!lastFragment) {
|
||||
lastFragment = &fragment;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isVerticalText) {
|
||||
chunkLength += fragment.y - (lastFragment->y + lastFragment->height);
|
||||
} else {
|
||||
chunkLength += fragment.x - (lastFragment->x + lastFragment->width);
|
||||
}
|
||||
|
||||
lastFragment = &fragment;
|
||||
}
|
||||
|
||||
return chunkLength;
|
||||
}
|
||||
|
||||
static void handleTextChunk(SVGTextFragmentIterator begin, SVGTextFragmentIterator end)
|
||||
{
|
||||
const SVGTextFragment& firstFragment = *begin;
|
||||
const auto isVerticalText = firstFragment.element->isVerticalWritingMode();
|
||||
if(firstFragment.element->hasAttribute(PropertyID::TextLength)) {
|
||||
LengthContext lengthContext(firstFragment.element);
|
||||
auto textLength = lengthContext.valueForLength(firstFragment.element->textLength());
|
||||
auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText);
|
||||
if(textLength > 0.f && chunkLength > 0.f) {
|
||||
size_t numCharacters = 0;
|
||||
for(auto it = begin; it != end; ++it) {
|
||||
const SVGTextFragment& fragment = *it;
|
||||
numCharacters += fragment.length;
|
||||
}
|
||||
|
||||
if(firstFragment.element->lengthAdjust() == LengthAdjust::SpacingAndGlyphs) {
|
||||
auto textLengthScale = textLength / chunkLength;
|
||||
auto lengthAdjustTransform = Transform::translated(firstFragment.x, firstFragment.y);
|
||||
if(isVerticalText) {
|
||||
lengthAdjustTransform.scale(1.f, textLengthScale);
|
||||
} else {
|
||||
lengthAdjustTransform.scale(textLengthScale, 1.f);
|
||||
}
|
||||
|
||||
lengthAdjustTransform.translate(-firstFragment.x, -firstFragment.y);
|
||||
for(auto it = begin; it != end; ++it) {
|
||||
SVGTextFragment& fragment = *it;
|
||||
fragment.lengthAdjustTransform = lengthAdjustTransform;
|
||||
}
|
||||
} else if(numCharacters > 1) {
|
||||
assert(firstFragment.element->lengthAdjust() == LengthAdjust::Spacing);
|
||||
size_t characterOffset = 0;
|
||||
auto textLengthShift = (textLength - chunkLength) / (numCharacters - 1);
|
||||
for(auto it = begin; it != end; ++it) {
|
||||
SVGTextFragment& fragment = *it;
|
||||
if(isVerticalText) {
|
||||
fragment.y += textLengthShift * characterOffset;
|
||||
} else {
|
||||
fragment.x += textLengthShift * characterOffset;
|
||||
}
|
||||
|
||||
characterOffset += fragment.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(needsTextAnchorAdjustment(firstFragment.element)) {
|
||||
auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText);
|
||||
auto chunkOffset = calculateTextAnchorOffset(firstFragment.element, chunkLength);
|
||||
for(auto it = begin; it != end; ++it) {
|
||||
SVGTextFragment& fragment = *it;
|
||||
if(isVerticalText) {
|
||||
fragment.y += chunkOffset;
|
||||
} else {
|
||||
fragment.x += chunkOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVGTextFragmentsBuilder::SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments)
|
||||
: m_text(text), m_fragments(fragments)
|
||||
{
|
||||
m_text.clear();
|
||||
m_fragments.clear();
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::build(const SVGTextElement* textElement)
|
||||
{
|
||||
handleElement(textElement);
|
||||
for(const auto& position : m_textPositions) {
|
||||
fillCharacterPositions(position);
|
||||
}
|
||||
|
||||
std::u32string_view wholeText(m_text);
|
||||
for(const auto& textPosition : m_textPositions) {
|
||||
if(!textPosition.node->isTextNode())
|
||||
continue;
|
||||
auto element = toSVGTextPositioningElement(textPosition.node->parentElement());
|
||||
const auto isVerticalText = element->isVerticalWritingMode();
|
||||
const auto isUprightText = element->isUprightTextOrientation();
|
||||
const auto& font = element->font();
|
||||
|
||||
SVGTextFragment fragment(element);
|
||||
auto recordTextFragment = [&](auto startOffset, auto endOffset) {
|
||||
auto text = wholeText.substr(startOffset, endOffset - startOffset);
|
||||
fragment.offset = startOffset;
|
||||
fragment.length = text.length();
|
||||
fragment.width = font.measureText(text);
|
||||
fragment.height = font.height() + font.lineGap();
|
||||
if(isVerticalText) {
|
||||
m_y += isUprightText ? fragment.height : fragment.width;
|
||||
} else {
|
||||
m_x += fragment.width;
|
||||
}
|
||||
|
||||
m_fragments.push_back(fragment);
|
||||
};
|
||||
|
||||
auto needsTextLengthSpacing = element->lengthAdjust() == LengthAdjust::Spacing && element->hasAttribute(PropertyID::TextLength);
|
||||
auto baselineOffset = calculateBaselineOffset(element);
|
||||
auto startOffset = textPosition.startOffset;
|
||||
auto textOffset = textPosition.startOffset;
|
||||
auto didStartTextFragment = false;
|
||||
auto applySpacingToNextCharacter = false;
|
||||
auto lastCharacter = 0u;
|
||||
auto lastAngle = 0.f;
|
||||
while(textOffset < textPosition.endOffset) {
|
||||
SVGCharacterPosition characterPosition;
|
||||
if(auto it = m_characterPositions.find(m_characterOffset); it != m_characterPositions.end()) {
|
||||
characterPosition = it->second;
|
||||
}
|
||||
|
||||
auto currentCharacter = wholeText.at(textOffset);
|
||||
auto angle = characterPosition.rotate.value_or(0);
|
||||
auto dx = characterPosition.dx.value_or(0);
|
||||
auto dy = characterPosition.dy.value_or(0);
|
||||
|
||||
auto shouldStartNewFragment = needsTextLengthSpacing || isVerticalText || applySpacingToNextCharacter
|
||||
|| characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle;
|
||||
if(shouldStartNewFragment && didStartTextFragment) {
|
||||
recordTextFragment(startOffset, textOffset);
|
||||
applySpacingToNextCharacter = false;
|
||||
startOffset = textOffset;
|
||||
}
|
||||
|
||||
auto startsNewTextChunk = (characterPosition.x || characterPosition.y) && textOffset == textPosition.startOffset;
|
||||
if(startsNewTextChunk || shouldStartNewFragment || !didStartTextFragment) {
|
||||
m_x = dx + characterPosition.x.value_or(m_x);
|
||||
m_y = dy + characterPosition.y.value_or(m_y);
|
||||
fragment.x = isVerticalText ? m_x + baselineOffset : m_x;
|
||||
fragment.y = isVerticalText ? m_y : m_y - baselineOffset;
|
||||
fragment.angle = angle;
|
||||
if(isVerticalText) {
|
||||
if(isUprightText) {
|
||||
fragment.y += font.height();
|
||||
} else {
|
||||
fragment.angle += 90.f;
|
||||
}
|
||||
}
|
||||
|
||||
fragment.startsNewTextChunk = startsNewTextChunk;
|
||||
didStartTextFragment = true;
|
||||
}
|
||||
|
||||
auto spacing = element->letter_spacing();
|
||||
if(currentCharacter && lastCharacter && element->word_spacing()) {
|
||||
if(currentCharacter == ' ' && lastCharacter != ' ') {
|
||||
spacing += element->word_spacing();
|
||||
}
|
||||
}
|
||||
|
||||
if(spacing) {
|
||||
applySpacingToNextCharacter = true;
|
||||
if(isVerticalText) {
|
||||
m_y += spacing;
|
||||
} else {
|
||||
m_x += spacing;
|
||||
}
|
||||
}
|
||||
|
||||
lastAngle = angle;
|
||||
lastCharacter = currentCharacter;
|
||||
++textOffset;
|
||||
++m_characterOffset;
|
||||
}
|
||||
|
||||
recordTextFragment(startOffset, textOffset);
|
||||
}
|
||||
|
||||
if(m_fragments.empty())
|
||||
return;
|
||||
auto it = m_fragments.begin();
|
||||
auto begin = m_fragments.begin();
|
||||
auto end = m_fragments.end();
|
||||
for(++it; it != end; ++it) {
|
||||
const SVGTextFragment& fragment = *it;
|
||||
if(!fragment.startsNewTextChunk)
|
||||
continue;
|
||||
handleTextChunk(begin, it);
|
||||
begin = it;
|
||||
}
|
||||
|
||||
handleTextChunk(begin, it);
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::handleText(const SVGTextNode* node)
|
||||
{
|
||||
const auto& text = node->data();
|
||||
if(text.empty())
|
||||
return;
|
||||
auto element = toSVGTextPositioningElement(node->parentElement());
|
||||
const auto startOffset = m_text.length();
|
||||
uint32_t lastCharacter = ' ';
|
||||
if(!m_text.empty()) {
|
||||
lastCharacter = m_text.back();
|
||||
}
|
||||
|
||||
plutovg_text_iterator_t it;
|
||||
plutovg_text_iterator_init(&it, text.data(), text.length(), PLUTOVG_TEXT_ENCODING_UTF8);
|
||||
while(plutovg_text_iterator_has_next(&it)) {
|
||||
auto currentCharacter = plutovg_text_iterator_next(&it);
|
||||
if(currentCharacter == '\t' || currentCharacter == '\n' || currentCharacter == '\r')
|
||||
currentCharacter = ' ';
|
||||
if(currentCharacter == ' ' && lastCharacter == ' ' && element->white_space() == WhiteSpace::Default)
|
||||
continue;
|
||||
m_text.push_back(currentCharacter);
|
||||
lastCharacter = currentCharacter;
|
||||
}
|
||||
|
||||
if(startOffset < m_text.length()) {
|
||||
m_textPositions.emplace_back(node, startOffset, m_text.length());
|
||||
}
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::handleElement(const SVGTextPositioningElement* element)
|
||||
{
|
||||
if(element->isDisplayNone())
|
||||
return;
|
||||
const auto itemIndex = m_textPositions.size();
|
||||
m_textPositions.emplace_back(element, m_text.length(), m_text.length());
|
||||
for(const auto& child : element->children()) {
|
||||
if(child->isTextNode()) {
|
||||
handleText(toSVGTextNode(child.get()));
|
||||
} else if(child->isTextPositioningElement()) {
|
||||
handleElement(toSVGTextPositioningElement(child.get()));
|
||||
}
|
||||
}
|
||||
|
||||
auto& position = m_textPositions[itemIndex];
|
||||
assert(position.node == element);
|
||||
position.endOffset = m_text.length();
|
||||
}
|
||||
|
||||
void SVGTextFragmentsBuilder::fillCharacterPositions(const SVGTextPosition& position)
|
||||
{
|
||||
if(!position.node->isTextPositioningElement())
|
||||
return;
|
||||
auto element = toSVGTextPositioningElement(position.node);
|
||||
const auto& xList = element->x();
|
||||
const auto& yList = element->y();
|
||||
const auto& dxList = element->dx();
|
||||
const auto& dyList = element->dy();
|
||||
const auto& rotateList = element->rotate();
|
||||
|
||||
auto xListSize = xList.size();
|
||||
auto yListSize = yList.size();
|
||||
auto dxListSize = dxList.size();
|
||||
auto dyListSize = dyList.size();
|
||||
auto rotateListSize = rotateList.size();
|
||||
if(!xListSize && !yListSize && !dxListSize && !dyListSize && !rotateListSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
LengthContext lengthContext(element);
|
||||
std::optional<float> lastRotation;
|
||||
for(auto offset = position.startOffset; offset < position.endOffset; ++offset) {
|
||||
auto index = offset - position.startOffset;
|
||||
if(index >= xListSize && index >= yListSize && index >= dxListSize && index >= dyListSize && index >= rotateListSize)
|
||||
break;
|
||||
auto& characterPosition = m_characterPositions[offset];
|
||||
if(index < xListSize)
|
||||
characterPosition.x = lengthContext.valueForLength(xList[index], LengthDirection::Horizontal);
|
||||
if(index < yListSize)
|
||||
characterPosition.y = lengthContext.valueForLength(yList[index], LengthDirection::Vertical);
|
||||
if(index < dxListSize)
|
||||
characterPosition.dx = lengthContext.valueForLength(dxList[index], LengthDirection::Horizontal);
|
||||
if(index < dyListSize)
|
||||
characterPosition.dy = lengthContext.valueForLength(dyList[index], LengthDirection::Vertical);
|
||||
if(index < rotateListSize) {
|
||||
characterPosition.rotate = rotateList[index];
|
||||
lastRotation = characterPosition.rotate;
|
||||
}
|
||||
}
|
||||
|
||||
if(lastRotation == std::nullopt)
|
||||
return;
|
||||
auto offset = position.startOffset + rotateList.size();
|
||||
while(offset < position.endOffset) {
|
||||
m_characterPositions[offset++].rotate = lastRotation;
|
||||
}
|
||||
}
|
||||
|
||||
SVGTextPositioningElement::SVGTextPositioningElement(Document* document, ElementID id)
|
||||
: SVGGraphicsElement(document, id)
|
||||
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_dx(PropertyID::Dx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
|
||||
, m_dy(PropertyID::Dy, LengthDirection::Vertical, LengthNegativeMode::Allow)
|
||||
, m_rotate(PropertyID::Rotate)
|
||||
, m_textLength(PropertyID::TextLength, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
|
||||
, m_lengthAdjust(PropertyID::LengthAdjust, LengthAdjust::Spacing)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_dx);
|
||||
addProperty(m_dy);
|
||||
addProperty(m_rotate);
|
||||
addProperty(m_textLength);
|
||||
addProperty(m_lengthAdjust);
|
||||
}
|
||||
|
||||
void SVGTextPositioningElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_font = state.font();
|
||||
m_fill = getPaintServer(state.fill(), state.fill_opacity());
|
||||
m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
|
||||
SVGGraphicsElement::layoutElement(state);
|
||||
|
||||
LengthContext lengthContext(this);
|
||||
m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal);
|
||||
m_letter_spacing = lengthContext.valueForLength(state.letter_spacing(), LengthDirection::Diagonal);
|
||||
m_word_spacing = lengthContext.valueForLength(state.word_spacing(), LengthDirection::Diagonal);
|
||||
|
||||
m_baseline_offset = convertBaselineOffset(state.baseline_shift());
|
||||
m_alignment_baseline = state.alignment_baseline();
|
||||
m_dominant_baseline = state.dominant_baseline();
|
||||
m_text_anchor = state.text_anchor();
|
||||
m_white_space = state.white_space();
|
||||
m_writing_mode = state.writing_mode();
|
||||
m_text_orientation = state.text_orientation();
|
||||
m_direction = state.direction();
|
||||
}
|
||||
|
||||
float SVGTextPositioningElement::convertBaselineOffset(const BaselineShift& baselineShift) const
|
||||
{
|
||||
if(baselineShift.type() == BaselineShift::Type::Baseline)
|
||||
return 0.f;
|
||||
if(baselineShift.type() == BaselineShift::Type::Sub)
|
||||
return -m_font.height() / 2.f;
|
||||
if(baselineShift.type() == BaselineShift::Type::Super) {
|
||||
return m_font.height() / 2.f;
|
||||
}
|
||||
|
||||
const auto& length = baselineShift.length();
|
||||
if(length.units() == LengthUnits::Percent)
|
||||
return length.value() * m_font.size() / 100.f;
|
||||
if(length.units() == LengthUnits::Ex)
|
||||
return length.value() * m_font.size() / 2.f;
|
||||
if(length.units() == LengthUnits::Em)
|
||||
return length.value() * m_font.size();
|
||||
return length.value();
|
||||
}
|
||||
|
||||
SVGTSpanElement::SVGTSpanElement(Document* document)
|
||||
: SVGTextPositioningElement(document, ElementID::Tspan)
|
||||
{
|
||||
}
|
||||
|
||||
SVGTextElement::SVGTextElement(Document* document)
|
||||
: SVGTextPositioningElement(document, ElementID::Text)
|
||||
{
|
||||
}
|
||||
|
||||
void SVGTextElement::layout(SVGLayoutState& state)
|
||||
{
|
||||
SVGTextPositioningElement::layout(state);
|
||||
SVGTextFragmentsBuilder(m_text, m_fragments).build(this);
|
||||
}
|
||||
|
||||
void SVGTextElement::render(SVGRenderState& state) const
|
||||
{
|
||||
if(m_fragments.empty() || isVisibilityHidden() || isDisplayNone())
|
||||
return;
|
||||
SVGBlendInfo blendInfo(this);
|
||||
SVGRenderState newState(this, state, localTransform());
|
||||
newState.beginGroup(blendInfo);
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->setColor(Color::White);
|
||||
}
|
||||
|
||||
std::u32string_view wholeText(m_text);
|
||||
for(const auto& fragment : m_fragments) {
|
||||
if(fragment.element->isVisibilityHidden())
|
||||
continue;
|
||||
auto transform = newState.currentTransform() * Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform;
|
||||
auto text = wholeText.substr(fragment.offset, fragment.length);
|
||||
auto origin = Point(fragment.x, fragment.y);
|
||||
|
||||
const auto& font = fragment.element->font();
|
||||
if(newState.mode() == SVGRenderMode::Clipping) {
|
||||
newState->fillText(text, font, origin, transform);
|
||||
} else {
|
||||
const auto& fill = fragment.element->fill();
|
||||
const auto& stroke = fragment.element->stroke();
|
||||
auto stroke_width = fragment.element->stroke_width();
|
||||
if(fill.applyPaint(newState))
|
||||
newState->fillText(text, font, origin, transform);
|
||||
if(stroke.applyPaint(newState)) {
|
||||
newState->strokeText(text, stroke_width, font, origin, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newState.endGroup(blendInfo);
|
||||
}
|
||||
|
||||
Rect SVGTextElement::boundingBox(bool includeStroke) const
|
||||
{
|
||||
auto boundingBox = Rect::Invalid;
|
||||
for(const auto& fragment : m_fragments) {
|
||||
const auto& font = fragment.element->font();
|
||||
const auto& stroke = fragment.element->stroke();
|
||||
auto fragmentTranform = Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform;
|
||||
auto fragmentRect = Rect(fragment.x, fragment.y - font.ascent(), fragment.width, fragment.height);
|
||||
if(includeStroke && stroke.isRenderable())
|
||||
fragmentRect.inflate(fragment.element->stroke_width() / 2.f);
|
||||
boundingBox.unite(fragmentTranform.mapRect(fragmentRect));
|
||||
}
|
||||
|
||||
if(!boundingBox.isValid())
|
||||
boundingBox = Rect::Empty;
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
155
vendor/lunasvg/source/svgtextelement.h
vendored
Normal file
155
vendor/lunasvg/source/svgtextelement.h
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
#ifndef LUNASVG_SVGTEXTELEMENT_H
|
||||
#define LUNASVG_SVGTEXTELEMENT_H
|
||||
|
||||
#include "svgelement.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
class SVGTextPositioningElement;
|
||||
class SVGTextElement;
|
||||
|
||||
struct SVGCharacterPosition {
|
||||
std::optional<float> x;
|
||||
std::optional<float> y;
|
||||
std::optional<float> dx;
|
||||
std::optional<float> dy;
|
||||
std::optional<float> rotate;
|
||||
};
|
||||
|
||||
using SVGCharacterPositions = std::map<size_t, SVGCharacterPosition>;
|
||||
|
||||
struct SVGTextPosition {
|
||||
SVGTextPosition(const SVGNode* node, size_t startOffset, size_t endOffset)
|
||||
: node(node), startOffset(startOffset), endOffset(endOffset)
|
||||
{}
|
||||
|
||||
const SVGNode* node;
|
||||
size_t startOffset;
|
||||
size_t endOffset;
|
||||
};
|
||||
|
||||
using SVGTextPositionList = std::vector<SVGTextPosition>;
|
||||
|
||||
struct SVGTextFragment {
|
||||
explicit SVGTextFragment(const SVGTextPositioningElement* element) : element(element) {}
|
||||
const SVGTextPositioningElement* element;
|
||||
Transform lengthAdjustTransform;
|
||||
size_t offset = 0;
|
||||
size_t length = 0;
|
||||
bool startsNewTextChunk = false;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float width = 0;
|
||||
float height = 0;
|
||||
float angle = 0;
|
||||
};
|
||||
|
||||
using SVGTextFragmentList = std::vector<SVGTextFragment>;
|
||||
|
||||
class SVGTextFragmentsBuilder {
|
||||
public:
|
||||
SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments);
|
||||
|
||||
void build(const SVGTextElement* textElement);
|
||||
|
||||
private:
|
||||
void handleText(const SVGTextNode* node);
|
||||
void handleElement(const SVGTextPositioningElement* element);
|
||||
void fillCharacterPositions(const SVGTextPosition& position);
|
||||
std::u32string& m_text;
|
||||
SVGTextFragmentList& m_fragments;
|
||||
SVGCharacterPositions m_characterPositions;
|
||||
SVGTextPositionList m_textPositions;
|
||||
size_t m_characterOffset = 0;
|
||||
float m_x = 0;
|
||||
float m_y = 0;
|
||||
};
|
||||
|
||||
class SVGTextPositioningElement : public SVGGraphicsElement {
|
||||
public:
|
||||
SVGTextPositioningElement(Document* document, ElementID id);
|
||||
|
||||
bool isTextPositioningElement() const final { return true; }
|
||||
|
||||
const LengthList& x() const { return m_x.values(); }
|
||||
const LengthList& y() const { return m_y.values(); }
|
||||
const LengthList& dx() const { return m_dx.values(); }
|
||||
const LengthList& dy() const { return m_dy.values(); }
|
||||
const NumberList& rotate() const { return m_rotate.values(); }
|
||||
|
||||
const SVGLength& textLength() const { return m_textLength; }
|
||||
LengthAdjust lengthAdjust() const { return m_lengthAdjust.value(); }
|
||||
|
||||
const Font& font() const { return m_font; }
|
||||
const SVGPaintServer& fill() const { return m_fill; }
|
||||
const SVGPaintServer& stroke() const { return m_stroke; }
|
||||
|
||||
bool isVerticalWritingMode() const { return m_writing_mode == WritingMode::Vertical; }
|
||||
bool isUprightTextOrientation() const { return m_text_orientation == TextOrientation::Upright; }
|
||||
|
||||
float stroke_width() const { return m_stroke_width; }
|
||||
float letter_spacing() const { return m_letter_spacing; }
|
||||
float word_spacing() const { return m_word_spacing; }
|
||||
float baseline_offset() const { return m_baseline_offset; }
|
||||
AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; }
|
||||
DominantBaseline dominant_baseline() const { return m_dominant_baseline; }
|
||||
TextAnchor text_anchor() const { return m_text_anchor; }
|
||||
WhiteSpace white_space() const { return m_white_space; }
|
||||
Direction direction() const { return m_direction; }
|
||||
|
||||
void layoutElement(const SVGLayoutState& state) override;
|
||||
|
||||
private:
|
||||
float convertBaselineOffset(const BaselineShift& baselineShift) const;
|
||||
SVGLengthList m_x;
|
||||
SVGLengthList m_y;
|
||||
SVGLengthList m_dx;
|
||||
SVGLengthList m_dy;
|
||||
SVGNumberList m_rotate;
|
||||
|
||||
SVGLength m_textLength;
|
||||
SVGEnumeration<LengthAdjust> m_lengthAdjust;
|
||||
|
||||
Font m_font;
|
||||
SVGPaintServer m_fill;
|
||||
SVGPaintServer m_stroke;
|
||||
|
||||
float m_stroke_width = 1.f;
|
||||
float m_letter_spacing = 0.f;
|
||||
float m_word_spacing = 0.f;
|
||||
float m_baseline_offset = 0.f;
|
||||
AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto;
|
||||
DominantBaseline m_dominant_baseline = DominantBaseline::Auto;
|
||||
TextAnchor m_text_anchor = TextAnchor::Start;
|
||||
WhiteSpace m_white_space = WhiteSpace::Default;
|
||||
WritingMode m_writing_mode = WritingMode::Horizontal;
|
||||
TextOrientation m_text_orientation = TextOrientation::Mixed;
|
||||
Direction m_direction = Direction::Ltr;
|
||||
};
|
||||
|
||||
class SVGTSpanElement final : public SVGTextPositioningElement {
|
||||
public:
|
||||
SVGTSpanElement(Document* document);
|
||||
};
|
||||
|
||||
class SVGTextElement final : public SVGTextPositioningElement {
|
||||
public:
|
||||
SVGTextElement(Document* document);
|
||||
|
||||
Rect fillBoundingBox() const final { return boundingBox(false); }
|
||||
Rect strokeBoundingBox() const final { return boundingBox(true); }
|
||||
|
||||
void layout(SVGLayoutState& state) final;
|
||||
void render(SVGRenderState& state) const final;
|
||||
|
||||
private:
|
||||
Rect boundingBox(bool includeStroke) const;
|
||||
SVGTextFragmentList m_fragments;
|
||||
std::u32string m_text;
|
||||
};
|
||||
|
||||
} // namespace lunasvg
|
||||
|
||||
#endif // LUNASVG_SVGTEXTELEMENT_H
|
||||
Reference in New Issue
Block a user