WIP: lunasvg implementation, things stopped working

This commit is contained in:
2026-03-03 14:01:22 -05:00
parent 19bf78d635
commit 2703bbd901
80 changed files with 38694 additions and 12 deletions

693
vendor/lunasvg/source/graphics.cpp vendored Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

498
vendor/lunasvg/source/svgelement.h vendored Normal file
View 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

View 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

View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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