#pragma once #include "pch.h" #include #include #include "JSValueReader.h" #include "D2DHelpers.h" #include "D2DBrush.h" #include "D2DDeviceContext.h" #define _USE_MATH_DEFINES #include using namespace winrt::Microsoft::ReactNative; namespace winrt::RNSVG { struct Utils { public: static std::vector GetAdjustedStrokeArray(IVector const &value, float strokeWidth, float canvasDiagonal) { std::vector 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 result; } static float GetCanvasDiagonal(Size const &size) { float powX{std::powf(size.Width, 2)}; float powY{std::powf(size.Height, 2)}; return std::sqrtf(powX + powY) * static_cast(M_SQRT1_2); } static float GetAbsoluteLength(SVGLength const &length, double relativeTo) { return GetAbsoluteLength(length, static_cast(relativeTo)); } static float GetAbsoluteLength(SVGLength const &length, float relativeTo) { auto value{length.Value}; auto unit{length.Unit}; // 1in = 2.54cm = 96px auto inch{96.0f}; auto cm{inch / 2.54f}; switch (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(M_PI) / 100.0f}; return Numerics::make_float3x2_rotation(radians); } static Numerics::float3x2 GetViewBoxTransform(Rect const &vbRect, Rect const &elRect, std::string align, RNSVG::MeetOrSlice const &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 D2D1_MATRIX_3X2_F GetViewBoxTransformD2D(Rect const &vbRect, Rect const &elRect, std::string align, RNSVG::MeetOrSlice const &meetOrSlice) { return D2DHelpers::AsD2DTransform(GetViewBoxTransform(vbRect, elRect, align, meetOrSlice)); } 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) { return value.IsNull() ? defaultValue : value.AsSingle(); } static std::string JSValueAsString(JSValue const &value, std::string defaultValue = "") { return value.IsNull() ? defaultValue : value.AsString(); } static Windows::UI::Color JSValueAsColor(JSValue const &value, Windows::UI::Color const &defaultValue = Colors::Transparent()) { if (value.IsNull()) { return defaultValue; } else if (auto const &brush{value.To()}) { if (auto const &scb{brush.try_as()}) { return scb.Color(); } } return defaultValue; } static SVGLength JSValueAsSVGLength(JSValue const &value, SVGLength const &defaultValue = {}) { return value.IsNull() ? defaultValue : value.To(); } static Numerics::float3x2 JSValueAsTransform(JSValue const &value, Numerics::float3x2 const &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 D2D1::Matrix3x2F JSValueAsD2DTransform(JSValue const &value, D2D1::Matrix3x2F const defaultValue = {}) { if (value.IsNull()) { return defaultValue; } else { auto const &matrix{value.AsArray()}; return D2D1::Matrix3x2F( 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 JSValueAsStops(JSValue const &value) { if (value.IsNull()) { return {}; } auto const &stops{value.AsArray()}; std::vector gradientStops; for (size_t i = 0; i < stops.size(); ++i) { D2D1_GRADIENT_STOP stop{}; stop.position = Utils::JSValueAsFloat(stops.at(i)); stop.color = D2DHelpers::AsD2DColor(Utils::JSValueAsColor(stops.at(++i))); gradientStops.emplace_back(stop); } return gradientStops; } static winrt::Windows::UI::Color JSValueAsD2DColor(float value) { auto color = static_cast(value); auto alpha = static_cast(color >> 24); auto red = static_cast((color >> 16) & 0xff); auto green = static_cast((color >> 8) & 0xff); auto blue = static_cast(color & 0xff); return winrt::Windows::UI::ColorHelper::FromArgb(alpha, red, green, blue); } static com_ptr GetCanvasBrush( hstring const &brushId, Windows::UI::Color const &color, RNSVG::SvgView const &root, com_ptr const &geometry, RNSVG::D2DDeviceContext const &context) { com_ptr brush; com_ptr deviceContext{get_self(context)->Get()}; auto winColor{Windows::UI::Colors::Transparent()}; if (root && brushId != L"") { if (brushId == L"currentColor") { com_ptr scb; winColor = root.CurrentColor(); deviceContext->CreateSolidColorBrush(D2DHelpers::AsD2DColor(winColor), scb.put()); brush = scb.as(); } else if (auto const &brushView{root.Brushes().TryLookup(brushId)}) { brushView.CreateBrush(); if (geometry) { D2D1_RECT_F bounds; geometry->GetBounds(nullptr, &bounds); brushView.SetBounds(D2DHelpers::FromD2DRect(bounds)); } brush = get_self(brushView.Brush())->Get(); } } if (!brush) { com_ptr scb; assert(root != nullptr); winColor = color; deviceContext->CreateSolidColorBrush(D2DHelpers::AsD2DColor(winColor), scb.put()); brush = scb.as(); } return brush; } static D2D1_VECTOR_2F GetScale(D2D1_MATRIX_3X2_F const matrix) { auto scaleX = std::sqrt(matrix.m11 * matrix.m11 + matrix.m12 * matrix.m12); auto scaleY = std::sqrt(matrix.m21 * matrix.m21 + matrix.m22 * matrix.m22); return {scaleX, scaleY}; } static D2D1_VECTOR_2F GetScale(Numerics::float3x2 const &matrix) { return GetScale(D2DHelpers::AsD2DTransform(matrix)); } }; } // namespace winrt::RNSVG