blob: 87ebc45d9bee966aada9587d9af5fffbbc3e92f8 [file]
/*
* Copyright 2026 Rive
*/
#include "utils/svg_factory.hpp"
#include <cstring>
#include <iomanip>
namespace rive
{
// ---- SVGRenderPath ----
SVGRenderPath::SVGRenderPath(RawPath& rawPath, FillRule fillRule)
: m_rawPath(rawPath)
, m_fillRule(fillRule)
{}
void SVGRenderPath::rewind() { m_rawPath.rewind(); }
void SVGRenderPath::fillRule(FillRule value) { m_fillRule = value; }
void SVGRenderPath::addRenderPath(
const RenderPath* path,
const Mat2D& transform)
{
auto* svgPath = static_cast<const SVGRenderPath*>(path);
m_rawPath.addPath(svgPath->m_rawPath, &transform);
}
void SVGRenderPath::moveTo(float x, float y) { m_rawPath.moveTo(x, y); }
void SVGRenderPath::lineTo(float x, float y)
{
m_rawPath.injectImplicitMoveIfNeeded();
m_rawPath.line({x, y});
}
void SVGRenderPath::cubicTo(
float ox,
float oy,
float ix,
float iy,
float x,
float y)
{
m_rawPath.injectImplicitMoveIfNeeded();
m_rawPath.cubic({ox, oy}, {ix, iy}, {x, y});
}
void SVGRenderPath::close() { m_rawPath.close(); }
void SVGRenderPath::addRawPath(const RawPath& path)
{
m_rawPath.addPath(path, nullptr);
}
std::string SVGRenderPath::toSvgD(int floatPrecision) const
{
auto verbs = m_rawPath.verbs();
auto points = m_rawPath.points();
std::ostringstream out;
out << std::setprecision(floatPrecision);
size_t ptIdx = 0;
for (size_t i = 0; i < verbs.size(); ++i)
{
switch (verbs[i])
{
case PathVerb::move:
out << "M" << points[ptIdx].x << " " << points[ptIdx].y;
// SVG never strokes a subpath that is only a moveto, but it
// does stroke a closed zero-length one, which is how a round or
// square cap draws its dot.
if (i + 1 == verbs.size() || verbs[i + 1] == PathVerb::move)
{
out << "Z";
}
ptIdx += 1;
break;
case PathVerb::line:
out << "L" << points[ptIdx].x << " " << points[ptIdx].y;
ptIdx += 1;
break;
case PathVerb::quad:
out << "Q" << points[ptIdx].x << " " << points[ptIdx].y << " "
<< points[ptIdx + 1].x << " " << points[ptIdx + 1].y;
ptIdx += 2;
break;
case PathVerb::cubic:
out << "C" << points[ptIdx].x << " " << points[ptIdx].y << " "
<< points[ptIdx + 1].x << " " << points[ptIdx + 1].y << " "
<< points[ptIdx + 2].x << " " << points[ptIdx + 2].y;
ptIdx += 3;
break;
case PathVerb::close:
out << "Z";
break;
}
}
return out.str();
}
// ---- SVGLinearGradientShader ----
SVGLinearGradientShader::SVGLinearGradientShader(
float sx,
float sy,
float ex,
float ey,
const ColorInt colors[],
const float stops[],
size_t count)
: m_sx(sx)
, m_sy(sy)
, m_ex(ex)
, m_ey(ey)
, m_colors(colors, colors + count)
, m_stops(stops, stops + count)
{}
static void emitColorStop(std::ostream& out, ColorInt color, float offset)
{
unsigned r = colorRed(color);
unsigned g = colorGreen(color);
unsigned b = colorBlue(color);
float a = colorAlpha(color) / 255.0f;
out << "<stop offset=\"" << offset << "\" stop-color=\"rgb(" << r << "," << g
<< "," << b << ")\" stop-opacity=\"" << a << "\"/>\n";
}
static void emitGradientTransform(
std::ostream& out,
const rive::Mat2D& transform)
{
if (transform != rive::Mat2D())
{
out << " gradientTransform=\"matrix(" << transform.xx() << ' '
<< transform.xy() << ' ' << transform.yx() << ' ' << transform.yy()
<< ' ' << transform.tx() << ' ' << transform.ty() << ")\"";
}
}
void SVGLinearGradientShader::emitDefs(
std::ostream& out,
const std::string& id,
const rive::Mat2D& transform) const
{
out << "<linearGradient id=\"" << id << "\" x1=\"" << m_sx << "\" y1=\""
<< m_sy << "\" x2=\"" << m_ex << "\" y2=\"" << m_ey
<< "\" gradientUnits=\"userSpaceOnUse\"";
emitGradientTransform(out, transform);
out << ">\n";
for (size_t i = 0; i < m_colors.size(); i++)
{
emitColorStop(out, m_colors[i], m_stops[i]);
}
out << "</linearGradient>\n";
}
// ---- SVGRadialGradientShader ----
SVGRadialGradientShader::SVGRadialGradientShader(
float cx,
float cy,
float radius,
const ColorInt colors[],
const float stops[],
size_t count)
: m_cx(cx)
, m_cy(cy)
, m_radius(radius)
, m_colors(colors, colors + count)
, m_stops(stops, stops + count)
{}
void SVGRadialGradientShader::emitDefs(
std::ostream& out,
const std::string& id,
const rive::Mat2D& transform) const
{
out << "<radialGradient id=\"" << id << "\" cx=\"" << m_cx << "\" cy=\""
<< m_cy << "\" r=\"" << m_radius << "\" gradientUnits=\"userSpaceOnUse\"";
emitGradientTransform(out, transform);
out << ">\n";
for (size_t i = 0; i < m_colors.size(); i++)
{
emitColorStop(out, m_colors[i], m_stops[i]);
}
out << "</radialGradient>\n";
}
// ---- SVGRenderPaint ----
void SVGRenderPaint::style(RenderPaintStyle value)
{
m_isStroke = (value == RenderPaintStyle::stroke);
}
void SVGRenderPaint::color(ColorInt value) { m_color = value; }
void SVGRenderPaint::thickness(float value) { m_thickness = value; }
void SVGRenderPaint::join(StrokeJoin value) { m_join = value; }
void SVGRenderPaint::cap(StrokeCap value) { m_cap = value; }
void SVGRenderPaint::blendMode(BlendMode value) { m_blendMode = value; }
void SVGRenderPaint::shader(rcp<RenderShader> sh) { m_shader = std::move(sh); }
SVGRenderShader* SVGRenderPaint::getSvgShader() const
{
return static_cast<SVGRenderShader*>(m_shader.get());
}
// ---- SVGRenderImage ----
SVGRenderImage::SVGRenderImage(
Span<const uint8_t> encodedBytes,
uint32_t width,
uint32_t height)
: m_encodedBytes(encodedBytes.begin(), encodedBytes.end())
{
m_Width = width;
m_Height = height;
}
static const char kBase64Table
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static std::string base64Encode(const uint8_t* data, size_t len)
{
std::string out;
out.reserve(((len + 2) / 3) * 4);
for (size_t i = 0; i < len; i += 3)
{
uint32_t n = (uint32_t)data[i] << 16;
if (i + 1 < len)
n |= (uint32_t)data[i + 1] << 8;
if (i + 2 < len)
n |= (uint32_t)data[i + 2];
out += kBase64Table[(n >> 18) & 0x3F];
out += kBase64Table[(n >> 12) & 0x3F];
out += (i + 1 < len) ? kBase64Table[(n >> 6) & 0x3F] : '=';
out += (i + 2 < len) ? kBase64Table[n & 0x3F] : '=';
}
return out;
}
std::string SVGRenderImage::toDataURI() const
{
// Detect format from magic bytes
const char* mime = "application/octet-stream";
if (m_encodedBytes.size() >= 8)
{
if (m_encodedBytes[0] == 0x89 && m_encodedBytes[1] == 'P')
mime = "image/png";
else if (m_encodedBytes[0] == 0xFF && m_encodedBytes[1] == 0xD8)
mime = "image/jpeg";
else if (
m_encodedBytes[0] == 'R' && m_encodedBytes[1] == 'I' &&
m_encodedBytes[2] == 'F' && m_encodedBytes[3] == 'F')
mime = "image/webp";
}
std::string uri = "data:";
uri += mime;
uri += ";base64,";
uri += base64Encode(m_encodedBytes.data(), m_encodedBytes.size());
return uri;
}
// ---- SVGRenderBuffer ----
SVGRenderBuffer::SVGRenderBuffer(
RenderBufferType type,
RenderBufferFlags flags,
size_t sizeInBytes)
: RenderBuffer(type, flags, sizeInBytes)
, m_bytes(sizeInBytes)
{}
void* SVGRenderBuffer::onMap() { return m_bytes.data(); }
void SVGRenderBuffer::onUnmap() {}
// ---- SVGFactory ----
rcp<RenderBuffer> SVGFactory::makeRenderBuffer(
RenderBufferType type,
RenderBufferFlags flags,
size_t sizeInBytes)
{
return make_rcp<SVGRenderBuffer>(type, flags, sizeInBytes);
}
rcp<RenderShader> SVGFactory::makeLinearGradient(
float sx,
float sy,
float ex,
float ey,
const ColorInt colors[],
const float stops[],
size_t count)
{
return make_rcp<SVGLinearGradientShader>(
sx,
sy,
ex,
ey,
colors,
stops,
count);
}
rcp<RenderShader> SVGFactory::makeRadialGradient(
float cx,
float cy,
float radius,
const ColorInt colors[],
const float stops[],
size_t count)
{
return make_rcp<SVGRadialGradientShader>(
cx,
cy,
radius,
colors,
stops,
count);
}
rcp<RenderPath> SVGFactory::makeRenderPath(RawPath& rawPath, FillRule fillRule)
{
return make_rcp<SVGRenderPath>(rawPath, fillRule);
}
rcp<RenderPath> SVGFactory::makeEmptyRenderPath()
{
return make_rcp<SVGRenderPath>();
}
rcp<RenderPaint> SVGFactory::makeRenderPaint()
{
return make_rcp<SVGRenderPaint>();
}
rcp<RenderImage> SVGFactory::decodeImage(Span<const uint8_t> data)
{
uint32_t width = 0, height = 0;
if (m_decodeImageSize == nullptr || !m_decodeImageSize(data, &width, &height))
{
return nullptr;
}
return make_rcp<SVGRenderImage>(data, width, height);
}
} // namespace rive