You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
11 KiB
344 lines
11 KiB
#pragma once
|
|
|
|
#include "pch.h"
|
|
|
|
#include <winrt/Microsoft.Graphics.Canvas.Brushes.h>
|
|
#include <winrt/Windows.Foundation.Numerics.h>
|
|
#include <winrt/Windows.UI.Text.h>
|
|
#include "JSValueReader.h"
|
|
|
|
#define _USE_MATH_DEFINES
|
|
#include <math.h>
|
|
|
|
using namespace winrt;
|
|
using namespace Microsoft::Graphics::Canvas;
|
|
using namespace Microsoft::ReactNative;
|
|
using namespace Windows::UI;
|
|
using namespace Windows::UI::Text;
|
|
|
|
namespace winrt::RNSVG {
|
|
struct Utils {
|
|
public:
|
|
static std::vector<float> GetAdjustedStrokeArray(IVector<SVGLength> const &value, float strokeWidth, float canvasDiagonal) {
|
|
std::vector<float> result;
|
|
|
|
for (auto const &item : value) {
|
|
float absValue{GetAbsoluteLength(item, canvasDiagonal)};
|
|
|
|
// Win2D sets the length of each dash as the product of the element value in array and stroke width,
|
|
// we divide each value in the dashArray by StrokeWidth to account for this.
|
|
// http://microsoft.github.io/Win2D/WinUI2/html/P_Microsoft_Graphics_Canvas_Geometry_CanvasStrokeStyle_CustomDashStyle.htm
|
|
result.push_back(absValue / (strokeWidth == 0.0f ? 1.0f : strokeWidth));
|
|
}
|
|
|
|
return std::move(result);
|
|
}
|
|
|
|
static float GetCanvasDiagonal(Windows::Foundation::Size const &size) {
|
|
float powX{std::powf(size.Width, 2)};
|
|
float powY{std::powf(size.Height, 2)};
|
|
|
|
return std::sqrtf(powX + powY) * static_cast<float>(M_SQRT1_2);
|
|
}
|
|
|
|
static float GetAbsoluteLength(SVGLength const &length, float relativeTo) {
|
|
auto value{length.Value()};
|
|
|
|
// 1in = 2.54cm = 96px
|
|
auto inch{96.0f};
|
|
auto cm{inch / 2.54f};
|
|
|
|
switch (length.Unit()) {
|
|
case RNSVG::LengthType::Percentage:
|
|
return value / 100.0f * relativeTo;
|
|
case RNSVG::LengthType::Centimeter:
|
|
// 1cm = 96px/2.54
|
|
return value * cm;
|
|
case RNSVG::LengthType::Millimeter:
|
|
// 1mm = 1/10th of 1cm
|
|
return value * cm / 10.0f;
|
|
case RNSVG::LengthType::Inch:
|
|
// 1in = 2.54cm = 96px
|
|
return value * inch;
|
|
case RNSVG::LengthType::Point:
|
|
// 1pt = 1/72th of 1in
|
|
return value * inch / 72.0f;
|
|
case RNSVG::LengthType::Pica:
|
|
// 1pc = 1/6th of 1in
|
|
return value * inch / 6.0f;
|
|
case RNSVG::LengthType::Pixel:
|
|
default:
|
|
return value;
|
|
}
|
|
}
|
|
|
|
static Numerics::float3x2 GetRotationMatrix(float degrees) {
|
|
// convert to radians
|
|
auto radians{degrees * static_cast<float>(M_PI) / 100.0f};
|
|
return Numerics::make_float3x2_rotation(radians);
|
|
}
|
|
|
|
static FontWeight FontWeightFrom(hstring const& weight, Xaml::FrameworkElement const& parent) {
|
|
if (weight == L"normal") {
|
|
return FontWeights::Normal();
|
|
} else if (weight == L"bold") {
|
|
return FontWeights::Bold();
|
|
} else if (weight == L"bolder" || weight == L"lighter" || weight == L"auto") {
|
|
auto const &groupView{parent.try_as<RNSVG::GroupView>()};
|
|
FontWeight parentWeight{
|
|
groupView ? FontWeightFrom(groupView.FontWeight(), groupView.SvgParent()) : FontWeights::Normal()};
|
|
|
|
if (weight == L"bolder") {
|
|
return Bolder(parentWeight.Weight);
|
|
} else if (weight == L"lighter") {
|
|
return Lighter(parentWeight.Weight);
|
|
} else if (weight == L"auto") {
|
|
return parentWeight;
|
|
}
|
|
}
|
|
|
|
return GetClosestFontWeight(std::stof(weight.c_str(), nullptr));
|
|
}
|
|
|
|
static FontWeight GetClosestFontWeight(float weight) {
|
|
if (weight > 325 && weight < 375) {
|
|
return FontWeights::SemiLight();
|
|
} else if (weight > 925) {
|
|
return FontWeights::ExtraBlack();
|
|
} else {
|
|
switch (static_cast<uint16_t>(std::round(weight / 100.0f))) {
|
|
case 1:
|
|
return FontWeights::Thin();
|
|
case 2:
|
|
return FontWeights::ExtraLight();
|
|
case 3:
|
|
return FontWeights::Light();
|
|
case 4:
|
|
return FontWeights::Normal();
|
|
case 5:
|
|
return FontWeights::Medium();
|
|
case 6:
|
|
return FontWeights::SemiBold();
|
|
case 7:
|
|
return FontWeights::Bold();
|
|
case 8:
|
|
return FontWeights::ExtraBold();
|
|
case 9:
|
|
default:
|
|
return FontWeights::ExtraBlack();
|
|
}
|
|
}
|
|
}
|
|
|
|
static FontWeight Bolder(uint16_t weight) {
|
|
if (weight < 350) {
|
|
return FontWeights::Normal();
|
|
} else if (weight < 550) {
|
|
return FontWeights::Bold();
|
|
} else if (weight < 900) {
|
|
return FontWeights::Black();
|
|
} else {
|
|
return FontWeights::ExtraBlack();
|
|
}
|
|
}
|
|
|
|
static FontWeight Lighter(uint16_t weight) {
|
|
if (weight < 550) {
|
|
return FontWeights::Thin();
|
|
} else if (weight < 750) {
|
|
return FontWeights::Normal();
|
|
} else {
|
|
return FontWeights::Bold();
|
|
}
|
|
}
|
|
|
|
static Numerics::float3x2 GetViewBoxTransform(Rect vbRect, Rect elRect, std::string align, RNSVG::MeetOrSlice meetOrSlice) {
|
|
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
|
|
|
|
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute
|
|
// respectively.
|
|
float vbX = vbRect.X;
|
|
float vbY = vbRect.Y;
|
|
float vbWidth = vbRect.Width;
|
|
float vbHeight = vbRect.Height;
|
|
|
|
// Let e-x, e-y, e-width, e-height be the position and size of the element respectively.
|
|
float eX = elRect.X;
|
|
float eY = elRect.Y;
|
|
float eWidth = elRect.Width;
|
|
float eHeight = elRect.Height;
|
|
|
|
// Initialize scale-x to e-width/vb-width.
|
|
float scaleX = eWidth / vbWidth;
|
|
|
|
// Initialize scale-y to e-height/vb-height.
|
|
float scaleY = eHeight / vbHeight;
|
|
|
|
// If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller.
|
|
// Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the
|
|
// larger.
|
|
if (align != "none" && meetOrSlice == RNSVG::MeetOrSlice::Meet) {
|
|
scaleX = scaleY = std::min(scaleX, scaleY);
|
|
} else if (align != "none" && meetOrSlice == RNSVG::MeetOrSlice::Slice) {
|
|
scaleX = scaleY = std::max(scaleX, scaleY);
|
|
}
|
|
|
|
// Initialize translate-x to e-x - (vb-x * scale-x).
|
|
float translateX = eX - (vbX * scaleX);
|
|
|
|
// Initialize translate-y to e-y - (vb-y * scale-y).
|
|
float translateY = eY - (vbY * scaleY);
|
|
|
|
// If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x.
|
|
if (align.find("xMid") != std::string::npos) {
|
|
translateX += (eWidth - vbWidth * scaleX) / 2.0f;
|
|
}
|
|
|
|
// If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x.
|
|
if (align.find("xMax") != std::string::npos) {
|
|
translateX += (eWidth - vbWidth * scaleX);
|
|
}
|
|
|
|
// If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y.
|
|
if (align.find("YMid") != std::string::npos) {
|
|
translateY += (eHeight - vbHeight * scaleY) / 2.0f;
|
|
}
|
|
|
|
// If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y.
|
|
if (align.find("YMax") != std::string::npos) {
|
|
translateY += (eHeight - vbHeight * scaleY);
|
|
}
|
|
|
|
// The transform applied to content contained by the element is given by
|
|
// translate(translate-x, translate-y) scale(scale-x, scale-y).
|
|
auto const &translate{Numerics::make_float3x2_translation(translateX, translateY)};
|
|
auto const &scale{Numerics::make_float3x2_scale(scaleX, scaleY)};
|
|
|
|
return scale * translate;
|
|
}
|
|
|
|
static RNSVG::MeetOrSlice GetMeetOrSlice(JSValue const &value) {
|
|
if (value.IsNull()) {
|
|
return RNSVG::MeetOrSlice::Meet;
|
|
}
|
|
|
|
switch (value.AsInt8()) {
|
|
case 2:
|
|
return RNSVG::MeetOrSlice::None;
|
|
case 1:
|
|
return RNSVG::MeetOrSlice::Slice;
|
|
case 0:
|
|
default:
|
|
return RNSVG::MeetOrSlice::Meet;
|
|
}
|
|
}
|
|
|
|
static std::string JSValueAsBrushUnits(JSValue const &value, std::string defaultValue = "objectBoundingBox") {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else {
|
|
switch (value.AsInt32()) {
|
|
case 1:
|
|
return "userSpaceOnUse";
|
|
case 0:
|
|
default:
|
|
return "objectBoundingBox";
|
|
}
|
|
}
|
|
}
|
|
|
|
static float JSValueAsFloat(JSValue const &value, float defaultValue = 0.0f) {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else {
|
|
return value.AsSingle();
|
|
}
|
|
}
|
|
|
|
static std::string JSValueAsString(JSValue const &value, std::string defaultValue = "") {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else {
|
|
return value.AsString();
|
|
}
|
|
}
|
|
|
|
static Color JSValueAsColor(JSValue const &value, Color defaultValue = Colors::Transparent()) {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else if (auto const &brush{value.To<Xaml::Media::Brush>()}) {
|
|
if (auto const &scb{brush.try_as<Xaml::Media::SolidColorBrush>()}) {
|
|
return scb.Color();
|
|
}
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
static SVGLength JSValueAsSVGLength(JSValue const &value, SVGLength const &defaultValue = {}) {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else {
|
|
return RNSVG::implementation::SVGLength::From(value);
|
|
}
|
|
}
|
|
|
|
static Numerics::float3x2 JSValueAsTransform(JSValue const& value, Numerics::float3x2 defaultValue = {}) {
|
|
if (value.IsNull()) {
|
|
return defaultValue;
|
|
} else {
|
|
auto const &matrix{value.AsArray()};
|
|
|
|
return Numerics::float3x2(
|
|
matrix.at(0).AsSingle(),
|
|
matrix.at(1).AsSingle(),
|
|
matrix.at(2).AsSingle(),
|
|
matrix.at(3).AsSingle(),
|
|
matrix.at(4).AsSingle(),
|
|
matrix.at(5).AsSingle());
|
|
}
|
|
}
|
|
|
|
static std::vector<Brushes::CanvasGradientStop> JSValueAsStops(JSValue const& value) {
|
|
if (value.IsNull()) {
|
|
return {};
|
|
}
|
|
|
|
auto const &stops{value.AsArray()};
|
|
std::vector<Brushes::CanvasGradientStop> canvasStops{};
|
|
|
|
for (size_t i = 0; i < stops.size(); ++i) {
|
|
Brushes::CanvasGradientStop stop{};
|
|
stop.Position = Utils::JSValueAsFloat(stops.at(i));
|
|
stop.Color = Utils::JSValueAsColor(stops.at(++i));
|
|
canvasStops.push_back(stop);
|
|
}
|
|
|
|
return canvasStops;
|
|
}
|
|
|
|
static Brushes::ICanvasBrush GetCanvasBrush(
|
|
hstring const &brushId,
|
|
Color color,
|
|
RNSVG::SvgView const &root,
|
|
Geometry::CanvasGeometry const &geometry,
|
|
ICanvasResourceCreator const &resourceCreator) {
|
|
Brushes::ICanvasBrush brush{nullptr};
|
|
if (root && brushId != L"") {
|
|
if (brushId == L"currentColor") {
|
|
brush = Brushes::CanvasSolidColorBrush(resourceCreator, root.CurrentColor());
|
|
} else if (auto const &brushView{root.Brushes().TryLookup(brushId)}) {
|
|
brushView.SetBounds(geometry.ComputeBounds());
|
|
brush = brushView.Brush();
|
|
}
|
|
}
|
|
|
|
if (!brush) {
|
|
brush = Brushes::CanvasSolidColorBrush(resourceCreator, color);
|
|
}
|
|
|
|
return brush;
|
|
}
|
|
};
|
|
} // namespace winrt::RNSVG
|