From ffde6edbf9259667cfaedf535f40933dab7cc991 Mon Sep 17 00:00:00 2001 From: scorsin-oai Date: Fri, 24 Apr 2026 13:07:28 -0500 Subject: [PATCH 1/6] Added built-in support for SVG (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This change adds support for SVG in the `` and `` element. This is implemented using the Skia SVG module. When rendered through an ``, the SVG gets rasterized, then encoded to PNG before being passed back to iOS/Android. There is thus a PNG encode/decode dance occurring, but it is still much much faster than spinning up a webview. That encode/decode dance could be optimized out if we added support for providing bitmaps directly from the C++ level in our asset loading APIs. When rendered through an ``, the SVG is rendered in real time in a hardware accelerated context (same as Lottie). When the element resizes, it will redraw at the new size, making svgs scalable when used in this context. Screenshot 2026-04-24 at 10 03 48 AM ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Documentation improvement - [ ] Performance optimization - [ ] Test improvement - [ ] Other (please describe) ## Testing - [ ] Tests pass locally (`bazel test //...`) - [ ] Added/updated tests for changes (if applicable) - [ ] Tested on multiple platforms (iOS/Android/Web/macOS as applicable) - [ ] Manual testing performed (describe below) ### Testing Details ## Checklist - [ ] Code follows project style guidelines - [ ] Documentation updated (if needed) - [ ] No breaking changes (or documented in description) - [ ] Commit messages follow [conventional format](../CONTRIBUTING.md#commit-messages) - [ ] No secrets, API keys, or internal URLs included ## Related Issues ## Additional Context (cherry picked from commit 4bfdd4341e3fd03c43e319a040298d3d1462d321) --- snap_drawing/BUILD.bazel | 1 + .../snap_drawing/cpp/Utils/AnimatedImage.cpp | 5 ++ .../src/snap_drawing/cpp/Utils/Image.cpp | 72 +++++++++++++++ .../src/snap_drawing/cpp/Utils/Image.hpp | 6 ++ .../cpp/Utils/SVGAnimatedImage.cpp | 78 ++++++++++++++++ .../cpp/Utils/SVGAnimatedImage.hpp | 43 +++++++++ .../src/snap_drawing/cpp/Utils/SVGUtils.cpp | 33 +++++++ .../src/snap_drawing/cpp/Utils/SVGUtils.hpp | 13 +++ snap_drawing/test/src/SVGImage_tests.cpp | 89 ++++++++++++++++++ .../src/valdi/android/AndroidAssetLoader.cpp | 90 ++++++++++++++----- .../src/valdi/android/AndroidAssetLoader.hpp | 12 ++- .../valdi/android/RuntimeManagerWrapper.cpp | 2 +- valdi/src/valdi/ios/SCValdiAssetLoader.mm | 44 ++++++++- 13 files changed, 460 insertions(+), 28 deletions(-) create mode 100644 snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.cpp create mode 100644 snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.hpp create mode 100644 snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp create mode 100644 snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp create mode 100644 snap_drawing/test/src/SVGImage_tests.cpp diff --git a/snap_drawing/BUILD.bazel b/snap_drawing/BUILD.bazel index 88618141..ce4e7d13 100644 --- a/snap_drawing/BUILD.bazel +++ b/snap_drawing/BUILD.bazel @@ -71,6 +71,7 @@ cc_library( "@skia//:png_decode_codec", "@skia//:png_encode_codec", "@skia//:skottie", + "@skia//:svg_renderer", "@skia//:webp_decode_codec", "@skia//:webp_encode_codec", ] + select({ diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/AnimatedImage.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/AnimatedImage.cpp index 26586bad..c1ce9aa3 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/AnimatedImage.cpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/AnimatedImage.cpp @@ -6,6 +6,7 @@ #include "snap_drawing/cpp/Utils/Image.hpp" #include "snap_drawing/cpp/Utils/LottieAnimatedImage.hpp" #include "snap_drawing/cpp/Utils/SkCodecAnimatedImage.hpp" +#include "snap_drawing/cpp/Utils/SVGAnimatedImage.hpp" #include "valdi_core/cpp/Utils/JSONReader.hpp" namespace snap::drawing { @@ -39,6 +40,10 @@ Valdi::Result> AnimatedImage::make(const Ref& f } const Valdi::BytesView bytesView(nullptr, data, length); + if (Image::isSVG(bytesView)) { + return SVGAnimatedImage::make(data, length).map>(); + } + auto skData = skDataFromBytes(bytesView, DataConversionModeAlwaysCopy); auto codec = SkCodec::MakeFromData(skData); if (codec == nullptr) { diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp index 6e76d699..486d10fb 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp @@ -9,8 +9,10 @@ #include "snap_drawing/cpp/Utils/BytesUtils.hpp" #include "snap_drawing/cpp/Utils/Bitmap.hpp" +#include "snap_drawing/cpp/Utils/SVGUtils.hpp" #include "valdi_core/cpp/Interfaces/IBitmap.hpp" +#include "valdi_core/cpp/Utils/TextParser.hpp" #include "valdi_core/cpp/Utils/ValueTypedArray.hpp" #include "snap_drawing/cpp/Utils/BitmapUtils.hpp" @@ -19,12 +21,17 @@ #include "include/codec/SkJpegDecoder.h" #include "include/codec/SkPngDecoder.h" #include "include/codec/SkWebpDecoder.h" +#include "include/core/SkCanvas.h" #include "include/core/SkStream.h" +#include "include/core/SkSurface.h" #include "include/encode/SkJpegEncoder.h" #include "include/encode/SkPngEncoder.h" #include "include/encode/SkWebpEncoder.h" +#include "modules/svg/include/SkSVGDOM.h" #include "src/image/SkImage_Base.h" +#include + namespace snap::drawing { Image::Image(const sk_sp& skImage) : _skImage(skImage) {} @@ -161,6 +168,10 @@ const Ref& Image::getFilter() const { } Valdi::Result> Image::make(const Valdi::BytesView& data) { + if (isSVG(data)) { + return makeFromSVG(data); + } + Image::initializeCodecs(); auto skData = skDataFromBytes(data, DataConversionModeNeverCopy); @@ -173,6 +184,67 @@ Valdi::Result> Image::make(const Valdi::BytesView& data) { return Ref(Valdi::makeShared(skImage)); } +bool Image::isSVG(const Valdi::BytesView& data) { + Valdi::TextParser parser(std::string_view(reinterpret_cast(data.data()), data.size())); + parser.tryParseWhitespaces(); + return parser.tryParse(" 0) { + return preferredDimension; + } + return static_cast(std::ceil(intrinsicDimension)); +} + +Valdi::Result> Image::makeFromSVG(const Valdi::BytesView& data, int preferredWidth, int preferredHeight) { + auto dom = makeSVGDOM(data); + if (!dom) { + return dom.moveError(); + } + + auto intrinsicSize = dom.value()->containerSize(); + auto intrinsicWidth = intrinsicSize.width(); + auto intrinsicHeight = intrinsicSize.height(); + + if (preferredWidth > 0 && preferredHeight <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { + preferredHeight = static_cast(std::ceil(preferredWidth * intrinsicHeight / intrinsicWidth)); + } else if (preferredHeight > 0 && preferredWidth <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { + preferredWidth = static_cast(std::ceil(preferredHeight * intrinsicWidth / intrinsicHeight)); + } + + auto outputWidth = resolveSVGDimension(preferredWidth, intrinsicWidth); + auto outputHeight = resolveSVGDimension(preferredHeight, intrinsicHeight); + if (outputWidth <= 0 || outputHeight <= 0) { + return Valdi::Error("SVG doesn't have a valid size"); + } + + auto imageInfo = SkImageInfo::MakeN32Premul(outputWidth, outputHeight); + auto surface = SkSurfaces::Raster(imageInfo); + if (!surface) { + return Valdi::Error("Unable to create SVG rasterization surface"); + } + + auto* canvas = surface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + + if (intrinsicWidth > 0 && intrinsicHeight > 0) { + canvas->scale(static_cast(outputWidth) / intrinsicWidth, + static_cast(outputHeight) / intrinsicHeight); + dom.value()->setContainerSize(intrinsicSize); + } else { + dom.value()->setContainerSize(SkSize::Make(outputWidth, outputHeight)); + } + + dom.value()->render(canvas); + auto skImage = surface->makeImageSnapshot(); + if (!skImage) { + return Valdi::Error("Unable to create image from SVG"); + } + + return Ref(Valdi::makeShared(skImage)); +} + Valdi::Result> Image::makeFromPixelsData(const Valdi::BitmapInfo& bitmapInfo, const Valdi::BytesView& pixelsData, bool shouldCopy) { diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/Image.hpp b/snap_drawing/src/snap_drawing/cpp/Utils/Image.hpp index f8a0cf85..acfb9ef5 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/Image.hpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/Image.hpp @@ -68,6 +68,12 @@ class Image : public Valdi::LoadedAsset { */ static Valdi::Result> make(const Valdi::BytesView& data); + static bool isSVG(const Valdi::BytesView& data); + + static Valdi::Result> makeFromSVG(const Valdi::BytesView& data, + int preferredWidth = 0, + int preferredHeight = 0); + /** Make an Image with the raw pixels data in the format specified in the BitmapInfo. If shouldCopy is false, the returned Image will use the bytes from the attached BytesView diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.cpp new file mode 100644 index 00000000..813c82a9 --- /dev/null +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.cpp @@ -0,0 +1,78 @@ +#include "snap_drawing/cpp/Utils/SVGAnimatedImage.hpp" + +#include "snap_drawing/cpp/Utils/SVGUtils.hpp" +#include "valdi_core/cpp/Utils/StringCache.hpp" + +#include "include/core/SkCanvas.h" + +namespace snap::drawing { + +SVGAnimatedImage::SVGAnimatedImage(const sk_sp& dom) : _dom(dom) { + auto size = _dom->containerSize(); + _size = Size(size.width(), size.height()); + if (_size.width <= 0 || _size.height <= 0) { + _size = Size(1, 1); + } +} + +SVGAnimatedImage::~SVGAnimatedImage() = default; + +Duration SVGAnimatedImage::getCurrentTime() const { + std::lock_guard lock(_mutex); + return _currentTime; +} + +const Duration& SVGAnimatedImage::getDuration() const { + return _duration; +} + +const Size& SVGAnimatedImage::getSize() const { + return _size; +} + +double SVGAnimatedImage::getFrameRate() const { + return 0.0; +} + +Valdi::Value SVGAnimatedImage::getMetadata() const { + return Valdi::Value() + .setMapValue("type", Valdi::Value(Valdi::StringBox::fromCString("svg"))) + .setMapValue("width", Valdi::Value(static_cast(_size.width))) + .setMapValue("height", Valdi::Value(static_cast(_size.height))); +} + +void SVGAnimatedImage::doDraw(SkCanvas* canvas, + const Rect& drawBounds, + const Duration& time, + FittingSizeMode fittingSizeMode) { + std::lock_guard lock(_mutex); + _currentTime = time; + + auto svgBounds = drawBounds.makeFittingSize(_size, fittingSizeMode); + auto saveCount = canvas->save(); + canvas->clipRect(drawBounds.getSkValue()); + canvas->translate(svgBounds.left, svgBounds.top); + + if (_size.width > 0 && _size.height > 0) { + canvas->scale(svgBounds.width() / _size.width, svgBounds.height() / _size.height); + _dom->setContainerSize(SkSize::Make(_size.width, _size.height)); + } else { + _dom->setContainerSize(SkSize::Make(svgBounds.width(), svgBounds.height())); + } + + _dom->render(canvas); + canvas->restoreToCount(saveCount); +} + +Valdi::Result> SVGAnimatedImage::make(const Valdi::Byte* data, size_t length) { + const Valdi::BytesView bytesView(nullptr, data, length); + auto dom = makeSVGDOM(bytesView); + if (!dom) { + return dom.moveError(); + } + return Valdi::makeShared(dom.value()); +} + +VALDI_CLASS_IMPL(SVGAnimatedImage) + +} // namespace snap::drawing diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.hpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.hpp new file mode 100644 index 00000000..65d678e7 --- /dev/null +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGAnimatedImage.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "snap_drawing/cpp/Utils/Aliases.hpp" +#include "snap_drawing/cpp/Utils/AnimatedImage.hpp" +#include "snap_drawing/cpp/Utils/Duration.hpp" +#include "snap_drawing/cpp/Utils/Geometry.hpp" +#include "valdi_core/cpp/Utils/Mutex.hpp" +#include "valdi_core/cpp/Utils/Result.hpp" + +#include "modules/svg/include/SkSVGDOM.h" + +namespace snap::drawing { + +class SVGAnimatedImage : public AnimatedImage { +public: + explicit SVGAnimatedImage(const sk_sp& dom); + ~SVGAnimatedImage() override; + + Duration getCurrentTime() const override; + const Duration& getDuration() const override; + const Size& getSize() const override; + double getFrameRate() const override; + Valdi::Value getMetadata() const override; + + static Valdi::Result> make(const Valdi::Byte* data, size_t length); + + VALDI_CLASS_HEADER(SVGAnimatedImage) + +protected: + void doDraw(SkCanvas* canvas, + const Rect& drawBounds, + const Duration& time, + FittingSizeMode fittingSizeMode) override; + +private: + mutable Valdi::Mutex _mutex; + sk_sp _dom; + Duration _duration; + Duration _currentTime; + Size _size; +}; + +} // namespace snap::drawing diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp new file mode 100644 index 00000000..bb0c7aeb --- /dev/null +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp @@ -0,0 +1,33 @@ +#include "snap_drawing/cpp/Utils/SVGUtils.hpp" + +#include "snap_drawing/cpp/Text/SkFontMgrSingleton.hpp" +#include "snap_drawing/cpp/Utils/Image.hpp" + +#include "include/core/SkStream.h" +#include "modules/skresources/include/SkResources.h" +#include "modules/svg/include/SkSVGDOM.h" + +#include + +namespace snap::drawing { + +Valdi::Result> makeSVGDOM(const Valdi::BytesView& data) { + Image::initializeCodecs(); + SkMemoryStream stream(data.data(), data.size(), false); + + auto fontManager = snap_drawing::getSkFontMgrSingleton(); + auto resourceProvider = skresources::DataURIResourceProviderProxy::Make( + nullptr, skresources::ImageDecodeStrategy::kPreDecode, fontManager); + + auto dom = SkSVGDOM::Builder() + .setFontManager(fontManager) + .setResourceProvider(std::move(resourceProvider)) + .make(stream); + if (!dom) { + return Valdi::Error("Unable to decode SVG"); + } + + return dom; +} + +} // namespace snap::drawing diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp new file mode 100644 index 00000000..55cff867 --- /dev/null +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "include/core/SkRefCnt.h" +#include "valdi_core/cpp/Utils/Bytes.hpp" +#include "valdi_core/cpp/Utils/Result.hpp" + +class SkSVGDOM; + +namespace snap::drawing { + +Valdi::Result> makeSVGDOM(const Valdi::BytesView& data); + +} // namespace snap::drawing diff --git a/snap_drawing/test/src/SVGImage_tests.cpp b/snap_drawing/test/src/SVGImage_tests.cpp new file mode 100644 index 00000000..e16e8712 --- /dev/null +++ b/snap_drawing/test/src/SVGImage_tests.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "snap_drawing/cpp/Utils/AnimatedImage.hpp" +#include "snap_drawing/cpp/Utils/Image.hpp" +#include "snap_drawing/cpp/Text/IFontManager.hpp" +#include "TestBitmap.hpp" + +namespace snap::drawing { + +static Valdi::BytesView bytesFromString(const char* value) { + return Valdi::BytesView(nullptr, reinterpret_cast(value), strlen(value)); +} + +static void expectBitmapIsRed(const Ref& image) { + TestBitmap bitmap(image->width(), image->height()); + auto info = bitmap.getInfo(); + auto* bytes = bitmap.lockBytes(); + image->draw(info, bytes); + bitmap.unlockBytes(); + + for (int y = 0; y < image->height(); y++) { + for (int x = 0; x < image->width(); x++) { + EXPECT_EQ(Color::red(), bitmap.getPixel(x, y)) << "at (" << x << ", " << y << ")"; + } + } +} + +TEST(SVGImage, detectsSVGAfterWhitespace) { + auto svg = bytesFromString(" \n"); + + ASSERT_TRUE(Image::isSVG(svg)); +} + +TEST(SVGImage, detectsXMLPreamble) { + auto svg = bytesFromString(""); + + ASSERT_TRUE(Image::isSVG(svg)); +} + +TEST(SVGImage, doesNotDetectLottieAsSVG) { + auto json = bytesFromString("{\"v\":\"5.7.4\",\"layers\":[]}"); + + ASSERT_FALSE(Image::isSVG(json)); +} + +TEST(SVGImage, rasterizesSVGWithIntrinsicSize) { + auto svg = bytesFromString( + "" + "" + ""); + + auto image = Image::make(svg); + + ASSERT_TRUE(image) << image.error().toString(); + EXPECT_EQ(12, image.value()->width()); + EXPECT_EQ(10, image.value()->height()); + expectBitmapIsRed(image.value()); +} + +TEST(SVGImage, rasterizesSVGWithPreferredSize) { + auto svg = bytesFromString( + "" + "" + ""); + + auto image = Image::makeFromSVG(svg, 24, 20); + + ASSERT_TRUE(image) << image.error().toString(); + EXPECT_EQ(24, image.value()->width()); + EXPECT_EQ(20, image.value()->height()); + expectBitmapIsRed(image.value()); +} + +TEST(SVGImage, createsAnimatedImageForSVG) { + auto svg = bytesFromString( + "" + "" + ""); + + auto image = AnimatedImage::make(nullptr, svg.data(), svg.size()); + + ASSERT_TRUE(image) << image.error().toString(); + EXPECT_EQ(12, image.value()->getSize().width); + EXPECT_EQ(10, image.value()->getSize().height); + EXPECT_EQ(0.0, image.value()->getFrameRate()); +} + +} // namespace snap::drawing diff --git a/valdi/src/valdi/android/AndroidAssetLoader.cpp b/valdi/src/valdi/android/AndroidAssetLoader.cpp index 419a0cd2..394e1697 100644 --- a/valdi/src/valdi/android/AndroidAssetLoader.cpp +++ b/valdi/src/valdi/android/AndroidAssetLoader.cpp @@ -13,6 +13,7 @@ #include "valdi_core/NativeCancelable.hpp" #include "valdi_core/cpp/Attributes/ImageFilter.hpp" #include "valdi_core/cpp/Resources/LoadedAsset.hpp" +#include "valdi_core/cpp/Threading/DispatchQueue.hpp" #include "valdi_core/cpp/Utils/DiskUtils.hpp" #include "valdi_core/cpp/Utils/Marshaller.hpp" #include "valdi_core/cpp/Utils/Trace.hpp" @@ -20,6 +21,8 @@ #include "valdi_core/jni/JavaCache.hpp" #include "valdi_core/jni/JavaUtils.hpp" +#include "snap_drawing/cpp/Utils/Image.hpp" + namespace ValdiAndroid { class AndroidLoadedAsset : public Valdi::LoadedAsset, public ValdiAndroid::GlobalRefJavaObjectBase { @@ -90,12 +93,43 @@ static std::vector getUrlSchemes(JavaEnv env, jobject supporte return urlSchemes; } +static void loadAssetFromBytes(const Valdi::Ref& resourceLoader, + const Valdi::BytesView& bytes, + int32_t preferredWidth, + int32_t preferredHeight, + const Valdi::Value& attachedData, + const Valdi::Ref& completion) { + auto* completionHandle = Valdi::unsafeBridgeRetain(completion.get()); + + const float* colorMatrixFilter = nullptr; + float blurRadiusFilter = 0.0f; + + auto typedFilter = attachedData.getTypedRef(); + if (typedFilter != nullptr) { + if (!typedFilter->isIdentityColorMatrix()) { + colorMatrixFilter = typedFilter->getColorMatrix(); + } + blurRadiusFilter = typedFilter->getBlurRadius(); + } + + resourceLoader->loadAssetFromBytes(bytes, + preferredWidth, + preferredHeight, + colorMatrixFilter, + blurRadiusFilter, + reinterpret_cast(completionHandle)); +} + class AndroidAssetLoaderWithDownloader : public Valdi::AssetLoader { public: AndroidAssetLoaderWithDownloader(const Valdi::Ref& resourceLoader, const std::vector& urlSchemes, - const Valdi::Ref& downloader) - : Valdi::AssetLoader(urlSchemes), _resourceLoader(resourceLoader), _downloader(downloader) {} + const Valdi::Ref& downloader, + const Valdi::Ref& workerQueue) + : Valdi::AssetLoader(urlSchemes), + _resourceLoader(resourceLoader), + _downloader(downloader), + _workerQueue(workerQueue) {} ~AndroidAssetLoaderWithDownloader() override = default; snap::valdi_core::AssetOutputType getOutputType() const override { @@ -114,41 +148,51 @@ class AndroidAssetLoaderWithDownloader : public Valdi::AssetLoader { const Valdi::Ref& completion) override { return _downloader->downloadItem( requestPayload.toStringBox(), - [resourceLoader = _resourceLoader, preferredWidth, preferredHeight, attachedData, completion](auto result) { + [resourceLoader = _resourceLoader, workerQueue = _workerQueue, preferredWidth, preferredHeight, attachedData, completion](auto result) { if (!result) { completion->onLoadComplete(result.moveError()); return; } - auto* completionHandle = Valdi::unsafeBridgeRetain(completion.get()); - - const float* colorMatrixFilter = nullptr; - float blurRadiusFilter = 0.0f; - - auto typedFilter = attachedData.getTypedRef(); - if (typedFilter != nullptr) { - if (!typedFilter->isIdentityColorMatrix()) { - colorMatrixFilter = typedFilter->getColorMatrix(); - } - blurRadiusFilter = typedFilter->getBlurRadius(); + auto bytes = result.value(); + if (snap::drawing::Image::isSVG(bytes)) { + workerQueue->async( + [resourceLoader, preferredWidth, preferredHeight, attachedData, completion, bytes]() { + auto image = snap::drawing::Image::makeFromSVG(bytes, preferredWidth, preferredHeight); + if (!image) { + completion->onLoadComplete(image.moveError()); + return; + } + + auto rasterizedBytes = image.value()->toPNG(); + if (!rasterizedBytes) { + completion->onLoadComplete(rasterizedBytes.moveError()); + return; + } + + loadAssetFromBytes(resourceLoader, + rasterizedBytes.value(), + preferredWidth, + preferredHeight, + attachedData, + completion); + }); + return; } - resourceLoader->loadAssetFromBytes(result.value(), - preferredWidth, - preferredHeight, - colorMatrixFilter, - blurRadiusFilter, - reinterpret_cast(completionHandle)); + loadAssetFromBytes(resourceLoader, bytes, preferredWidth, preferredHeight, attachedData, completion); }); } private: Valdi::Ref _resourceLoader; Valdi::Ref _downloader; + Valdi::Ref _workerQueue; }; -AndroidAssetLoaderFactory::AndroidAssetLoaderFactory(const Valdi::Ref& resourceLoader) - : _resourceLoader(resourceLoader) {} +AndroidAssetLoaderFactory::AndroidAssetLoaderFactory(const Valdi::Ref& resourceLoader, + const Valdi::Ref& workerQueue) + : _resourceLoader(resourceLoader), _workerQueue(workerQueue) {} AndroidAssetLoaderFactory::~AndroidAssetLoaderFactory() = default; snap::valdi_core::AssetOutputType AndroidAssetLoaderFactory::getOutputType() const { @@ -157,7 +201,7 @@ snap::valdi_core::AssetOutputType AndroidAssetLoaderFactory::getOutputType() con Valdi::Ref AndroidAssetLoaderFactory::createAssetLoader( const std::vector& urlSchemes, const Valdi::Ref& downloader) { - return Valdi::makeShared(_resourceLoader, urlSchemes, downloader); + return Valdi::makeShared(_resourceLoader, urlSchemes, downloader, _workerQueue); } AndroidAssetLoader::AndroidAssetLoader(const Valdi::Ref& resourceLoader, diff --git a/valdi/src/valdi/android/AndroidAssetLoader.hpp b/valdi/src/valdi/android/AndroidAssetLoader.hpp index d5be943e..31a47ae5 100644 --- a/valdi/src/valdi/android/AndroidAssetLoader.hpp +++ b/valdi/src/valdi/android/AndroidAssetLoader.hpp @@ -16,9 +16,18 @@ namespace ValdiAndroid { class ResourceLoader; +} // namespace ValdiAndroid + +namespace Valdi { +class DispatchQueue; +} + +namespace ValdiAndroid { + class AndroidAssetLoaderFactory : public Valdi::AssetLoaderFactory { public: - AndroidAssetLoaderFactory(const Valdi::Ref& resourceLoader); + AndroidAssetLoaderFactory(const Valdi::Ref& resourceLoader, + const Valdi::Ref& workerQueue); ~AndroidAssetLoaderFactory() override; snap::valdi_core::AssetOutputType getOutputType() const override; @@ -29,6 +38,7 @@ class AndroidAssetLoaderFactory : public Valdi::AssetLoaderFactory { private: Valdi::Ref _resourceLoader; + Valdi::Ref _workerQueue; }; class AndroidAssetLoader : public Valdi::AssetLoader, public ValdiAndroid::GlobalRefJavaObjectBase { diff --git a/valdi/src/valdi/android/RuntimeManagerWrapper.cpp b/valdi/src/valdi/android/RuntimeManagerWrapper.cpp index 9fdd8c4e..6b296fb4 100644 --- a/valdi/src/valdi/android/RuntimeManagerWrapper.cpp +++ b/valdi/src/valdi/android/RuntimeManagerWrapper.cpp @@ -170,7 +170,7 @@ RuntimeManagerWrapper::RuntimeManagerWrapper(JavaEnv env, } #endif _runtimeManager->getAssetLoaderManager()->registerAssetLoaderFactory( - Valdi::makeShared(_resourceLoader)); + Valdi::makeShared(_resourceLoader, _runtimeManager->getWorkerQueue())); } RuntimeManagerWrapper::~RuntimeManagerWrapper() { diff --git a/valdi/src/valdi/ios/SCValdiAssetLoader.mm b/valdi/src/valdi/ios/SCValdiAssetLoader.mm index 844d6e83..0b930e35 100644 --- a/valdi/src/valdi/ios/SCValdiAssetLoader.mm +++ b/valdi/src/valdi/ios/SCValdiAssetLoader.mm @@ -23,6 +23,8 @@ #import "valdi_core/cpp/Attributes/ImageFilter.hpp" #import "valdi/ios/Utils/SCValdiImageFilter.h" +#include "snap_drawing/cpp/Utils/Image.hpp" + namespace ValdiIOS { static std::vector getSupportedSchemes(NSArray *schemes) { @@ -105,6 +107,22 @@ static void handleImageLoadResult(SCValdiImage *image, } } +static Valdi::Result rasterizeSVGToPNGData(const Valdi::BytesView& bytes, + int32_t preferredWidth, + int32_t preferredHeight) { + auto image = snap::drawing::Image::makeFromSVG(bytes, preferredWidth, preferredHeight); + if (!image) { + return image.moveError(); + } + + auto png = image.value()->toPNG(); + if (!png) { + return png.moveError(); + } + + return ValdiIOS::NSDataFromBuffer(png.value()); +} + Valdi::Shared ImageAssetLoaderIOS::loadAsset(const Valdi::Value& requestPayload, int32_t preferredWidth, int32_t preferredHeight, @@ -251,14 +269,16 @@ static void handleVideoDidFinishLoading(id player, const Valdi::Ref& completion) final { auto imageFilter = associatedData.getTypedRef(); return _downloader->downloadItem(requestPayload.toStringBox(), - [weakSelf = Valdi::weakRef(this), imageFilter = std::move(imageFilter), completion](const auto& result) { + [weakSelf = Valdi::weakRef(this), imageFilter = std::move(imageFilter), completion, preferredWidth, preferredHeight](const auto& result) { if (auto strongSelf = weakSelf.lock()) { - strongSelf->onBytesLoaded(result, imageFilter, completion); + strongSelf->onBytesLoaded(result, preferredWidth, preferredHeight, imageFilter, completion); } }); } void onBytesLoaded(const Valdi::Result& result, + int32_t preferredWidth, + int32_t preferredHeight, const Valdi::Ref &imageFilter, const Valdi::Ref& completion) { if (!result) { @@ -266,7 +286,25 @@ void onBytesLoaded(const Valdi::Result& result, return; } - NSData *data = ValdiIOS::NSDataFromBuffer(result.value()); + auto bytes = result.value(); + if (snap::drawing::Image::isSVG(bytes)) { + _workerQueue->async([weakSelf = Valdi::weakRef(this), bytes, preferredWidth, preferredHeight, imageFilter, completion]() { + auto pngData = rasterizeSVGToPNGData(bytes, preferredWidth, preferredHeight); + if (!pngData) { + completion->onLoadComplete(pngData.error()); + return; + } + + NSError *error = nil; + SCValdiImage *image = [SCValdiImage imageWithData:pngData.value() error:&error]; + if (auto strongSelf = weakSelf.lock()) { + handleImageLoadResult(image, error, imageFilter, strongSelf->_workerQueue, completion); + } + }); + return; + } + + NSData *data = ValdiIOS::NSDataFromBuffer(bytes); NSError *error = nil; SCValdiImage *image = [SCValdiImage imageWithData:data error:&error]; From 8d394d365312122401ab4408cc0833311638f30b Mon Sep 17 00:00:00 2001 From: scorsin-oai Date: Mon, 4 May 2026 11:56:36 -0500 Subject: [PATCH 2/6] Copy-less SVG rasterization (#63) This change removes the PNG encode-decode dance when using an `` element with an SVG source. We were encoding the SVGs into PNG before handing back to the iOS/Android platform code which was able to take care of the decode from byte array. We now directly raster into a Bitmap, and the bitmap gets represented into an iOS/Android image representation without a further copy, and without decode/encode dance. - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Documentation improvement - [ ] Performance optimization - [ ] Test improvement - [ ] Other (please describe) - [ ] Tests pass locally (`bazel test //...`) - [ ] Added/updated tests for changes (if applicable) - [ ] Tested on multiple platforms (iOS/Android/Web/macOS as applicable) - [ ] Manual testing performed (describe below) - [ ] Code follows project style guidelines - [ ] Documentation updated (if needed) - [ ] No breaking changes (or documented in description) - [ ] Commit messages follow [conventional format](../CONTRIBUTING.md#commit-messages) - [ ] No secrets, API keys, or internal URLs included (cherry picked from commit 9730fbebdec48853fd1ceadc7d4604636b23e1c6) --- .../src/snap_drawing/cpp/Utils/Image.cpp | 61 +------- .../src/snap_drawing/cpp/Utils/SVGUtils.cpp | 116 +++++++++++++++ .../src/snap_drawing/cpp/Utils/SVGUtils.hpp | 12 ++ valdi/BUILD.bazel | 29 ++++ .../com/snap/valdi/bundle/ResourceResolver.kt | 29 +++- .../com/snap/valdi/utils/ValdiImageFactory.kt | 7 +- .../src/valdi/android/AndroidAssetLoader.cpp | 60 +++++--- valdi/src/valdi/android/ResourceLoader.cpp | 18 +++ valdi/src/valdi/android/ResourceLoader.hpp | 8 ++ valdi/src/valdi/ios/SCValdiAssetLoader.mm | 39 ++--- valdi/src/valdi/svg/SVGRenderer.cpp | 18 +++ valdi/src/valdi/svg/SVGRenderer.hpp | 20 +++ valdi/test/svg/SVGRenderer_tests.cpp | 99 +++++++++++++ valdi_core/BUILD.bazel | 8 +- .../valdi_core/SCValdiIOSBitmapFactory.hpp | 19 +++ .../ios/valdi_core/SCValdiIOSBitmapFactory.mm | 135 ++++++++++++++++++ .../valdi_core/SCValdiObjCConversionUtils.mm | 2 + 17 files changed, 580 insertions(+), 100 deletions(-) create mode 100644 valdi/src/valdi/svg/SVGRenderer.cpp create mode 100644 valdi/src/valdi/svg/SVGRenderer.hpp create mode 100644 valdi/test/svg/SVGRenderer_tests.cpp create mode 100644 valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.hpp create mode 100644 valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.mm diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp index 486d10fb..b71abe64 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/Image.cpp @@ -9,10 +9,10 @@ #include "snap_drawing/cpp/Utils/BytesUtils.hpp" #include "snap_drawing/cpp/Utils/Bitmap.hpp" +#include "snap_drawing/cpp/Utils/BitmapFactory.hpp" #include "snap_drawing/cpp/Utils/SVGUtils.hpp" #include "valdi_core/cpp/Interfaces/IBitmap.hpp" -#include "valdi_core/cpp/Utils/TextParser.hpp" #include "valdi_core/cpp/Utils/ValueTypedArray.hpp" #include "snap_drawing/cpp/Utils/BitmapUtils.hpp" @@ -185,64 +185,17 @@ Valdi::Result> Image::make(const Valdi::BytesView& data) { } bool Image::isSVG(const Valdi::BytesView& data) { - Valdi::TextParser parser(std::string_view(reinterpret_cast(data.data()), data.size())); - parser.tryParseWhitespaces(); - return parser.tryParse(" 0) { - return preferredDimension; - } - return static_cast(std::ceil(intrinsicDimension)); + return snap::drawing::isSVG(data); } Valdi::Result> Image::makeFromSVG(const Valdi::BytesView& data, int preferredWidth, int preferredHeight) { - auto dom = makeSVGDOM(data); - if (!dom) { - return dom.moveError(); - } - - auto intrinsicSize = dom.value()->containerSize(); - auto intrinsicWidth = intrinsicSize.width(); - auto intrinsicHeight = intrinsicSize.height(); - - if (preferredWidth > 0 && preferredHeight <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { - preferredHeight = static_cast(std::ceil(preferredWidth * intrinsicHeight / intrinsicWidth)); - } else if (preferredHeight > 0 && preferredWidth <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { - preferredWidth = static_cast(std::ceil(preferredHeight * intrinsicWidth / intrinsicHeight)); - } - - auto outputWidth = resolveSVGDimension(preferredWidth, intrinsicWidth); - auto outputHeight = resolveSVGDimension(preferredHeight, intrinsicHeight); - if (outputWidth <= 0 || outputHeight <= 0) { - return Valdi::Error("SVG doesn't have a valid size"); + auto bitmap = rasterizeSVG( + data, BitmapFactory::getInstance(Valdi::ColorType::ColorTypeRGBA8888), preferredWidth, preferredHeight); + if (!bitmap) { + return bitmap.moveError(); } - auto imageInfo = SkImageInfo::MakeN32Premul(outputWidth, outputHeight); - auto surface = SkSurfaces::Raster(imageInfo); - if (!surface) { - return Valdi::Error("Unable to create SVG rasterization surface"); - } - - auto* canvas = surface->getCanvas(); - canvas->clear(SK_ColorTRANSPARENT); - - if (intrinsicWidth > 0 && intrinsicHeight > 0) { - canvas->scale(static_cast(outputWidth) / intrinsicWidth, - static_cast(outputHeight) / intrinsicHeight); - dom.value()->setContainerSize(intrinsicSize); - } else { - dom.value()->setContainerSize(SkSize::Make(outputWidth, outputHeight)); - } - - dom.value()->render(canvas); - auto skImage = surface->makeImageSnapshot(); - if (!skImage) { - return Valdi::Error("Unable to create image from SVG"); - } - - return Ref(Valdi::makeShared(skImage)); + return makeFromBitmap(bitmap.moveValue(), false); } Valdi::Result> Image::makeFromPixelsData(const Valdi::BitmapInfo& bitmapInfo, diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp index bb0c7aeb..ce05117a 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.cpp @@ -1,16 +1,27 @@ #include "snap_drawing/cpp/Utils/SVGUtils.hpp" #include "snap_drawing/cpp/Text/SkFontMgrSingleton.hpp" +#include "snap_drawing/cpp/Utils/BitmapUtils.hpp" #include "snap_drawing/cpp/Utils/Image.hpp" +#include "include/core/SkCanvas.h" #include "include/core/SkStream.h" +#include "include/core/SkSurface.h" #include "modules/skresources/include/SkResources.h" #include "modules/svg/include/SkSVGDOM.h" +#include "valdi_core/cpp/Utils/TextParser.hpp" +#include #include namespace snap::drawing { +bool isSVG(const Valdi::BytesView& data) { + Valdi::TextParser parser(std::string_view(reinterpret_cast(data.data()), data.size())); + parser.tryParseWhitespaces(); + return parser.tryParse("> makeSVGDOM(const Valdi::BytesView& data) { Image::initializeCodecs(); SkMemoryStream stream(data.data(), data.size(), false); @@ -30,4 +41,109 @@ Valdi::Result> makeSVGDOM(const Valdi::BytesView& data) { return dom; } +Valdi::Result> getSVGSize(const Valdi::BytesView& data) { + auto dom = makeSVGDOM(data); + if (!dom) { + return dom.moveError(); + } + + auto size = dom.value()->containerSize(); + return std::make_pair(static_cast(std::ceil(size.width())), static_cast(std::ceil(size.height()))); +} + +static int resolveSVGDimension(int preferredDimension, SkScalar intrinsicDimension) { + if (preferredDimension > 0) { + return preferredDimension; + } + return static_cast(std::ceil(intrinsicDimension)); +} + +class ScopedBitmapLock { +public: + explicit ScopedBitmapLock(const Valdi::Ref& bitmap) : _bitmap(bitmap) { + _bytes = _bitmap->lockBytes(); + } + + ~ScopedBitmapLock() { + if (_bytes != nullptr) { + _bitmap->unlockBytes(); + } + } + + void* bytes() const { + return _bytes; + } + +private: + Valdi::Ref _bitmap; + void* _bytes = nullptr; +}; + +Valdi::Result> rasterizeSVG(const Valdi::BytesView& data, + const Valdi::Ref& bitmapFactory, + int preferredWidth, + int preferredHeight) { + if (bitmapFactory == nullptr) { + return Valdi::Error("SVG rasterization requires a bitmap factory"); + } + + auto dom = makeSVGDOM(data); + if (!dom) { + return dom.moveError(); + } + + auto intrinsicSize = dom.value()->containerSize(); + auto intrinsicWidth = intrinsicSize.width(); + auto intrinsicHeight = intrinsicSize.height(); + + if (preferredWidth > 0 && preferredHeight <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { + preferredHeight = static_cast(std::ceil(preferredWidth * intrinsicHeight / intrinsicWidth)); + } else if (preferredHeight > 0 && preferredWidth <= 0 && intrinsicWidth > 0 && intrinsicHeight > 0) { + preferredWidth = static_cast(std::ceil(preferredHeight * intrinsicWidth / intrinsicHeight)); + } + + auto outputWidth = resolveSVGDimension(preferredWidth, intrinsicWidth); + auto outputHeight = resolveSVGDimension(preferredHeight, intrinsicHeight); + if (outputWidth <= 0 || outputHeight <= 0) { + return Valdi::Error("SVG doesn't have a valid size"); + } + + auto bitmap = bitmapFactory->createBitmap(outputWidth, outputHeight); + if (!bitmap) { + return bitmap.error().rethrow("Failed to create SVG rasterization bitmap: "); + } + + auto outputBitmap = bitmap.moveValue(); + auto bitmapInfo = outputBitmap->getInfo(); + if (bitmapInfo.width != outputWidth || bitmapInfo.height != outputHeight) { + return Valdi::Error("SVG rasterization bitmap factory returned an unexpected bitmap size"); + } + + ScopedBitmapLock bitmapLock(outputBitmap); + if (bitmapLock.bytes() == nullptr) { + return Valdi::Error("Failed to lock SVG rasterization bitmap bytes"); + } + + auto surface = SkSurfaces::WrapPixels(toSkiaImageInfo(bitmapInfo), bitmapLock.bytes(), bitmapInfo.rowBytes); + if (!surface) { + return Valdi::Error("Unable to create SVG rasterization surface"); + } + + auto* canvas = surface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + + if (intrinsicWidth > 0 && intrinsicHeight > 0) { + canvas->scale(static_cast(outputWidth) / intrinsicWidth, + static_cast(outputHeight) / intrinsicHeight); + dom.value()->setContainerSize(intrinsicSize); + } else { + dom.value()->setContainerSize(SkSize::Make(outputWidth, outputHeight)); + } + + dom.value()->render(canvas); + surface = nullptr; + + return outputBitmap; +} + } // namespace snap::drawing diff --git a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp index 55cff867..f78c179a 100644 --- a/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp +++ b/snap_drawing/src/snap_drawing/cpp/Utils/SVGUtils.hpp @@ -1,13 +1,25 @@ #pragma once #include "include/core/SkRefCnt.h" +#include "valdi_core/cpp/Interfaces/IBitmap.hpp" +#include "valdi_core/cpp/Interfaces/IBitmapFactory.hpp" #include "valdi_core/cpp/Utils/Bytes.hpp" #include "valdi_core/cpp/Utils/Result.hpp" +#include class SkSVGDOM; namespace snap::drawing { +bool isSVG(const Valdi::BytesView& data); + Valdi::Result> makeSVGDOM(const Valdi::BytesView& data); +Valdi::Result> getSVGSize(const Valdi::BytesView& data); + +Valdi::Result> rasterizeSVG(const Valdi::BytesView& data, + const Valdi::Ref& bitmapFactory, + int preferredWidth = 0, + int preferredHeight = 0); + } // namespace snap::drawing diff --git a/valdi/BUILD.bazel b/valdi/BUILD.bazel index 8cf58597..3f1e09d0 100644 --- a/valdi/BUILD.bazel +++ b/valdi/BUILD.bazel @@ -172,6 +172,23 @@ cc_library( ], ) +cc_library( + name = "valdi_svg", + srcs = glob([ + "src/valdi/svg/**/*.cpp", + ]), + hdrs = glob([ + "src/valdi/svg/**/*.hpp", + ]), + copts = CC_COMPILER_FLAGS, + strip_include_prefix = "src", + visibility = ["//visibility:public"], + deps = [ + "//snap_drawing:snap_drawing_cc", + "//valdi_core:valdi_core_cc", + ], +) + # Implementation of the JS engine abstraction # using JavaScriptCore cc_library( @@ -408,6 +425,7 @@ cc_library( ":valdi_jni", ":valdi_runtime_with_vm", ":valdi_snap_drawing", + ":valdi_svg", "//libs/utils:utils_encoding_cc", "//valdi_core:valdi_core_android", ], @@ -593,6 +611,7 @@ objc_library( deps = [ ":valdi_runtime_with_vm", ":valdi_snap_drawing", + ":valdi_svg", "//libs/utils:utils_obj_cpp_ptr_wrapper", "//src/valdi_modules/src/valdi/drawing:drawing_objc", "//valdi:djinni_objc_cpp", @@ -770,6 +789,15 @@ valdi_test( ], ) +valdi_test( + name = "test_svg", + srcs = glob(["test/svg/**/*.cpp"]), + deps = [ + ":valdi_svg", + "//snap_drawing:test_utils", + ], +) + # Embed generated .valdiarchive containing the fake uploaded resources valdi_static_resource( name = "remote_assets_static_res", @@ -848,6 +876,7 @@ cc_test( ":test_integration_lib", ":test_runtime_lib", ":test_snap_drawing_lib", + ":test_svg_lib", "@gtest//:gtest_main", ], ) diff --git a/valdi/src/java/com/snap/valdi/bundle/ResourceResolver.kt b/valdi/src/java/com/snap/valdi/bundle/ResourceResolver.kt index 77d9553c..d6c1d751 100644 --- a/valdi/src/java/com/snap/valdi/bundle/ResourceResolver.kt +++ b/valdi/src/java/com/snap/valdi/bundle/ResourceResolver.kt @@ -17,6 +17,7 @@ import com.snap.valdi.utils.info import com.snapchat.client.valdi_core.Cancelable import android.util.Log +import android.graphics.Bitmap import com.snap.valdi.imageloading.ValdiImageLoaderPostprocessor import com.snap.valdi.utils.ValdiImageFactory @@ -122,6 +123,32 @@ class ResourceResolver(private val context: Context, colorMatrixFilter: Any?, blurRadiusFilter: Float, completionHandle: Long): Any? { + return loadBitmapAsset(preferredWidth, preferredHeight, colorMatrixFilter, blurRadiusFilter, completionHandle) { + ValdiImageFactory.fromByteArray(bytes) + } + } + + /** + Called by C++ to perform a load from an already-rasterized bitmap into a ValdiImage. + */ + @Keep + fun loadAssetFromBitmap(bitmap: Any, + preferredWidth: Int, + preferredHeight: Int, + colorMatrixFilter: Any?, + blurRadiusFilter: Float, + completionHandle: Long): Any? { + return loadBitmapAsset(preferredWidth, preferredHeight, colorMatrixFilter, blurRadiusFilter, completionHandle) { + ValdiImageFactory.fromBitmap(bitmap as Bitmap) + } + } + + private fun loadBitmapAsset(preferredWidth: Int, + preferredHeight: Int, + colorMatrixFilter: Any?, + blurRadiusFilter: Float, + completionHandle: Long, + createImage: () -> ValdiImage): Any? { val completion = AssetImageLoaderCompletion(completionHandle, colorMatrixFilter as? FloatArray) val options = ValdiImageLoadOptions( preferredWidth, @@ -132,7 +159,7 @@ class ResourceResolver(private val context: Context, this.postprocessor.executor.submit { val valdiImage = try { - ValdiImageFactory.fromByteArray(bytes) + createImage() } catch (err: Throwable) { completion.onImageLoadComplete(0, 0, null, err) return@submit diff --git a/valdi/src/java/com/snap/valdi/utils/ValdiImageFactory.kt b/valdi/src/java/com/snap/valdi/utils/ValdiImageFactory.kt index ce90c145..41afe80f 100644 --- a/valdi/src/java/com/snap/valdi/utils/ValdiImageFactory.kt +++ b/valdi/src/java/com/snap/valdi/utils/ValdiImageFactory.kt @@ -25,8 +25,13 @@ object ValdiImageFactory { return createImage(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)) } + @JvmStatic + fun fromBitmap(bitmap: Bitmap): ValdiImage { + return createImage(bitmap) + } + @JvmStatic fun fromFilePath(filePath: String): ValdiImage { return createImage(BitmapFactory.decodeFile(filePath)) } -} \ No newline at end of file +} diff --git a/valdi/src/valdi/android/AndroidAssetLoader.cpp b/valdi/src/valdi/android/AndroidAssetLoader.cpp index 394e1697..5b82303f 100644 --- a/valdi/src/valdi/android/AndroidAssetLoader.cpp +++ b/valdi/src/valdi/android/AndroidAssetLoader.cpp @@ -7,9 +7,11 @@ #include "valdi/android/AndroidAssetLoader.hpp" #include "valdi/android/AndroidBitmap.hpp" +#include "valdi/android/AndroidBitmapFactory.hpp" #include "valdi/android/ResourceLoader.hpp" #include "valdi/runtime/Interfaces/IRemoteDownloader.hpp" #include "valdi/runtime/Resources/AssetLoaderCompletion.hpp" +#include "valdi/svg/SVGRenderer.hpp" #include "valdi_core/NativeCancelable.hpp" #include "valdi_core/cpp/Attributes/ImageFilter.hpp" #include "valdi_core/cpp/Resources/LoadedAsset.hpp" @@ -21,8 +23,6 @@ #include "valdi_core/jni/JavaCache.hpp" #include "valdi_core/jni/JavaUtils.hpp" -#include "snap_drawing/cpp/Utils/Image.hpp" - namespace ValdiAndroid { class AndroidLoadedAsset : public Valdi::LoadedAsset, public ValdiAndroid::GlobalRefJavaObjectBase { @@ -120,6 +120,39 @@ static void loadAssetFromBytes(const Valdi::Ref& resourceLoader, reinterpret_cast(completionHandle)); } +static void loadAssetFromBitmap(const Valdi::Ref& resourceLoader, + const Valdi::Ref& bitmap, + int32_t preferredWidth, + int32_t preferredHeight, + const Valdi::Value& attachedData, + const Valdi::Ref& completion) { + auto androidBitmap = Valdi::castOrNull(bitmap); + if (androidBitmap == nullptr) { + completion->onLoadComplete(Valdi::Error("SVG rasterization did not return an Android bitmap")); + return; + } + + auto* completionHandle = Valdi::unsafeBridgeRetain(completion.get()); + + const float* colorMatrixFilter = nullptr; + float blurRadiusFilter = 0.0f; + + auto typedFilter = attachedData.getTypedRef(); + if (typedFilter != nullptr) { + if (!typedFilter->isIdentityColorMatrix()) { + colorMatrixFilter = typedFilter->getColorMatrix(); + } + blurRadiusFilter = typedFilter->getBlurRadius(); + } + + resourceLoader->loadAssetFromBitmap(androidBitmap->getJavaBitmap(), + preferredWidth, + preferredHeight, + colorMatrixFilter, + blurRadiusFilter, + reinterpret_cast(completionHandle)); +} + class AndroidAssetLoaderWithDownloader : public Valdi::AssetLoader { public: AndroidAssetLoaderWithDownloader(const Valdi::Ref& resourceLoader, @@ -155,27 +188,18 @@ class AndroidAssetLoaderWithDownloader : public Valdi::AssetLoader { } auto bytes = result.value(); - if (snap::drawing::Image::isSVG(bytes)) { + if (Valdi::SVGRenderer::isSVG(bytes)) { workerQueue->async( [resourceLoader, preferredWidth, preferredHeight, attachedData, completion, bytes]() { - auto image = snap::drawing::Image::makeFromSVG(bytes, preferredWidth, preferredHeight); - if (!image) { - completion->onLoadComplete(image.moveError()); - return; - } - - auto rasterizedBytes = image.value()->toPNG(); - if (!rasterizedBytes) { - completion->onLoadComplete(rasterizedBytes.moveError()); + auto bitmap = Valdi::SVGRenderer::rasterizeSVG( + bytes, AndroidBitmapFactory::getSharedInstance(), preferredWidth, preferredHeight); + if (!bitmap) { + completion->onLoadComplete(bitmap.moveError()); return; } - loadAssetFromBytes(resourceLoader, - rasterizedBytes.value(), - preferredWidth, - preferredHeight, - attachedData, - completion); + loadAssetFromBitmap( + resourceLoader, bitmap.moveValue(), preferredWidth, preferredHeight, attachedData, completion); }); return; } diff --git a/valdi/src/valdi/android/ResourceLoader.cpp b/valdi/src/valdi/android/ResourceLoader.cpp index 11cd4af5..97011188 100644 --- a/valdi/src/valdi/android/ResourceLoader.cpp +++ b/valdi/src/valdi/android/ResourceLoader.cpp @@ -46,6 +46,7 @@ ResourceLoader::ResourceLoader(JavaEnv env, cls.getMethod("requestPayloadFromURL", _requestPayloadFromURLMethod); cls.getMethod("loadAsset", _loadAssetMethod); cls.getMethod("loadAssetFromBytes", _loadAssetFromBytes); + cls.getMethod("loadAssetFromBitmap", _loadAssetFromBitmap); #ifdef __ANDROID__ _assetManager = AAssetManager_fromJava(JavaEnv::getUnsafeEnv(), _assetManagerJava.get()); @@ -162,4 +163,21 @@ JavaObject ResourceLoader::loadAssetFromBytes(const Valdi::BytesView& bytes, callbackHandle); } +JavaObject ResourceLoader::loadAssetFromBitmap(const JavaObject& bitmap, + int32_t preferredWidth, + int32_t preferredHeight, + const float* colorMatrixFilter, + float blurRadiusFilter, + int64_t callbackHandle) { + auto jColorMatrixFilter = colorMatrixToFloatArray(colorMatrixFilter); + + return _loadAssetFromBitmap.call(_localResourceResolver.toObject(), + bitmap, + preferredWidth, + preferredHeight, + jColorMatrixFilter, + blurRadiusFilter, + callbackHandle); +} + } // namespace ValdiAndroid diff --git a/valdi/src/valdi/android/ResourceLoader.hpp b/valdi/src/valdi/android/ResourceLoader.hpp index ae6e9a9f..60924f5a 100644 --- a/valdi/src/valdi/android/ResourceLoader.hpp +++ b/valdi/src/valdi/android/ResourceLoader.hpp @@ -46,6 +46,13 @@ class ResourceLoader : public Valdi::SharedPtrRefCountable, public Valdi::IResou float blurRadiusFilter, int64_t callbackHandle); + JavaObject loadAssetFromBitmap(const JavaObject& bitmap, + int32_t preferredWidth, + int32_t preferredHeight, + const float* colorMatrixFilter, + float blurRadiusFilter, + int64_t callbackHandle); + private: [[maybe_unused]] AAssetManager* _assetManager = nullptr; GlobalRefJavaObjectBase _localResourceResolver; @@ -56,6 +63,7 @@ class ResourceLoader : public Valdi::SharedPtrRefCountable, public Valdi::IResou JavaMethod _loadAssetMethod; JavaMethod _loadAssetFromBytes; + JavaMethod _loadAssetFromBitmap; bool _shouldDeferModuleLoadToJava; }; diff --git a/valdi/src/valdi/ios/SCValdiAssetLoader.mm b/valdi/src/valdi/ios/SCValdiAssetLoader.mm index 0b930e35..935b5052 100644 --- a/valdi/src/valdi/ios/SCValdiAssetLoader.mm +++ b/valdi/src/valdi/ios/SCValdiAssetLoader.mm @@ -21,9 +21,10 @@ #import "valdi_core/cpp/Resources/LoadedAsset.hpp" #import "valdi_core/SCNValdiCoreCancelable+Private.h" #import "valdi_core/cpp/Attributes/ImageFilter.hpp" +#import "valdi_core/SCValdiIOSBitmapFactory.hpp" #import "valdi/ios/Utils/SCValdiImageFilter.h" -#include "snap_drawing/cpp/Utils/Image.hpp" +#include "valdi/svg/SVGRenderer.hpp" namespace ValdiIOS { @@ -107,22 +108,6 @@ static void handleImageLoadResult(SCValdiImage *image, } } -static Valdi::Result rasterizeSVGToPNGData(const Valdi::BytesView& bytes, - int32_t preferredWidth, - int32_t preferredHeight) { - auto image = snap::drawing::Image::makeFromSVG(bytes, preferredWidth, preferredHeight); - if (!image) { - return image.moveError(); - } - - auto png = image.value()->toPNG(); - if (!png) { - return png.moveError(); - } - - return ValdiIOS::NSDataFromBuffer(png.value()); -} - Valdi::Shared ImageAssetLoaderIOS::loadAsset(const Valdi::Value& requestPayload, int32_t preferredWidth, int32_t preferredHeight, @@ -287,18 +272,24 @@ void onBytesLoaded(const Valdi::Result& result, } auto bytes = result.value(); - if (snap::drawing::Image::isSVG(bytes)) { + if (Valdi::SVGRenderer::isSVG(bytes)) { _workerQueue->async([weakSelf = Valdi::weakRef(this), bytes, preferredWidth, preferredHeight, imageFilter, completion]() { - auto pngData = rasterizeSVGToPNGData(bytes, preferredWidth, preferredHeight); - if (!pngData) { - completion->onLoadComplete(pngData.error()); + auto bitmap = Valdi::SVGRenderer::rasterizeSVG( + bytes, ValdiIOS::getIOSBitmapFactory(), preferredWidth, preferredHeight); + if (!bitmap) { + completion->onLoadComplete(bitmap.error()); + return; + } + + auto imageResult = ValdiIOS::imageFromBitmap(bitmap.moveValue()); + if (!imageResult) { + completion->onLoadComplete(imageResult.error()); return; } - NSError *error = nil; - SCValdiImage *image = [SCValdiImage imageWithData:pngData.value() error:&error]; if (auto strongSelf = weakSelf.lock()) { - handleImageLoadResult(image, error, imageFilter, strongSelf->_workerQueue, completion); + auto imageRef = imageResult.moveValue(); + handleImageLoadResult((SCValdiImage *)imageRef.getValue(), nil, imageFilter, strongSelf->_workerQueue, completion); } }); return; diff --git a/valdi/src/valdi/svg/SVGRenderer.cpp b/valdi/src/valdi/svg/SVGRenderer.cpp new file mode 100644 index 00000000..90019964 --- /dev/null +++ b/valdi/src/valdi/svg/SVGRenderer.cpp @@ -0,0 +1,18 @@ +#include "valdi/svg/SVGRenderer.hpp" + +#include "snap_drawing/cpp/Utils/SVGUtils.hpp" + +namespace Valdi { + +bool SVGRenderer::isSVG(const BytesView& data) { + return snap::drawing::isSVG(data); +} + +Result> SVGRenderer::rasterizeSVG(const BytesView& svgData, + const Ref& bitmapFactory, + int32_t preferredWidth, + int32_t preferredHeight) { + return snap::drawing::rasterizeSVG(svgData, bitmapFactory, preferredWidth, preferredHeight); +} + +} // namespace Valdi diff --git a/valdi/src/valdi/svg/SVGRenderer.hpp b/valdi/src/valdi/svg/SVGRenderer.hpp new file mode 100644 index 00000000..e4204b6b --- /dev/null +++ b/valdi/src/valdi/svg/SVGRenderer.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "valdi_core/cpp/Interfaces/IBitmap.hpp" +#include "valdi_core/cpp/Interfaces/IBitmapFactory.hpp" +#include "valdi_core/cpp/Utils/Bytes.hpp" +#include "valdi_core/cpp/Utils/Result.hpp" + +namespace Valdi { + +class SVGRenderer { +public: + static bool isSVG(const BytesView& data); + + static Result> rasterizeSVG(const BytesView& svgData, + const Ref& bitmapFactory, + int32_t preferredWidth, + int32_t preferredHeight); +}; + +} // namespace Valdi diff --git a/valdi/test/svg/SVGRenderer_tests.cpp b/valdi/test/svg/SVGRenderer_tests.cpp new file mode 100644 index 00000000..7ffc6572 --- /dev/null +++ b/valdi/test/svg/SVGRenderer_tests.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include "TestBitmap.hpp" +#include "snap_drawing/cpp/Utils/SVGUtils.hpp" +#include "valdi/svg/SVGRenderer.hpp" +#include "valdi_core/cpp/Interfaces/IBitmapFactory.hpp" + +namespace { + +Valdi::BytesView bytesFromString(const char* value) { + return Valdi::BytesView(nullptr, reinterpret_cast(value), strlen(value)); +} + +class TrackingBitmapFactory : public Valdi::IBitmapFactory { +public: + Valdi::Result> createBitmap(int width, int height) override { + lastBitmap = Valdi::makeShared(width, height); + return Valdi::Ref(lastBitmap); + } + + Valdi::Ref lastBitmap; +}; + +void expectBitmapIsRed(const Valdi::Ref& bitmap) { + auto info = bitmap->getInfo(); + for (int y = 0; y < info.height; y++) { + for (int x = 0; x < info.width; x++) { + EXPECT_EQ(snap::drawing::Color::red(), bitmap->getPixel(x, y)) << "at (" << x << ", " << y << ")"; + } + } +} + +} // namespace + +TEST(SVGRenderer, delegatesDetectionToSharedSVGUtils) { + auto svg = bytesFromString(" \n"); + auto xml = bytesFromString(""); + auto json = bytesFromString("{\"v\":\"5.7.4\",\"layers\":[]}"); + + EXPECT_TRUE(snap::drawing::isSVG(svg)); + EXPECT_TRUE(Valdi::SVGRenderer::isSVG(svg)); + EXPECT_TRUE(Valdi::SVGRenderer::isSVG(xml)); + EXPECT_FALSE(Valdi::SVGRenderer::isSVG(json)); +} + +TEST(SVGRenderer, reportsInvalidSVG) { + auto factory = Valdi::makeShared(); + + auto result = Valdi::SVGRenderer::rasterizeSVG(bytesFromString("(); + auto svg = bytesFromString( + "" + "" + ""); + + auto result = Valdi::SVGRenderer::rasterizeSVG(svg, factory, 0, 0); + + ASSERT_TRUE(result) << result.error().toString(); + EXPECT_EQ(factory->lastBitmap.get(), result.value().get()); + EXPECT_EQ(12, result.value()->getInfo().width); + EXPECT_EQ(10, result.value()->getInfo().height); + expectBitmapIsRed(factory->lastBitmap); +} + +TEST(SVGRenderer, rasterizesUsingPreferredSize) { + auto factory = Valdi::makeShared(); + auto svg = bytesFromString( + "" + "" + ""); + + auto result = Valdi::SVGRenderer::rasterizeSVG(svg, factory, 24, 20); + + ASSERT_TRUE(result) << result.error().toString(); + EXPECT_EQ(24, result.value()->getInfo().width); + EXPECT_EQ(20, result.value()->getInfo().height); + expectBitmapIsRed(factory->lastBitmap); +} + +TEST(SVGRenderer, preservesAspectRatioWhenOnePreferredDimensionIsProvided) { + auto factory = Valdi::makeShared(); + auto svg = bytesFromString( + "" + "" + ""); + + auto result = Valdi::SVGRenderer::rasterizeSVG(svg, factory, 24, 0); + + ASSERT_TRUE(result) << result.error().toString(); + EXPECT_EQ(24, result.value()->getInfo().width); + EXPECT_EQ(20, result.value()->getInfo().height); + expectBitmapIsRed(factory->lastBitmap); +} diff --git a/valdi_core/BUILD.bazel b/valdi_core/BUILD.bazel index 74dd6b31..835c399f 100644 --- a/valdi_core/BUILD.bazel +++ b/valdi_core/BUILD.bazel @@ -205,6 +205,7 @@ PRIVATE_HEADERS = [ "src/valdi_core/ios/valdi_core/SCValdiObjCConversionUtils.h", "src/valdi_core/ios/valdi_core/SCValdiObjCValue.h", "src/valdi_core/ios/valdi_core/SCValdiObjCValueDelegate.h", + "src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.hpp", "src/valdi_core/ios/valdi_core/SCValdiTouches.h", "src/valdi_core/ios/valdi_core/SCValdiValueUtils.h", "src/valdi_core/ios/valdi_core/ValueFunctionWithSCValdiFunction.h", @@ -344,8 +345,11 @@ objc_library( ], module_name = "valdi_core", sdk_frameworks = select({ - "@platforms//os:ios": ["UIKit"], - "@platforms//os:macos": [], + "@platforms//os:ios": [ + "CoreGraphics", + "UIKit", + ], + "@platforms//os:macos": ["Foundation"], }), tags = [ "core_target", diff --git a/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.hpp b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.hpp new file mode 100644 index 00000000..5e60f69f --- /dev/null +++ b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.hpp @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __cplusplus + +#import "valdi_core/SCValdiImage.h" +#import "valdi_core/SCValdiObjCConversionUtils.h" +#include "valdi_core/cpp/Interfaces/IBitmap.hpp" +#include "valdi_core/cpp/Interfaces/IBitmapFactory.hpp" +#include "valdi_core/cpp/Utils/Result.hpp" + +namespace ValdiIOS { + +const Valdi::Ref& getIOSBitmapFactory(); + +Valdi::Result imageFromBitmap(const Valdi::Ref& bitmap); + +} // namespace ValdiIOS + +#endif diff --git a/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.mm b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.mm new file mode 100644 index 00000000..f1b5379e --- /dev/null +++ b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiIOSBitmapFactory.mm @@ -0,0 +1,135 @@ +#import "valdi_core/SCValdiIOSBitmapFactory.hpp" + +#import "valdi_core/SCValdiBitmap.h" +#include "valdi_core/cpp/Utils/Shared.hpp" + +#include + +namespace ValdiIOS { + +class IOSBitmap : public Valdi::IBitmap { +public: + explicit IOSBitmap(const Valdi::BitmapInfo& info) : _info(info) { + _bytes = std::malloc(_info.bytesLength()); + } + + ~IOSBitmap() override { + dispose(); + } + + void dispose() override { + if (_bytes != nullptr) { + std::free(_bytes); + _bytes = nullptr; + } + } + + Valdi::BitmapInfo getInfo() const override { + return _info; + } + + void* lockBytes() override { + return _bytes; + } + + void unlockBytes() override {} + +private: + Valdi::BitmapInfo _info; + void* _bytes = nullptr; +}; + +class IOSBitmapFactory : public Valdi::IBitmapFactory { +public: + Valdi::Result> createBitmap(int width, int height) override { + if (width <= 0 || height <= 0) { + return Valdi::Error("Invalid iOS bitmap size"); + } + + auto info = Valdi::BitmapInfo( + width, height, Valdi::ColorType::ColorTypeBGRA8888, Valdi::AlphaType::AlphaTypePremul, width * 4); + auto bitmap = Valdi::makeShared(info); + if (bitmap->lockBytes() == nullptr) { + return Valdi::Error("Failed to allocate iOS bitmap"); + } + + return Valdi::Ref(bitmap); + } +}; + +const Valdi::Ref& getIOSBitmapFactory() { + static const Valdi::Ref kInstance = Valdi::makeShared(); + return kInstance; +} + +static void releaseBitmapProviderData(void* info, const void* /*data*/, size_t /*size*/) { + auto bitmap = Valdi::unsafeBridgeTransfer(info); + bitmap->unlockBytes(); +} + +Valdi::Result imageFromBitmap(const Valdi::Ref& bitmap) { + if (bitmap == nullptr) { + return Valdi::Error("Cannot create iOS image from a null bitmap"); + } + + auto bitmapInfo = bitmap->getInfo(); + if (bitmapInfo.colorType != Valdi::ColorType::ColorTypeBGRA8888 && + bitmapInfo.colorType != Valdi::ColorType::ColorTypeRGBA8888) { + return Valdi::Error("Unsupported iOS bitmap color type"); + } + + auto* bytes = bitmap->lockBytes(); + if (bytes == nullptr) { + return Valdi::Error("Failed to lock iOS bitmap bytes"); + } + + auto* retainedBitmap = Valdi::unsafeBridgeRetain(bitmap.get()); + CGDataProviderRef dataProvider = + CGDataProviderCreateWithData(retainedBitmap, bytes, bitmapInfo.bytesLength(), releaseBitmapProviderData); + if (dataProvider == nullptr) { + bitmap->unlockBytes(); + Valdi::unsafeBridgeRelease(retainedBitmap); + return Valdi::Error("Failed to create iOS image data provider"); + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + if (colorSpace == nullptr) { + CGDataProviderRelease(dataProvider); + return Valdi::Error("Failed to create iOS image color space"); + } + + auto bitsPerPixel = Valdi::BitmapInfo::bytesPerPixelForColorType(bitmapInfo.colorType) * 8; + CGImageRef cgImage = CGImageCreate(bitmapInfo.width, + bitmapInfo.height, + 8, + bitsPerPixel, + bitmapInfo.rowBytes, + colorSpace, + CGBitmapInfoFromValdiBitmapInfoCpp(bitmapInfo), + dataProvider, + nullptr, + false, + kCGRenderingIntentDefault); + CGColorSpaceRelease(colorSpace); + CGDataProviderRelease(dataProvider); + + if (cgImage == nullptr) { + return Valdi::Error("Failed to create iOS CGImage"); + } + + UIImage* uiImage = [UIImage imageWithCGImage:cgImage]; + CGImageRelease(cgImage); + + if (uiImage == nil) { + return Valdi::Error("Failed to create iOS UIImage"); + } + + SCValdiImage* image = [SCValdiImage imageWithUIImage:uiImage]; + if (image == nil) { + return Valdi::Error("Failed to create iOS Valdi image"); + } + + return ObjCObjectDirectRef(image); +} + +} // namespace ValdiIOS diff --git a/valdi_core/src/valdi_core/ios/valdi_core/SCValdiObjCConversionUtils.mm b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiObjCConversionUtils.mm index bd074b54..c73157bf 100644 --- a/valdi_core/src/valdi_core/ios/valdi_core/SCValdiObjCConversionUtils.mm +++ b/valdi_core/src/valdi_core/ios/valdi_core/SCValdiObjCConversionUtils.mm @@ -144,6 +144,8 @@ + (instancetype)objectWithRef:(Valdi::Ref)ref ObjCObjectDirectRef::ObjCObjectDirectRef(id value): _value(value) {} +ObjCObjectDirectRef::ObjCObjectDirectRef(const ObjCObjectDirectRef &other): _value(other._value) {} + ObjCObjectDirectRef::ObjCObjectDirectRef(ObjCObjectDirectRef &&other): _value(other._value) { other._value = nil; } From add79ff0392f38b1a5671ff583f12aef0f7bf332 Mon Sep 17 00:00:00 2001 From: scorsin-oai Date: Mon, 4 May 2026 14:48:08 -0500 Subject: [PATCH 3/6] Building resvg from source (#64) This change builds the resvg library from source, which will make it possible to use it on iOS and Android (right now we precompile the binaries for macOS and Linux only). - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Documentation improvement - [ ] Performance optimization - [ ] Test improvement - [ ] Other (please describe) - [ ] Tests pass locally (`bazel test //...`) - [ ] Added/updated tests for changes (if applicable) - [ ] Tested on multiple platforms (iOS/Android/Web/macOS as applicable) - [ ] Manual testing performed (describe below) - [ ] Code follows project style guidelines - [ ] Documentation updated (if needed) - [ ] No breaking changes (or documented in description) - [ ] Commit messages follow [conventional format](../CONTRIBUTING.md#commit-messages) - [ ] No secrets, API keys, or internal URLs included (cherry picked from commit e29161e0cf8be8dccdee431f8ea169782932268f) --- MODULE.bazel | 55 +- bzl/dependencies.bzl | 18 +- bzl/workspace_postinit.bzl | 2 + bzl/workspace_rust_init.bzl | 43 + .../src/image_toolbox/SVGRenderer.cpp | 12 +- third-party/resvg/cargo-bazel-lock.json | 1860 +++++++++++++++++ third-party/resvg/resvg.BUILD | 62 +- third-party/resvg/resvg_libs/BUILD.bazel | 9 - third-party/resvg/resvg_libs/MODULE.bazel | 4 - third-party/resvg/resvg_libs/WORKSPACE | 0 .../libs/Linux/x86_64/lib/libresvg.a | 3 - .../libs/Macos/armv8/lib/libresvg.a | 3 - .../libs/Macos/x86_64/lib/libresvg.a | 3 - 13 files changed, 2024 insertions(+), 50 deletions(-) create mode 100644 bzl/workspace_rust_init.bzl create mode 100644 third-party/resvg/cargo-bazel-lock.json delete mode 100644 third-party/resvg/resvg_libs/BUILD.bazel delete mode 100644 third-party/resvg/resvg_libs/MODULE.bazel delete mode 100644 third-party/resvg/resvg_libs/WORKSPACE delete mode 100644 third-party/resvg/resvg_libs/libs/Linux/x86_64/lib/libresvg.a delete mode 100644 third-party/resvg/resvg_libs/libs/Macos/armv8/lib/libresvg.a delete mode 100644 third-party/resvg/resvg_libs/libs/Macos/x86_64/lib/libresvg.a diff --git a/MODULE.bazel b/MODULE.bazel index 95969d46..e1eac7d9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -28,6 +28,7 @@ single_version_override( bazel_dep(name = "bazel_skylib", version = "1.2.0") bazel_dep(name = "rules_java", version = "7.4.0") +bazel_dep(name = "rules_rust", version = "0.64.0") bazel_dep(name = "rules_android", version = "0.6.5") bazel_dep(name = "rules_android_ndk", version = "0.1.3") @@ -200,12 +201,6 @@ local_path_override( path = "bin", ) -bazel_dep(name = "resvg_libs") -local_path_override( - module_name = "resvg_libs", - path = "third-party/resvg/resvg_libs", -) - bazel_dep(name = "jsoncpp", version = "1.9.6") http_archive( @@ -259,11 +254,53 @@ http_archive( http_archive( name = "resvg", - url = "https://github.com/RazrFalcon/resvg/archive/a739aef5d01360ec238c886bc50674f31458df00.zip", + url = "https://github.com/linebender/resvg/archive/b3c7f58d059da6aa0a25141b1948c61b8c579c12.zip", build_file = "@valdi//third-party/resvg:resvg.BUILD", - strip_prefix = "resvg-a739aef5d01360ec238c886bc50674f31458df00", - integrity = "sha256-kohUhIYyFoaeIHLBhYwq6g7+h34r3i9/IIQoWyJAmSE=", + strip_prefix = "resvg-b3c7f58d059da6aa0a25141b1948c61b8c579c12", + integrity = "sha256-0F/rzIZh2AQW3zyJ8S0c269xlbCvn8rFSAI2r0jLdgU=", +) + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2024", + extra_target_triples = ["aarch64-linux-android"], + versions = ["1.87.0"], +) +use_repo(rust, "rust_toolchains") +register_toolchains("@rust_toolchains//:all") + +resvg_crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +resvg_crate.spec(package = "base64", version = "0.22.1") +resvg_crate.spec(package = "data-url", version = "0.3.2") +resvg_crate.spec(package = "flate2", default_features = False, features = ["rust_backend"], version = "1.1.5") +resvg_crate.spec(package = "imagesize", version = "0.14.0") +resvg_crate.spec(package = "kurbo", version = "0.13.0") +resvg_crate.spec(package = "log", version = "0.4.29") +resvg_crate.spec(package = "pico-args", features = ["eq-separator"], version = "0.5.0") +resvg_crate.spec(package = "rgb", version = "0.8.52") +resvg_crate.spec(package = "roxmltree", version = "0.21.1") +resvg_crate.spec(package = "simplecss", version = "0.2.2") +resvg_crate.spec(package = "siphasher", version = "1.0.1") +resvg_crate.spec(package = "strict-num", version = "0.1.1") +resvg_crate.spec(package = "svgtypes", version = "0.16.1") +resvg_crate.spec(package = "tiny-skia", version = "0.12.0") +resvg_crate.spec(package = "tiny-skia-path", version = "0.12.0") +resvg_crate.spec(package = "xmlwriter", version = "0.1.0") +resvg_crate.render_config( + default_package_name = "", + repositories = ["resvg_crates"], +) +resvg_crate.from_specs( + name = "resvg_crates", + lockfile = "//third-party/resvg:cargo-bazel-lock.json", + supported_platform_triples = [ + "aarch64-apple-darwin", + "aarch64-linux-android", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + ], ) +use_repo(resvg_crate, "resvg_crates") bazel_dep(name = "rules_license", version = "1.0.0") diff --git a/bzl/dependencies.bzl b/bzl/dependencies.bzl index e3fa4af1..0d219dad 100644 --- a/bzl/dependencies.bzl +++ b/bzl/dependencies.bzl @@ -263,12 +263,6 @@ def setup_dependencies(workspace_root = None): path = "/bin", ) - local_or_nested_repository( - workspace_root = workspace_root, - name = "resvg_libs", - path = "/third-party/resvg/resvg_libs", - ) - # From https://github.com/open-source-parsers/jsoncpp/releases/tag/1.8.0 http_archive( name = "jsoncpp", @@ -429,6 +423,12 @@ def setup_dependencies(workspace_root = None): url = "https://github.com/bazel-contrib/bazel-lib/releases/download/v2.21.2/bazel-lib-v2.21.2.tar.gz", ) + http_archive( + name = "rules_rust", + integrity = "sha256-2GH766nwQzOgrmnkSO6D1pF/JC3bt/41xo/CEqarpUY=", + urls = ["https://github.com/bazelbuild/rules_rust/releases/download/0.64.0/rules_rust-0.64.0.tar.gz"], + ) + # fuzzing rules directly copied from https://github.com/bazelbuild/rules_fuzzing/tree/1dbcd9167300ad226d29972f5f9c925d6d81f441?tab=readme-ov-file#configuring-the-workspace http_archive( name = "rules_fuzzing", @@ -446,10 +446,10 @@ def setup_dependencies(workspace_root = None): http_archive( name = "resvg", - url = "https://github.com/RazrFalcon/resvg/archive/a739aef5d01360ec238c886bc50674f31458df00.zip", + url = "https://github.com/linebender/resvg/archive/b3c7f58d059da6aa0a25141b1948c61b8c579c12.zip", build_file = "@valdi//third-party/resvg:resvg.BUILD", - strip_prefix = "resvg-a739aef5d01360ec238c886bc50674f31458df00", - integrity = "sha256-kohUhIYyFoaeIHLBhYwq6g7+h34r3i9/IIQoWyJAmSE=", + strip_prefix = "resvg-b3c7f58d059da6aa0a25141b1948c61b8c579c12", + integrity = "sha256-0F/rzIZh2AQW3zyJ8S0c269xlbCvn8rFSAI2r0jLdgU=", ) http_archive( diff --git a/bzl/workspace_postinit.bzl b/bzl/workspace_postinit.bzl index cdf63104..a97c0093 100644 --- a/bzl/workspace_postinit.bzl +++ b/bzl/workspace_postinit.bzl @@ -1,6 +1,8 @@ load("@llvm_toolchain//:toolchains.bzl", "llvm_register_toolchains") +load("@resvg_crates//:defs.bzl", resvg_crates = "crate_repositories") load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") def valdi_post_initialize_workspace(): llvm_register_toolchains() + resvg_crates() rules_jvm_external_setup() diff --git a/bzl/workspace_rust_init.bzl b/bzl/workspace_rust_init.bzl new file mode 100644 index 00000000..5cd4e245 --- /dev/null +++ b/bzl/workspace_rust_init.bzl @@ -0,0 +1,43 @@ +load("@rules_rust//crate_universe:defs.bzl", "crate", "crates_repository", "render_config") +load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies") +load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains") + +def valdi_initialize_rust_workspace(): + rules_rust_dependencies() + rust_register_toolchains( + edition = "2024", + extra_target_triples = ["aarch64-linux-android"], + versions = ["1.87.0"], + ) + + crate_universe_dependencies() + crates_repository( + name = "resvg_crates", + cargo_lockfile = "@resvg//:Cargo.lock", + lockfile = "@valdi//third-party/resvg:cargo-bazel-lock.json", + packages = { + "base64": crate.spec(version = "0.22.1"), + "data-url": crate.spec(version = "0.3.2"), + "flate2": crate.spec(default_features = False, features = ["rust_backend"], version = "1.1.5"), + "imagesize": crate.spec(version = "0.14.0"), + "kurbo": crate.spec(version = "0.13.0"), + "log": crate.spec(version = "0.4.29"), + "pico-args": crate.spec(features = ["eq-separator"], version = "0.5.0"), + "rgb": crate.spec(version = "0.8.52"), + "roxmltree": crate.spec(version = "0.21.1"), + "simplecss": crate.spec(version = "0.2.2"), + "siphasher": crate.spec(version = "1.0.1"), + "strict-num": crate.spec(version = "0.1.1"), + "svgtypes": crate.spec(version = "0.16.1"), + "tiny-skia": crate.spec(version = "0.12.0"), + "tiny-skia-path": crate.spec(version = "0.12.0"), + "xmlwriter": crate.spec(version = "0.1.0"), + }, + render_config = render_config(default_package_name = ""), + supported_platform_triples = [ + "aarch64-apple-darwin", + "aarch64-linux-android", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + ], + ) diff --git a/libs/image_toolbox/src/image_toolbox/SVGRenderer.cpp b/libs/image_toolbox/src/image_toolbox/SVGRenderer.cpp index a8bbfe4e..57938330 100644 --- a/libs/image_toolbox/src/image_toolbox/SVGRenderer.cpp +++ b/libs/image_toolbox/src/image_toolbox/SVGRenderer.cpp @@ -120,14 +120,14 @@ Valdi::Result> SVGRenderer::render(cons auto* outputBytes = outputBitmap->lockBytes(); - resvg_fit_to fitTo; - fitTo.type = RESVG_FIT_TO_TYPE_ZOOM; - fitTo.value = std::max(static_cast(surfaceWidth) / static_cast(svgSize.first), - static_cast(surfaceHeight) / static_cast(svgSize.second)); + auto transform = resvg_transform_identity(); + auto scale = std::max(static_cast(surfaceWidth) / static_cast(svgSize.first), + static_cast(surfaceHeight) / static_cast(svgSize.second)); + transform.a = scale; + transform.d = scale; resvg_render(result.value()->tree(), - fitTo, - resvg_transform_identity(), + transform, static_cast(surfaceWidth), static_cast(surfaceHeight), reinterpret_cast(outputBytes)); diff --git a/third-party/resvg/cargo-bazel-lock.json b/third-party/resvg/cargo-bazel-lock.json new file mode 100644 index 00000000..15a39de7 --- /dev/null +++ b/third-party/resvg/cargo-bazel-lock.json @@ -0,0 +1,1860 @@ +{ + "checksum": "06d04385a4c9a860653db05ed854f5edcc0097e19987df72ed0ade7c1fc35ab0", + "crates": { + "adler2 2.0.1": { + "name": "adler2", + "version": "2.0.1", + "package_url": "https://github.com/oyvindln/adler2", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/adler2/2.0.1/download", + "sha256": "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + } + }, + "targets": [ + { + "Library": { + "crate_name": "adler2", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "adler2", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "2.0.1" + }, + "license": "0BSD OR MIT OR Apache-2.0", + "license_ids": [ + "0BSD", + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-0BSD" + }, + "arrayref 0.3.9": { + "name": "arrayref", + "version": "0.3.9", + "package_url": "https://github.com/droundy/arrayref", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/arrayref/0.3.9/download", + "sha256": "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "arrayref", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "arrayref", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.3.9" + }, + "license": "BSD-2-Clause", + "license_ids": [ + "BSD-2-Clause" + ], + "license_file": "LICENSE" + }, + "arrayvec 0.7.6": { + "name": "arrayvec", + "version": "0.7.6", + "package_url": "https://github.com/bluss/arrayvec", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/arrayvec/0.7.6/download", + "sha256": "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + } + }, + "targets": [ + { + "Library": { + "crate_name": "arrayvec", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "arrayvec", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "0.7.6" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "autocfg 1.5.0": { + "name": "autocfg", + "version": "1.5.0", + "package_url": "https://github.com/cuviper/autocfg", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/autocfg/1.5.0/download", + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "autocfg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "autocfg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.5.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "base64 0.22.1": { + "name": "base64", + "version": "0.22.1", + "package_url": "https://github.com/marshallpierce/rust-base64", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/base64/0.22.1/download", + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + } + }, + "targets": [ + { + "Library": { + "crate_name": "base64", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "base64", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.22.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "bitflags 2.10.0": { + "name": "bitflags", + "version": "2.10.0", + "package_url": "https://github.com/bitflags/bitflags", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bitflags/2.10.0/download", + "sha256": "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bitflags", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bitflags", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "2.10.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "bytemuck 1.24.0": { + "name": "bytemuck", + "version": "1.24.0", + "package_url": "https://github.com/Lokathor/bytemuck", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bytemuck/1.24.0/download", + "sha256": "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bytemuck", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bytemuck", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "aarch64_simd" + ], + "selects": {} + }, + "edition": "2018", + "version": "1.24.0" + }, + "license": "Zlib OR Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT", + "Zlib" + ], + "license_file": "LICENSE-APACHE" + }, + "cfg-if 1.0.4": { + "name": "cfg-if", + "version": "1.0.4", + "package_url": "https://github.com/rust-lang/cfg-if", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cfg-if/1.0.4/download", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cfg_if", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cfg_if", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.0.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crc32fast 1.5.0": { + "name": "crc32fast", + "version": "1.5.0", + "package_url": "https://github.com/srijs/rust-crc32fast", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crc32fast/1.5.0/download", + "sha256": "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crc32fast", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crc32fast", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "crc32fast 1.5.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.5.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "data-url 0.3.2": { + "name": "data-url", + "version": "0.3.2", + "package_url": "https://github.com/servo/rust-url", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/data-url/0.3.2/download", + "sha256": "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + } + }, + "targets": [ + { + "Library": { + "crate_name": "data_url", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "data_url", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.3.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "direct-cargo-bazel-deps 0.0.1": { + "name": "direct-cargo-bazel-deps", + "version": "0.0.1", + "package_url": null, + "repository": null, + "targets": [ + { + "Library": { + "crate_name": "direct_cargo_bazel_deps", + "crate_root": ".direct_cargo_bazel_deps.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "direct_cargo_bazel_deps", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "base64 0.22.1", + "target": "base64" + }, + { + "id": "data-url 0.3.2", + "target": "data_url" + }, + { + "id": "flate2 1.1.5", + "target": "flate2" + }, + { + "id": "imagesize 0.14.0", + "target": "imagesize" + }, + { + "id": "kurbo 0.13.0", + "target": "kurbo" + }, + { + "id": "log 0.4.29", + "target": "log" + }, + { + "id": "pico-args 0.5.0", + "target": "pico_args" + }, + { + "id": "rgb 0.8.52", + "target": "rgb" + }, + { + "id": "roxmltree 0.21.1", + "target": "roxmltree" + }, + { + "id": "simplecss 0.2.2", + "target": "simplecss" + }, + { + "id": "siphasher 1.0.1", + "target": "siphasher" + }, + { + "id": "strict-num 0.1.1", + "target": "strict_num" + }, + { + "id": "svgtypes 0.16.1", + "target": "svgtypes" + }, + { + "id": "tiny-skia 0.12.0", + "target": "tiny_skia" + }, + { + "id": "tiny-skia-path 0.12.0", + "target": "tiny_skia_path" + }, + { + "id": "xmlwriter 0.1.0", + "target": "xmlwriter" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.0.1" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "euclid 0.22.11": { + "name": "euclid", + "version": "0.22.11", + "package_url": "https://github.com/servo/euclid", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/euclid/0.22.11/download", + "sha256": "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" + } + }, + "targets": [ + { + "Library": { + "crate_name": "euclid", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "euclid", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.22.11" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "fdeflate 0.3.7": { + "name": "fdeflate", + "version": "0.3.7", + "package_url": "https://github.com/image-rs/fdeflate", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/fdeflate/0.3.7/download", + "sha256": "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "fdeflate", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "fdeflate", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "simd-adler32 0.3.8", + "target": "simd_adler32" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.3.7" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "flate2 1.1.5": { + "name": "flate2", + "version": "1.1.5", + "package_url": "https://github.com/rust-lang/flate2-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/flate2/1.1.5/download", + "sha256": "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "flate2", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "flate2", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "any_impl", + "default", + "miniz_oxide", + "rust_backend" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crc32fast 1.5.0", + "target": "crc32fast" + }, + { + "id": "miniz_oxide 0.8.9", + "target": "miniz_oxide" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.1.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "float-cmp 0.9.0": { + "name": "float-cmp", + "version": "0.9.0", + "package_url": "https://github.com/mikedilger/float-cmp", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/float-cmp/0.9.0/download", + "sha256": "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "float_cmp", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "float_cmp", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.9.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "imagesize 0.14.0": { + "name": "imagesize", + "version": "0.14.0", + "package_url": "https://github.com/Roughsketch/imagesize", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/imagesize/0.14.0/download", + "sha256": "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "imagesize", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "imagesize", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "aesprite", + "bmp", + "dds", + "default", + "exr", + "farbfeld", + "gif", + "hdr", + "heif", + "ico", + "ilbm", + "jpeg", + "jxl", + "ktx2", + "mod", + "png", + "pnm", + "psd", + "qoi", + "tga", + "tiff", + "vtf", + "webp" + ], + "selects": {} + }, + "edition": "2021", + "version": "0.14.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "kurbo 0.13.0": { + "name": "kurbo", + "version": "0.13.0", + "package_url": "https://github.com/linebender/kurbo", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/kurbo/0.13.0/download", + "sha256": "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "kurbo", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "kurbo", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "arrayvec 0.7.6", + "target": "arrayvec" + }, + { + "id": "smallvec 1.15.1", + "target": "smallvec" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.13.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "log 0.4.29": { + "name": "log", + "version": "0.4.29", + "package_url": "https://github.com/rust-lang/log", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/log/0.4.29/download", + "sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + } + }, + "targets": [ + { + "Library": { + "crate_name": "log", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "log", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "0.4.29" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "memchr 2.7.6": { + "name": "memchr", + "version": "2.7.6", + "package_url": "https://github.com/BurntSushi/memchr", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/memchr/2.7.6/download", + "sha256": "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + } + }, + "targets": [ + { + "Library": { + "crate_name": "memchr", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "memchr", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "2.7.6" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "miniz_oxide 0.8.9": { + "name": "miniz_oxide", + "version": "0.8.9", + "package_url": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/miniz_oxide/0.8.9/download", + "sha256": "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" + } + }, + "targets": [ + { + "Library": { + "crate_name": "miniz_oxide", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "miniz_oxide", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "simd", + "simd-adler32", + "with-alloc" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "adler2 2.0.1", + "target": "adler2" + }, + { + "id": "simd-adler32 0.3.8", + "target": "simd_adler32" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.9" + }, + "license": "MIT OR Zlib OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT", + "Zlib" + ], + "license_file": "LICENSE" + }, + "num-traits 0.2.19": { + "name": "num-traits", + "version": "0.2.19", + "package_url": "https://github.com/rust-num/num-traits", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/num-traits/0.2.19/download", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + }, + "targets": [ + { + "Library": { + "crate_name": "num_traits", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "num_traits", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "autocfg 1.5.0", + "target": "autocfg" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "pico-args 0.5.0": { + "name": "pico-args", + "version": "0.5.0", + "package_url": "https://github.com/RazrFalcon/pico-args", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/pico-args/0.5.0/download", + "sha256": "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + } + }, + "targets": [ + { + "Library": { + "crate_name": "pico_args", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "pico_args", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "eq-separator" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.5.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "png 0.18.0": { + "name": "png", + "version": "0.18.0", + "package_url": "https://github.com/image-rs/image-png", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/png/0.18.0/download", + "sha256": "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" + } + }, + "targets": [ + { + "Library": { + "crate_name": "png", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "png", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "bitflags 2.10.0", + "target": "bitflags" + }, + { + "id": "crc32fast 1.5.0", + "target": "crc32fast" + }, + { + "id": "fdeflate 0.3.7", + "target": "fdeflate" + }, + { + "id": "flate2 1.1.5", + "target": "flate2" + }, + { + "id": "miniz_oxide 0.8.9", + "target": "miniz_oxide" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.18.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rgb 0.8.52": { + "name": "rgb", + "version": "0.8.52", + "package_url": "https://github.com/kornelski/rust-rgb", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rgb/0.8.52/download", + "sha256": "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rgb", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rgb", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "argb", + "as-bytes", + "bytemuck", + "default", + "grb" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bytemuck 1.24.0", + "target": "bytemuck" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.52" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "roxmltree 0.21.1": { + "name": "roxmltree", + "version": "0.21.1", + "package_url": "https://github.com/RazrFalcon/roxmltree", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/roxmltree/0.21.1/download", + "sha256": "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "roxmltree", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "roxmltree", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "positions", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "memchr 2.7.6", + "target": "memchr" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.21.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "simd-adler32 0.3.8": { + "name": "simd-adler32", + "version": "0.3.8", + "package_url": "https://github.com/mcountryman/simd-adler32", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simd-adler32/0.3.8/download", + "sha256": "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simd_adler32", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simd_adler32", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "const-generics", + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.3.8" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE.md" + }, + "simplecss 0.2.2": { + "name": "simplecss", + "version": "0.2.2", + "package_url": "https://github.com/linebender/simplecss", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simplecss/0.2.2/download", + "sha256": "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simplecss", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simplecss", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "log 0.4.29", + "target": "log" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.2" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "siphasher 1.0.1": { + "name": "siphasher", + "version": "1.0.1", + "package_url": "https://github.com/jedisct1/rust-siphash", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/siphasher/1.0.1/download", + "sha256": "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "siphasher", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "siphasher", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "1.0.1" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, + "smallvec 1.15.1": { + "name": "smallvec", + "version": "1.15.1", + "package_url": "https://github.com/servo/rust-smallvec", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/smallvec/1.15.1/download", + "sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + } + }, + "targets": [ + { + "Library": { + "crate_name": "smallvec", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "smallvec", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.15.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "strict-num 0.1.1": { + "name": "strict-num", + "version": "0.1.1", + "package_url": "https://github.com/RazrFalcon/strict-num", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/strict-num/0.1.1/download", + "sha256": "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + } + }, + "targets": [ + { + "Library": { + "crate_name": "strict_num", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "strict_num", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "approx-eq", + "default", + "float-cmp" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "float-cmp 0.9.0", + "target": "float_cmp" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.1" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "svgtypes 0.16.1": { + "name": "svgtypes", + "version": "0.16.1", + "package_url": "https://github.com/linebender/svgtypes", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/svgtypes/0.16.1/download", + "sha256": "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "svgtypes", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "svgtypes", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "kurbo 0.13.0", + "target": "kurbo" + }, + { + "id": "siphasher 1.0.1", + "target": "siphasher" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.16.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "tiny-skia 0.12.0": { + "name": "tiny-skia", + "version": "0.12.0", + "package_url": "https://github.com/linebender/tiny-skia", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tiny-skia/0.12.0/download", + "sha256": "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tiny_skia", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tiny_skia", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "png-format", + "simd", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "arrayref 0.3.9", + "target": "arrayref" + }, + { + "id": "arrayvec 0.7.6", + "target": "arrayvec" + }, + { + "id": "bytemuck 1.24.0", + "target": "bytemuck" + }, + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "log 0.4.29", + "target": "log" + }, + { + "id": "png 0.18.0", + "target": "png" + }, + { + "id": "tiny-skia-path 0.12.0", + "target": "tiny_skia_path" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.12.0" + }, + "license": "BSD-3-Clause", + "license_ids": [ + "BSD-3-Clause" + ], + "license_file": "LICENSE" + }, + "tiny-skia-path 0.12.0": { + "name": "tiny-skia-path", + "version": "0.12.0", + "package_url": "https://github.com/linebender/tiny-skia/tree/master/path", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tiny-skia-path/0.12.0/download", + "sha256": "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tiny_skia_path", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tiny_skia_path", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "arrayref 0.3.9", + "target": "arrayref" + }, + { + "id": "bytemuck 1.24.0", + "target": "bytemuck" + }, + { + "id": "strict-num 0.1.1", + "target": "strict_num" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.12.0" + }, + "license": "BSD-3-Clause", + "license_ids": [ + "BSD-3-Clause" + ], + "license_file": "LICENSE" + }, + "xmlwriter 0.1.0": { + "name": "xmlwriter", + "version": "0.1.0", + "package_url": "https://github.com/RazrFalcon/xmlwriter", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/xmlwriter/0.1.0/download", + "sha256": "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "xmlwriter", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "xmlwriter", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "0.1.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + } + }, + "binary_crates": [], + "workspace_members": { + "direct-cargo-bazel-deps 0.0.1": "" + }, + "conditions": { + "aarch64-apple-darwin": [ + "aarch64-apple-darwin" + ], + "aarch64-linux-android": [ + "aarch64-linux-android" + ], + "x86_64-apple-darwin": [ + "x86_64-apple-darwin" + ], + "x86_64-unknown-linux-gnu": [ + "x86_64-unknown-linux-gnu" + ] + }, + "direct_deps": [ + "base64 0.22.1", + "data-url 0.3.2", + "flate2 1.1.5", + "imagesize 0.14.0", + "kurbo 0.13.0", + "log 0.4.29", + "pico-args 0.5.0", + "rgb 0.8.52", + "roxmltree 0.21.1", + "simplecss 0.2.2", + "siphasher 1.0.1", + "strict-num 0.1.1", + "svgtypes 0.16.1", + "tiny-skia 0.12.0", + "tiny-skia-path 0.12.0", + "xmlwriter 0.1.0" + ], + "direct_dev_deps": [], + "unused_patches": [] +} diff --git a/third-party/resvg/resvg.BUILD b/third-party/resvg/resvg.BUILD index 03c2b723..382dd526 100644 --- a/third-party/resvg/resvg.BUILD +++ b/third-party/resvg/resvg.BUILD @@ -1,8 +1,62 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_static_library") + +rust_library( + name = "usvg", + srcs = glob(["crates/usvg/src/**/*.rs"], exclude = ["crates/usvg/src/main.rs"]), + crate_name = "usvg", + crate_root = "crates/usvg/src/lib.rs", + edition = "2024", + deps = [ + "@resvg_crates//:base64", + "@resvg_crates//:data-url", + "@resvg_crates//:flate2", + "@resvg_crates//:imagesize", + "@resvg_crates//:kurbo", + "@resvg_crates//:log", + "@resvg_crates//:pico-args", + "@resvg_crates//:roxmltree", + "@resvg_crates//:simplecss", + "@resvg_crates//:siphasher", + "@resvg_crates//:strict-num", + "@resvg_crates//:svgtypes", + "@resvg_crates//:tiny-skia-path", + "@resvg_crates//:xmlwriter", + ], +) + +rust_library( + name = "resvg_rust", + srcs = glob(["crates/resvg/src/**/*.rs"], exclude = ["crates/resvg/src/main.rs"]), + crate_name = "resvg", + crate_root = "crates/resvg/src/lib.rs", + edition = "2024", + deps = [ + ":usvg", + "@resvg_crates//:log", + "@resvg_crates//:pico-args", + "@resvg_crates//:rgb", + "@resvg_crates//:svgtypes", + "@resvg_crates//:tiny-skia", + ], +) + +rust_static_library( + name = "resvg_static", + srcs = ["crates/c-api/lib.rs"], + crate_name = "resvg", + crate_root = "crates/c-api/lib.rs", + edition = "2024", + deps = [ + ":resvg_rust", + "@resvg_crates//:log", + ], +) + cc_library( name = "resvg", - srcs = [], - hdrs = ["c-api/resvg.h"], - include_prefix = "resvg", + hdrs = ["crates/c-api/resvg.h"], + include_prefix = "resvg/c-api", + strip_include_prefix = "crates/c-api", visibility = ["//visibility:public"], - deps = ["@resvg_libs"], + deps = [":resvg_static"], ) diff --git a/third-party/resvg/resvg_libs/BUILD.bazel b/third-party/resvg/resvg_libs/BUILD.bazel deleted file mode 100644 index 9efd7a76..00000000 --- a/third-party/resvg/resvg_libs/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -cc_import( - name = "resvg_libs", - static_library = select({ - "@valdi//bzl/conditions:macos_arm64": "libs/Macos/armv8/lib/libresvg.a", - "@valdi//bzl/conditions:macos_x86_64": "libs/Macos/x86_64/lib/libresvg.a", - "@snap_platforms//conditions:linux": "libs/Linux/x86_64/lib/libresvg.a", - }), - visibility = ["//visibility:public"], -) diff --git a/third-party/resvg/resvg_libs/MODULE.bazel b/third-party/resvg/resvg_libs/MODULE.bazel deleted file mode 100644 index 28a5a9ac..00000000 --- a/third-party/resvg/resvg_libs/MODULE.bazel +++ /dev/null @@ -1,4 +0,0 @@ -module(name = "resvg_libs") - -bazel_dep(name = "valdi", version = "0.1") -bazel_dep(name = "snap_platforms") diff --git a/third-party/resvg/resvg_libs/WORKSPACE b/third-party/resvg/resvg_libs/WORKSPACE deleted file mode 100644 index e69de29b..00000000 diff --git a/third-party/resvg/resvg_libs/libs/Linux/x86_64/lib/libresvg.a b/third-party/resvg/resvg_libs/libs/Linux/x86_64/lib/libresvg.a deleted file mode 100644 index ef560cf9..00000000 --- a/third-party/resvg/resvg_libs/libs/Linux/x86_64/lib/libresvg.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2af06e0569cfcb978b6f8bee9487f73c40b67d3963adfc1ed4c6d0f025b3a2cb -size 26226654 diff --git a/third-party/resvg/resvg_libs/libs/Macos/armv8/lib/libresvg.a b/third-party/resvg/resvg_libs/libs/Macos/armv8/lib/libresvg.a deleted file mode 100644 index b74eed23..00000000 --- a/third-party/resvg/resvg_libs/libs/Macos/armv8/lib/libresvg.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dda4698fe801ea8fe8c382ec80575b42c5d4c320eba2b2273a35b664282ec11b -size 19524296 diff --git a/third-party/resvg/resvg_libs/libs/Macos/x86_64/lib/libresvg.a b/third-party/resvg/resvg_libs/libs/Macos/x86_64/lib/libresvg.a deleted file mode 100644 index fd24ea65..00000000 --- a/third-party/resvg/resvg_libs/libs/Macos/x86_64/lib/libresvg.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d4b6eda22c46ca912bb5468929f68ed06520b176c7ed629c988d3e8f793c258 -size 19797616 From a76f8f3727069cc84c7b3aa786449fce49ceab27 Mon Sep 17 00:00:00 2001 From: scorsin-oai Date: Tue, 5 May 2026 16:44:28 -0500 Subject: [PATCH 4/6] Fixed android resvg rust build (#71) ReSVG doesn't build on Android armv7 and x86_64. This change fixes it. - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Documentation improvement - [ ] Performance optimization - [ ] Test improvement - [ ] Other (please describe) - [ ] Tests pass locally (`bazel test //...`) - [ ] Added/updated tests for changes (if applicable) - [ ] Tested on multiple platforms (iOS/Android/Web/macOS as applicable) - [ ] Manual testing performed (describe below) - [ ] Code follows project style guidelines - [ ] Documentation updated (if needed) - [ ] No breaking changes (or documented in description) - [ ] Commit messages follow [conventional format](../CONTRIBUTING.md#commit-messages) - [ ] No secrets, API keys, or internal URLs included (cherry picked from commit 8255db95f0658a49f76e08b4497f5dacf201f8fb) --- MODULE.bazel | 12 +++++++++++- bzl/workspace_rust_init.bzl | 12 +++++++++++- third-party/resvg/cargo-bazel-lock.json | 11 ++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index e1eac7d9..480ea8b2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -263,7 +263,14 @@ http_archive( rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") rust.toolchain( edition = "2024", - extra_target_triples = ["aarch64-linux-android"], + extra_target_triples = [ + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-apple-ios", + "x86_64-linux-android", + ], versions = ["1.87.0"], ) use_repo(rust, "rust_toolchains") @@ -296,6 +303,9 @@ resvg_crate.from_specs( supported_platform_triples = [ "aarch64-apple-darwin", "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-apple-ios", + "x86_64-linux-android", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", ], diff --git a/bzl/workspace_rust_init.bzl b/bzl/workspace_rust_init.bzl index 5cd4e245..0be5ca0f 100644 --- a/bzl/workspace_rust_init.bzl +++ b/bzl/workspace_rust_init.bzl @@ -6,7 +6,14 @@ def valdi_initialize_rust_workspace(): rules_rust_dependencies() rust_register_toolchains( edition = "2024", - extra_target_triples = ["aarch64-linux-android"], + extra_target_triples = [ + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-apple-ios", + "x86_64-linux-android", + ], versions = ["1.87.0"], ) @@ -37,6 +44,9 @@ def valdi_initialize_rust_workspace(): supported_platform_triples = [ "aarch64-apple-darwin", "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-apple-ios", + "x86_64-linux-android", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", ], diff --git a/third-party/resvg/cargo-bazel-lock.json b/third-party/resvg/cargo-bazel-lock.json index 15a39de7..138f9cd1 100644 --- a/third-party/resvg/cargo-bazel-lock.json +++ b/third-party/resvg/cargo-bazel-lock.json @@ -1,5 +1,5 @@ { - "checksum": "06d04385a4c9a860653db05ed854f5edcc0097e19987df72ed0ade7c1fc35ab0", + "checksum": "67a51ae2af037351f4d514b8ad1cc478d7d50895963b481743c76a6f2035847d", "crates": { "adler2 2.0.1": { "name": "adler2", @@ -1830,9 +1830,18 @@ "aarch64-linux-android": [ "aarch64-linux-android" ], + "armv7-linux-androideabi": [ + "armv7-linux-androideabi" + ], "x86_64-apple-darwin": [ "x86_64-apple-darwin" ], + "x86_64-apple-ios": [ + "x86_64-apple-ios" + ], + "x86_64-linux-android": [ + "x86_64-linux-android" + ], "x86_64-unknown-linux-gnu": [ "x86_64-unknown-linux-gnu" ] From 3971d1fad568edd4e41089a5e75931030814934e Mon Sep 17 00:00:00 2001 From: scorsin-oai Date: Wed, 6 May 2026 13:23:31 -0500 Subject: [PATCH 5/6] Removed pngquant binary (#74) This PR removes the need for bundling a pngquant binary. We now build pngquant from source inside the compiler toolbox binary. This simplifies the framework packaging by removing an additional binary that we have to carry around. This change was made possible recently because we are now building resvg from source, which is a rust library, and pngquant is in rust too. pngquant is now a rust dependency of compiler toolbox. - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Documentation improvement - [ ] Performance optimization - [ ] Test improvement - [ ] Other (please describe) - [ ] Tests pass locally (`bazel test //...`) - [ ] Added/updated tests for changes (if applicable) - [ ] Tested on multiple platforms (iOS/Android/Web/macOS as applicable) - [ ] Manual testing performed (describe below) - [ ] Code follows project style guidelines - [ ] Documentation updated (if needed) - [ ] No breaking changes (or documented in description) - [ ] Commit messages follow [conventional format](../CONTRIBUTING.md#commit-messages) - [ ] No secrets, API keys, or internal URLs included (cherry picked from commit 184a16b09137196a9b289c71c8436062ac4acd26) --- MODULE.bazel | 32 + bin/BUILD.bazel | 14 - bin/MODULE.bazel | 2 +- bin/compiler/macos/valdi_compiler | 4 +- bzl/additional_dependencies.bzl | 24 - bzl/dependencies.bzl | 8 + bzl/prebuilt_tools.bzl | 10 - bzl/valdi/BUILD.bazel | 1 - bzl/valdi/valdi_compiled.bzl | 3 - bzl/valdi/valdi_config.yaml.tpl | 4 - bzl/valdi/valdi_run_compiler.bzl | 6 - bzl/valdi/valdi_toolchain.bzl | 8 - bzl/valdi_compiler_repos_extension.bzl | 2 - bzl/workspace_postinit.bzl | 2 + bzl/workspace_rust_init.bzl | 22 + .../Sources/Config/ValdiProjectConfig.swift | 13 - .../Sources/Images/ImageConverter.swift | 34 +- .../Sources/Images/ImageToolbox.swift | 7 + .../ToolboxExecutable/ToolboxExecutable.swift | 4 + .../Sources/ValdiCompilerArguments.swift | 3 - scripts/linux_deps_setup.sh | 5 - third-party/pngquant/BUILD.bazel | 5 + third-party/pngquant/Cargo.lock | 241 +++ third-party/pngquant/cargo-bazel-lock.json | 1673 +++++++++++++++++ third-party/pngquant/pngquant.BUILD | 94 + valdi/compiler/toolbox/BUILD.bazel | 1 + .../compiler_toolbox/CompilerToolbox.cpp | 21 + .../src/valdi/compiler_toolbox/PNGQuant.cpp | 53 + .../src/valdi/compiler_toolbox/PNGQuant.hpp | 10 + 29 files changed, 2178 insertions(+), 128 deletions(-) create mode 100644 third-party/pngquant/BUILD.bazel create mode 100644 third-party/pngquant/Cargo.lock create mode 100644 third-party/pngquant/cargo-bazel-lock.json create mode 100644 third-party/pngquant/pngquant.BUILD create mode 100644 valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.cpp create mode 100644 valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.hpp diff --git a/MODULE.bazel b/MODULE.bazel index 480ea8b2..6561de77 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -260,6 +260,14 @@ http_archive( integrity = "sha256-0F/rzIZh2AQW3zyJ8S0c269xlbCvn8rFSAI2r0jLdgU=", ) +http_archive( + name = "pngquant", + url = "https://github.com/kornelski/pngquant/archive/5b4e91f5dd6af27c928474ffd526bb69e17b0f37.zip", + build_file = "@valdi//third-party/pngquant:pngquant.BUILD", + strip_prefix = "pngquant-5b4e91f5dd6af27c928474ffd526bb69e17b0f37", + integrity = "sha256-CqMGdZFDTa+6gyxJN3Wvr6d5O4ezR9xvyBd1tzUNR+8=", +) + rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") rust.toolchain( edition = "2024", @@ -312,6 +320,30 @@ resvg_crate.from_specs( ) use_repo(resvg_crate, "resvg_crates") +pngquant_crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +pngquant_crate.spec(package = "cc", version = "1.0.72") +pngquant_crate.spec(package = "dunce", version = "1.0.4") +pngquant_crate.spec(package = "getopts", version = "0.2.21") +pngquant_crate.spec(package = "imagequant-sys", version = "4.1.0") +pngquant_crate.spec(package = "libc", version = "0.2.112") +pngquant_crate.spec(package = "libpng-sys", version = "1.1.9") +pngquant_crate.spec(package = "libz-sys", version = "1.1.28") +pngquant_crate.spec(package = "wild", version = "2.2.0") +pngquant_crate.render_config( + default_package_name = "", + repositories = ["pngquant_crates"], +) +pngquant_crate.from_specs( + name = "pngquant_crates", + lockfile = "//third-party/pngquant:cargo-bazel-lock.json", + supported_platform_triples = [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + ], +) +use_repo(pngquant_crate, "pngquant_crates") + bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_fuzzing", version = "0.5.2") diff --git a/bin/BUILD.bazel b/bin/BUILD.bazel index 7e5e436e..c13be7d4 100644 --- a/bin/BUILD.bazel +++ b/bin/BUILD.bazel @@ -3,8 +3,6 @@ load("@bazel_skylib//rules:native_binary.bzl", "native_binary") load( "@valdi//bzl:prebuilt_tools.bzl", "bundle_js", - "pngquant_linux", - "pngquant_macos", "valdi_compiler_companion_files", ) @@ -32,18 +30,6 @@ alias( visibility = ["//visibility:public"], ) -native_binary( - name = "pngquant", - src = select( - { - "@bazel_tools//src/conditions:darwin": pngquant_macos(), - "@bazel_tools//src/conditions:linux_x86_64": pngquant_linux(), - }, - ), - out = "pngquant", - visibility = ["//visibility:public"], -) - alias( name = "valdi_standalone", actual = "@valdi//valdi:valdi_standalone", diff --git a/bin/MODULE.bazel b/bin/MODULE.bazel index 9ca69ce7..39a80301 100644 --- a/bin/MODULE.bazel +++ b/bin/MODULE.bazel @@ -9,4 +9,4 @@ bazel_dep(name = "aspect_rules_js", version = "1.37.0") # that workspace_prepare.bzl would normally create are never set up. # This extension creates them and makes them visible to this module. valdi_tools = use_extension("@valdi//bzl:valdi_compiler_repos_extension.bzl", "valdi_compiler_repos") -use_repo(valdi_tools, "valdi_compiler_macos", "valdi_compiler_linux", "valdi_pngquant_macos", "valdi_pngquant_linux", "jscore_libs") \ No newline at end of file +use_repo(valdi_tools, "valdi_compiler_macos", "valdi_compiler_linux", "jscore_libs") diff --git a/bin/compiler/macos/valdi_compiler b/bin/compiler/macos/valdi_compiler index 33e05681..83e4f601 100755 --- a/bin/compiler/macos/valdi_compiler +++ b/bin/compiler/macos/valdi_compiler @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3eb564712e0fe984ccebbca476eca663619fcb95078009f20d83f48d2388890 -size 49159568 +oid sha256:6b94e3663c5c02c642aa229f5b6a42b6cd8f4a0ec0beb042cf30a85fd6e810e7 +size 48635152 diff --git a/bzl/additional_dependencies.bzl b/bzl/additional_dependencies.bzl index 17b06c2d..36d47bb3 100644 --- a/bzl/additional_dependencies.bzl +++ b/bzl/additional_dependencies.bzl @@ -47,18 +47,6 @@ def setup_additional_dependencies(bzlmod = False): build_file_content = SOURCES_FILEGROUP_BUILD_FILE_CONTENT, ) - native.new_local_repository( - name = "valdi_pngquant_macos", - path = "bin/pngquant/macos", - build_file_content = SOURCES_FILEGROUP_BUILD_FILE_CONTENT, - ) - - native.new_local_repository( - name = "valdi_pngquant_linux", - path = "bin/pngquant/linux", - build_file_content = SOURCES_FILEGROUP_BUILD_FILE_CONTENT, - ) - native.new_local_repository( name = "valdi_compiler_companion", path = "bin/compiler_companion", @@ -90,18 +78,6 @@ def setup_additional_dependencies(bzlmod = False): target_dir = "bin/compiler/macos", ) - nested_repository( - name = "valdi_pngquant_macos", - source_repo = "valdi", - target_dir = "bin/pngquant/macos", - ) - - nested_repository( - name = "valdi_pngquant_linux", - source_repo = "valdi", - target_dir = "bin/pngquant/linux", - ) - nested_repository( name = "valdi_compiler_companion", source_repo = "valdi", diff --git a/bzl/dependencies.bzl b/bzl/dependencies.bzl index 0d219dad..bbb44ee5 100644 --- a/bzl/dependencies.bzl +++ b/bzl/dependencies.bzl @@ -452,6 +452,14 @@ def setup_dependencies(workspace_root = None): integrity = "sha256-0F/rzIZh2AQW3zyJ8S0c269xlbCvn8rFSAI2r0jLdgU=", ) + http_archive( + name = "pngquant", + url = "https://github.com/kornelski/pngquant/archive/5b4e91f5dd6af27c928474ffd526bb69e17b0f37.zip", + build_file = "@valdi//third-party/pngquant:pngquant.BUILD", + strip_prefix = "pngquant-5b4e91f5dd6af27c928474ffd526bb69e17b0f37", + integrity = "sha256-CqMGdZFDTa+6gyxJN3Wvr6d5O4ezR9xvyBd1tzUNR+8=", + ) + http_archive( name = "skia", url = "https://github.com/google/skia/archive/8d5c6efb04514a31f09a2e865940f99cdf60ce21.zip", diff --git a/bzl/prebuilt_tools.bzl b/bzl/prebuilt_tools.bzl index fbee746e..6981a940 100644 --- a/bzl/prebuilt_tools.bzl +++ b/bzl/prebuilt_tools.bzl @@ -2,16 +2,6 @@ INTERNAL_BUILD = False -def pngquant_linux(): - if INTERNAL_BUILD: - return "@valdi_pngquant_linux//:pngquant" - return "pngquant/linux/pngquant" - -def pngquant_macos(): - if INTERNAL_BUILD: - return "@valdi_pngquant_macos//:pngquant" - return "pngquant/macos/pngquant" - def valdi_compiler_companion_files(): if INTERNAL_BUILD: return ["@valdi_compiler_companion//:all_files"] diff --git a/bzl/valdi/BUILD.bazel b/bzl/valdi/BUILD.bazel index 202ae7a2..e718fd08 100644 --- a/bzl/valdi/BUILD.bazel +++ b/bzl/valdi/BUILD.bazel @@ -405,7 +405,6 @@ valdi_toolchain( compiler = ":compiler_source", compiler_companion = ":compiler_companion_source", compiler_toolbox = "@valdi_toolchain//:valdi_compiler_toolbox", - pngquant = "@valdi_toolchain//:pngquant", sqldelight_compiler = "@valdi_toolchain//:sqldelight_compiler", ) diff --git a/bzl/valdi/valdi_compiled.bzl b/bzl/valdi/valdi_compiled.bzl index a7a8f748..af955967 100644 --- a/bzl/valdi/valdi_compiled.bzl +++ b/bzl/valdi/valdi_compiled.bzl @@ -2226,7 +2226,6 @@ def _prepare_hotreload_arguments(module_names, config_yaml_file, explicit_input_ companion_bin_wrapper = toolchain.companion.files.to_list()[0] compiler_toolbox = toolchain.compiler_toolbox.files.to_list()[0] - pngquant = toolchain.pngquant.files.to_list()[0] minify_config = toolchain.minify_config.files.to_list()[0] client_sql = toolchain.sqldelight_compiler.files.to_list() @@ -2243,8 +2242,6 @@ def _prepare_hotreload_arguments(module_names, config_yaml_file, explicit_input_ args.append(companion_bin_wrapper.path) args.append("--direct-compiler-toolbox-path") args.append(compiler_toolbox.path) - args.append("--direct-pngquant-path") - args.append(pngquant.path) args.append("--direct-minify-config-path") args.append(minify_config.path) diff --git a/bzl/valdi/valdi_config.yaml.tpl b/bzl/valdi/valdi_config.yaml.tpl index 60c83ea3..7f127c8b 100644 --- a/bzl/valdi/valdi_config.yaml.tpl +++ b/bzl/valdi/valdi_config.yaml.tpl @@ -51,10 +51,6 @@ compiler_toolbox_path: linux: {COMPILER_TOOLBOX_PATH} macos: {COMPILER_TOOLBOX_PATH} -pngquant_bin_path: - linux: {PNGQUANT_PATH} - macos: {PNGQUANT_PATH} - minify_config_path: {MINIFY_CONFIG_PATH} node_modules_dir: {NODE_MODULES_DIR} diff --git a/bzl/valdi/valdi_run_compiler.bzl b/bzl/valdi/valdi_run_compiler.bzl index d5ea3d7f..4e1b50b1 100644 --- a/bzl/valdi/valdi_run_compiler.bzl +++ b/bzl/valdi/valdi_run_compiler.bzl @@ -18,7 +18,6 @@ def generate_config(ctx): companion_path = toolchain.companion.files.to_list()[0].path minify_config_path = toolchain.minify_config.files.to_list()[0].path compiler_toolbox_path = toolchain.compiler_toolbox.files.to_list()[0].path - pngquant_path = toolchain.pngquant.files.to_list()[0].path ctx.actions.expand_template( output = out, @@ -29,7 +28,6 @@ def generate_config(ctx): "{COMPANION_BINARY}": companion_path + "/scripts/run.sh", "{MINIFY_CONFIG_PATH}": "$PWD/" + minify_config_path, "{COMPILER_TOOLBOX_PATH}": "$PWD/" + compiler_toolbox_path, - "{PNGQUANT_PATH}": "$PWD/" + pngquant_path, "{NODE_MODULES_DIR}": NODE_MODULES_BASE, }, ) @@ -42,7 +40,6 @@ def resolve_compiler_executable(ctx, toolchain, include_tools): * compiler_toolbox binary * companion tool (see //src/valdi_internal/compiler/companion:bin_wrapper) * minify config file - * pngquant binary * sqldelight compiler binary Args: @@ -66,7 +63,6 @@ def resolve_compiler_executable(ctx, toolchain, include_tools): if include_tools: inputs_depsets.append(toolchain.compiler_toolbox.files) inputs_depsets.append(toolchain.minify_config.files) - inputs_depsets.append(toolchain.pngquant.files) inputs_depsets.append(toolchain.sqldelight_compiler.files) sqldelight_compiler = toolchain.sqldelight_compiler @@ -87,14 +83,12 @@ def run_valdi_compiler(ctx, args, outputs, inputs, mnemonic, progress_message, u companion_bin_wrapper = toolchain.companion.files.to_list()[0] compiler_toolbox = toolchain.compiler_toolbox.files.to_list()[0] - pngquant = toolchain.pngquant.files.to_list()[0] minify_config = toolchain.minify_config.files.to_list()[0] client_sql = toolchain.sqldelight_compiler.files.to_list() args.add("--bazel") args.add("--direct-companion-path", companion_bin_wrapper) args.add("--direct-compiler-toolbox-path", compiler_toolbox) - args.add("--direct-pngquant-path", pngquant) args.add("--direct-minify-config-path", minify_config) if client_sql: diff --git a/bzl/valdi/valdi_toolchain.bzl b/bzl/valdi/valdi_toolchain.bzl index eaed4052..68b9f28e 100644 --- a/bzl/valdi/valdi_toolchain.bzl +++ b/bzl/valdi/valdi_toolchain.bzl @@ -5,7 +5,6 @@ ValdiCompilerInfo = provider( fields = [ "compiler", "compiler_toolbox", - "pngquant", "companion", "minify_config", "sqldelight_compiler", @@ -17,7 +16,6 @@ def _valdi_toolchain_impl(ctx): info = ValdiCompilerInfo( compiler = ctx.attr.compiler, compiler_toolbox = ctx.attr.compiler_toolbox, - pngquant = ctx.attr.pngquant, companion = ctx.attr.compiler_companion, minify_config = ctx.attr.minify_config, sqldelight_compiler = ctx.attr.sqldelight_compiler, @@ -50,12 +48,6 @@ valdi_toolchain = rule( "minify_config": attr.label( default = "//modules:minify_config", ), - "pngquant": attr.label( - executable = True, - cfg = "exec", - allow_single_file = True, - doc = "The pngquant executable to use.", - ), "sqldelight_compiler": attr.label( cfg = "exec", doc = "The sqldelight compiler to use.", diff --git a/bzl/valdi_compiler_repos_extension.bzl b/bzl/valdi_compiler_repos_extension.bzl index 8e2c8abb..e22157aa 100644 --- a/bzl/valdi_compiler_repos_extension.bzl +++ b/bzl/valdi_compiler_repos_extension.bzl @@ -79,8 +79,6 @@ def _valdi_compiler_repos_impl(module_ctx): repos = { "valdi_compiler_macos": "bin/compiler/macos", "valdi_compiler_linux": "bin/compiler/linux", - "valdi_pngquant_macos": "bin/pngquant/macos", - "valdi_pngquant_linux": "bin/pngquant/linux", "jscore_libs": "third-party/jscore/libs", } for name, target_dir in repos.items(): diff --git a/bzl/workspace_postinit.bzl b/bzl/workspace_postinit.bzl index a97c0093..86887aa8 100644 --- a/bzl/workspace_postinit.bzl +++ b/bzl/workspace_postinit.bzl @@ -1,8 +1,10 @@ load("@llvm_toolchain//:toolchains.bzl", "llvm_register_toolchains") load("@resvg_crates//:defs.bzl", resvg_crates = "crate_repositories") +load("@pngquant_crates//:defs.bzl", pngquant_crates = "crate_repositories") load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") def valdi_post_initialize_workspace(): llvm_register_toolchains() resvg_crates() + pngquant_crates() rules_jvm_external_setup() diff --git a/bzl/workspace_rust_init.bzl b/bzl/workspace_rust_init.bzl index 0be5ca0f..36632d6c 100644 --- a/bzl/workspace_rust_init.bzl +++ b/bzl/workspace_rust_init.bzl @@ -51,3 +51,25 @@ def valdi_initialize_rust_workspace(): "x86_64-unknown-linux-gnu", ], ) + + crates_repository( + name = "pngquant_crates", + cargo_lockfile = "@valdi//third-party/pngquant:Cargo.lock", + lockfile = "@valdi//third-party/pngquant:cargo-bazel-lock.json", + packages = { + "cc": crate.spec(version = "1.0.72"), + "dunce": crate.spec(version = "1.0.4"), + "getopts": crate.spec(version = "0.2.21"), + "imagequant-sys": crate.spec(version = "4.1.0"), + "libc": crate.spec(version = "0.2.112"), + "libpng-sys": crate.spec(version = "1.1.9"), + "libz-sys": crate.spec(version = "1.1.28"), + "wild": crate.spec(version = "2.2.0"), + }, + render_config = render_config(default_package_name = ""), + supported_platform_triples = [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + ], + ) diff --git a/compiler/compiler/Compiler/Sources/Config/ValdiProjectConfig.swift b/compiler/compiler/Compiler/Sources/Config/ValdiProjectConfig.swift index 0be2e2e4..614f62eb 100644 --- a/compiler/compiler/Compiler/Sources/Config/ValdiProjectConfig.swift +++ b/compiler/compiler/Compiler/Sources/Config/ValdiProjectConfig.swift @@ -67,8 +67,6 @@ struct ValdiProjectConfig { let minifyConfigURL: URL? - let pngquantURL: URL? - let clientSqlURL: URL? let sqlExportPathTemplate: String? @@ -273,16 +271,6 @@ struct ValdiProjectConfig { throw CompilerError("compiler_toolbox_path not configured in the Valdi project config file") } - var pngquantURL: URL? - if let path = args.directPngquantPath { - logger.info("Using direct pngquant: \(path)") - pngquantURL = currentDirectoryUrl.resolving(path: path) - } else { - pngquantURL = try getOSSpecificValue(config: config, key: "pngquant_bin_path")?.string - .map { try $0.resolvingVariables(environment) } - .flatMap { configDirectoryUrl.resolving(path: $0, isDirectory: false) } - } - var minifyConfigURL:URL? if let path = args.directMinifyConfigPath { logger.info("Using direct minify config: \(path)") @@ -355,7 +343,6 @@ struct ValdiProjectConfig { shouldSkipRemoveOrphanFiles: false, compilerToolboxURL: compilerToolboxURL, minifyConfigURL: minifyConfigURL, - pngquantURL: pngquantURL, clientSqlURL: clientSqlURL, sqlExportPathTemplate: sqlExportPathTemplate, androidDefaultClassPath: androidDefaultClassPath, diff --git a/compiler/compiler/Compiler/Sources/Images/ImageConverter.swift b/compiler/compiler/Compiler/Sources/Images/ImageConverter.swift index b30aecd5..cb3b32b3 100644 --- a/compiler/compiler/Compiler/Sources/Images/ImageConverter.swift +++ b/compiler/compiler/Compiler/Sources/Images/ImageConverter.swift @@ -49,38 +49,16 @@ final class ImageConverter { func dependenciesVersions() throws -> [String: String] { return [ - "pnquant": try pngquantVersionString(), "image_toolbox": try imageToolbox.getVersion() ] } - func pngquantCommand() -> String { - return projectConfig.pngquantURL?.path ?? "pngquant" - } - - private func pngquantVersionString() throws -> String { - logger.debug("Resolving pngquant version") - return try run(logger: logger, command: [ - pngquantCommand(), - "--version" - ]).trimmed - } - private func optimizePNG(outputFileURL: URL) throws { guard outputFileURL.pathExtension == "png" else { return } - - _ = try run(logger: logger, - command: [ - pngquantCommand(), - "--skip-if-larger", - "--ext=.png", - "--force", - "--speed=1", - "--quality=70-90", - outputFileURL.path - ]) + + try imageToolbox.optimizePNG(inputPath: outputFileURL.path) } public func convert(imageInfo: ImageInfo, filePath: String, outputFileURL: URL, conversionInfo: ImageConversionInfo) throws -> ImageInfo { @@ -108,12 +86,4 @@ final class ImageConverter { return ImageInfo(size: conversionInfo.outputSize) } - private func run(logger: ILogger, command: [String]) throws -> String { - let handle = SyncProcessHandle.usingEnv(logger: logger, command: command) - try handle.run() - if !handle.stderr.content.isEmpty { - throw CompilerError("ImageConverter error: " + handle.stderr.contentAsString) - } - return handle.stdout.contentAsString - } } diff --git a/compiler/compiler/Compiler/Sources/Images/ImageToolbox.swift b/compiler/compiler/Compiler/Sources/Images/ImageToolbox.swift index 0fc19f43..21ecc454 100644 --- a/compiler/compiler/Compiler/Sources/Images/ImageToolbox.swift +++ b/compiler/compiler/Compiler/Sources/Images/ImageToolbox.swift @@ -30,4 +30,11 @@ class ImageToolbox { func getVersion() throws -> String { return try toolboxExecutable.getVersionString() } + + func optimizePNG(inputPath: String) throws { + _ = try toolboxExecutable.pngquant(arguments: [ + "-i", + inputPath + ]) + } } diff --git a/compiler/compiler/Compiler/Sources/ToolboxExecutable/ToolboxExecutable.swift b/compiler/compiler/Compiler/Sources/ToolboxExecutable/ToolboxExecutable.swift index 5d7a369d..2f56be4a 100644 --- a/compiler/compiler/Compiler/Sources/ToolboxExecutable/ToolboxExecutable.swift +++ b/compiler/compiler/Compiler/Sources/ToolboxExecutable/ToolboxExecutable.swift @@ -69,6 +69,10 @@ class ToolboxExecutable { logIfNeeded(runOutput: output) } + func pngquant(arguments: [String]) throws -> String { + return try run(arguments: ["pngquant"] + arguments) + } + func getVersionString() throws -> String { return try lock.lock { if let version { diff --git a/compiler/compiler/Compiler/Sources/ValdiCompilerArguments.swift b/compiler/compiler/Compiler/Sources/ValdiCompilerArguments.swift index 011958c2..38f53cd9 100644 --- a/compiler/compiler/Compiler/Sources/ValdiCompilerArguments.swift +++ b/compiler/compiler/Compiler/Sources/ValdiCompilerArguments.swift @@ -184,9 +184,6 @@ struct ValdiCompilerArguments: ParsableCommand { @Option(help: "path to the compiler toolbox app") var directCompilerToolboxPath: String? - @Option(help: "path to the pngquant app") - var directPngquantPath: String? - @Option(help: "path to the minify config") var directMinifyConfigPath: String? diff --git a/scripts/linux_deps_setup.sh b/scripts/linux_deps_setup.sh index 078fe86a..4d3a87ec 100755 --- a/scripts/linux_deps_setup.sh +++ b/scripts/linux_deps_setup.sh @@ -117,8 +117,3 @@ echo echo "********************************************************************************" echo "Installing usbmuxd, adb..." sudo apt-get --assume-yes --quiet install usbmuxd adb - -echo -echo "********************************************************************************" -echo "Installing Valdi image processing dependencies..." -sudo apt-get --assume-yes --quiet install pngquant diff --git a/third-party/pngquant/BUILD.bazel b/third-party/pngquant/BUILD.bazel new file mode 100644 index 00000000..5bfbe692 --- /dev/null +++ b/third-party/pngquant/BUILD.bazel @@ -0,0 +1,5 @@ +exports_files([ + "Cargo.lock", + "cargo-bazel-lock.json", + "pngquant.BUILD", +]) diff --git a/third-party/pngquant/Cargo.lock b/third-party/pngquant/Cargo.lock new file mode 100644 index 00000000..40659d1b --- /dev/null +++ b/third-party/pngquant/Cargo.lock @@ -0,0 +1,241 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "direct-cargo-bazel-deps" +version = "0.0.1" +dependencies = [ + "cc", + "dunce", + "getopts", + "imagequant-sys", + "libc", + "libpng-sys", + "libz-sys", + "wild", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "imagequant" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5d73b959dfbe5d6b5cd3ca8de5265c7bc58297f20560a60a1d2ba6a19991f" +dependencies = [ + "arrayvec", + "once_cell", + "rayon", + "rgb", + "thread_local", +] + +[[package]] +name = "imagequant-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5361d7aab1d0c5623172cd6d18920bf21ef56f6e21d7290f1e31e0794c4a76e" +dependencies = [ + "bitflags", + "imagequant", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libpng-sys" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8d56e0541e2b1d73868e7b877624e1f9ce8787a91acdb867c30c579349d38b" +dependencies = [ + "cc", + "dunce", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wild" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1" +dependencies = [ + "glob", +] diff --git a/third-party/pngquant/cargo-bazel-lock.json b/third-party/pngquant/cargo-bazel-lock.json new file mode 100644 index 00000000..63e29ccd --- /dev/null +++ b/third-party/pngquant/cargo-bazel-lock.json @@ -0,0 +1,1673 @@ +{ + "checksum": "6508ed96a535fcc3a286d2e1474c5ea78577bccbd159088005a1f99effd88e37", + "crates": { + "arrayvec 0.7.6": { + "name": "arrayvec", + "version": "0.7.6", + "package_url": "https://github.com/bluss/arrayvec", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/arrayvec/0.7.6/download", + "sha256": "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + } + }, + "targets": [ + { + "Library": { + "crate_name": "arrayvec", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "arrayvec", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.7.6" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "bitflags 2.11.1": { + "name": "bitflags", + "version": "2.11.1", + "package_url": "https://github.com/bitflags/bitflags", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bitflags/2.11.1/download", + "sha256": "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bitflags", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bitflags", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "2.11.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "bytemuck 1.25.0": { + "name": "bytemuck", + "version": "1.25.0", + "package_url": "https://github.com/Lokathor/bytemuck", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bytemuck/1.25.0/download", + "sha256": "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bytemuck", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bytemuck", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.25.0" + }, + "license": "Zlib OR Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT", + "Zlib" + ], + "license_file": "LICENSE-APACHE" + }, + "cc 1.2.61": { + "name": "cc", + "version": "1.2.61", + "package_url": "https://github.com/rust-lang/cc-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cc/1.2.61/download", + "sha256": "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cc", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cc", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "find-msvc-tools 0.1.9", + "target": "find_msvc_tools" + }, + { + "id": "shlex 1.3.0", + "target": "shlex" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.2.61" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "cfg-if 1.0.4": { + "name": "cfg-if", + "version": "1.0.4", + "package_url": "https://github.com/rust-lang/cfg-if", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cfg-if/1.0.4/download", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cfg_if", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cfg_if", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.0.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-deque 0.8.6": { + "name": "crossbeam-deque", + "version": "0.8.6", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-deque/0.8.6/download", + "sha256": "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_deque", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_deque", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-epoch 0.9.18", + "target": "crossbeam_epoch" + }, + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.6" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-epoch 0.9.18": { + "name": "crossbeam-epoch", + "version": "0.9.18", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_epoch", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_epoch", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.9.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-utils 0.8.21": { + "name": "crossbeam-utils", + "version": "0.8.21", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_utils", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_utils", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.21" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "direct-cargo-bazel-deps 0.0.1": { + "name": "direct-cargo-bazel-deps", + "version": "0.0.1", + "package_url": null, + "repository": null, + "targets": [ + { + "Library": { + "crate_name": "direct_cargo_bazel_deps", + "crate_root": ".direct_cargo_bazel_deps.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "direct_cargo_bazel_deps", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.61", + "target": "cc" + }, + { + "id": "dunce 1.0.5", + "target": "dunce" + }, + { + "id": "getopts 0.2.24", + "target": "getopts" + }, + { + "id": "imagequant-sys 4.1.0", + "target": "imagequant_sys" + }, + { + "id": "libc 0.2.186", + "target": "libc" + }, + { + "id": "libpng-sys 1.1.11", + "target": "libpng_sys" + }, + { + "id": "libz-sys 1.1.28", + "target": "libz_sys" + }, + { + "id": "wild 2.2.1", + "target": "wild" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.0.1" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "dunce 1.0.5": { + "name": "dunce", + "version": "1.0.5", + "package_url": "https://gitlab.com/kornelski/dunce", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/dunce/1.0.5/download", + "sha256": "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + } + }, + "targets": [ + { + "Library": { + "crate_name": "dunce", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "dunce", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.5" + }, + "license": "CC0-1.0 OR MIT-0 OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "CC0-1.0", + "MIT-0" + ], + "license_file": "LICENSE" + }, + "either 1.15.0": { + "name": "either", + "version": "1.15.0", + "package_url": "https://github.com/rayon-rs/either", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/either/1.15.0/download", + "sha256": "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + } + }, + "targets": [ + { + "Library": { + "crate_name": "either", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "either", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.15.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "find-msvc-tools 0.1.9": { + "name": "find-msvc-tools", + "version": "0.1.9", + "package_url": "https://github.com/rust-lang/cc-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/find-msvc-tools/0.1.9/download", + "sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + } + }, + "targets": [ + { + "Library": { + "crate_name": "find_msvc_tools", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "find_msvc_tools", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "0.1.9" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "getopts 0.2.24": { + "name": "getopts", + "version": "0.2.24", + "package_url": "https://github.com/rust-lang/getopts", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/getopts/0.2.24/download", + "sha256": "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" + } + }, + "targets": [ + { + "Library": { + "crate_name": "getopts", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "getopts", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "unicode-width 0.2.2", + "target": "unicode_width" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.24" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "glob 0.3.3": { + "name": "glob", + "version": "0.3.3", + "package_url": "https://github.com/rust-lang/glob", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/glob/0.3.3/download", + "sha256": "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + } + }, + "targets": [ + { + "Library": { + "crate_name": "glob", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "glob", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.3.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "imagequant 4.4.1": { + "name": "imagequant", + "version": "4.4.1", + "package_url": "https://github.com/ImageOptim/libimagequant", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/imagequant/4.4.1/download", + "sha256": "caf5d73b959dfbe5d6b5cd3ca8de5265c7bc58297f20560a60a1d2ba6a19991f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "imagequant", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "imagequant", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "_internal_c_ffi", + "default", + "threads" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "arrayvec 0.7.6", + "target": "arrayvec" + }, + { + "id": "once_cell 1.21.4", + "target": "once_cell" + }, + { + "id": "rayon 1.12.0", + "target": "rayon" + }, + { + "id": "rgb 0.8.53", + "target": "rgb" + }, + { + "id": "thread_local 1.1.9", + "target": "thread_local" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "4.4.1" + }, + "license": "GPL-3.0-or-later", + "license_ids": [ + "GPL-3.0" + ], + "license_file": null + }, + "imagequant-sys 4.1.0": { + "name": "imagequant-sys", + "version": "4.1.0", + "package_url": "https://github.com/ImageOptim/libimagequant", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/imagequant-sys/4.1.0/download", + "sha256": "b5361d7aab1d0c5623172cd6d18920bf21ef56f6e21d7290f1e31e0794c4a76e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "imagequant_sys", + "crate_root": "src/ffi.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "imagequant_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bitflags 2.11.1", + "target": "bitflags" + }, + { + "id": "imagequant 4.4.1", + "target": "imagequant" + }, + { + "id": "imagequant-sys 4.1.0", + "target": "build_script_build" + }, + { + "id": "libc 0.2.186", + "target": "libc" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "4.1.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "links": "imagequant" + }, + "license": "GPL-3.0-or-later", + "license_ids": [ + "GPL-3.0" + ], + "license_file": null + }, + "libc 0.2.186": { + "name": "libc", + "version": "0.2.186", + "package_url": "https://github.com/rust-lang/libc", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/libc/0.2.186/download", + "sha256": "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + } + }, + "targets": [ + { + "Library": { + "crate_name": "libc", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "libc", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "libc 0.2.186", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.186" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "libpng-sys 1.1.11": { + "name": "libpng-sys", + "version": "1.1.11", + "package_url": "https://github.com/kornelski/rust-libpng-sys.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/libpng-sys/1.1.11/download", + "sha256": "2e8d56e0541e2b1d73868e7b877624e1f9ce8787a91acdb867c30c579349d38b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "libpng_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "libpng_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "libz-sys" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "libc 0.2.186", + "target": "libc" + }, + { + "id": "libpng-sys 1.1.11", + "target": "build_script_build" + }, + { + "id": "libz-sys 1.1.28", + "target": "libz_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.11" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.61", + "target": "cc" + }, + { + "id": "dunce 1.0.5", + "target": "dunce" + }, + { + "id": "pkg-config 0.3.33", + "target": "pkg_config" + } + ], + "selects": {} + }, + "link_deps": { + "common": [ + { + "id": "libz-sys 1.1.28", + "target": "libz_sys" + } + ], + "selects": {} + }, + "links": "png" + }, + "license": "Libpng", + "license_ids": [ + "Libpng" + ], + "license_file": null + }, + "libz-sys 1.1.28": { + "name": "libz-sys", + "version": "1.1.28", + "package_url": "https://github.com/rust-lang/libz-sys", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/libz-sys/1.1.28/download", + "sha256": "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" + } + }, + "targets": [ + { + "Library": { + "crate_name": "libz_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "libz_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "libc", + "stock-zlib" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "libc 0.2.186", + "target": "libc" + }, + { + "id": "libz-sys 1.1.28", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.1.28" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.61", + "target": "cc" + }, + { + "id": "pkg-config 0.3.33", + "target": "pkg_config" + }, + { + "id": "vcpkg 0.2.15", + "target": "vcpkg" + } + ], + "selects": {} + }, + "links": "z" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "once_cell 1.21.4": { + "name": "once_cell", + "version": "1.21.4", + "package_url": "https://github.com/matklad/once_cell", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/once_cell/1.21.4/download", + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + } + }, + "targets": [ + { + "Library": { + "crate_name": "once_cell", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "once_cell", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "race", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.21.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "pkg-config 0.3.33": { + "name": "pkg-config", + "version": "0.3.33", + "package_url": "https://github.com/rust-lang/pkg-config-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/pkg-config/0.3.33/download", + "sha256": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "pkg_config", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "pkg_config", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "0.3.33" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rayon 1.12.0": { + "name": "rayon", + "version": "1.12.0", + "package_url": "https://github.com/rayon-rs/rayon", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rayon/1.12.0/download", + "sha256": "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rayon", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rayon", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + }, + { + "id": "rayon-core 1.13.0", + "target": "rayon_core" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.12.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rayon-core 1.13.0": { + "name": "rayon-core", + "version": "1.13.0", + "package_url": "https://github.com/rayon-rs/rayon", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rayon-core/1.13.0/download", + "sha256": "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rayon_core", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rayon_core", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "crossbeam-deque 0.8.6", + "target": "crossbeam_deque" + }, + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + }, + { + "id": "rayon-core 1.13.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.13.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "links": "rayon-core" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rgb 0.8.53": { + "name": "rgb", + "version": "0.8.53", + "package_url": "https://github.com/kornelski/rust-rgb", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rgb/0.8.53/download", + "sha256": "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rgb", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rgb", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "bytemuck" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bytemuck 1.25.0", + "target": "bytemuck" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.53" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "shlex 1.3.0": { + "name": "shlex", + "version": "1.3.0", + "package_url": "https://github.com/comex/rust-shlex", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/shlex/1.3.0/download", + "sha256": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + } + }, + "targets": [ + { + "Library": { + "crate_name": "shlex", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "shlex", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2015", + "version": "1.3.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "thread_local 1.1.9": { + "name": "thread_local", + "version": "1.1.9", + "package_url": "https://github.com/Amanieu/thread_local-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/thread_local/1.1.9/download", + "sha256": "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" + } + }, + "targets": [ + { + "Library": { + "crate_name": "thread_local", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "thread_local", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.9" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "unicode-width 0.2.2": { + "name": "unicode-width", + "version": "0.2.2", + "package_url": "https://github.com/unicode-rs/unicode-width", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/unicode-width/0.2.2/download", + "sha256": "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + } + }, + "targets": [ + { + "Library": { + "crate_name": "unicode_width", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "unicode_width", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "cjk", + "default" + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "vcpkg 0.2.15": { + "name": "vcpkg", + "version": "0.2.15", + "package_url": "https://github.com/mcgoo/vcpkg-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/vcpkg/0.2.15/download", + "sha256": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + } + }, + "targets": [ + { + "Library": { + "crate_name": "vcpkg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "vcpkg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.2.15" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "wild 2.2.1": { + "name": "wild", + "version": "2.2.1", + "package_url": "https://gitlab.com/kornelski/wild", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/wild/2.2.1/download", + "sha256": "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1" + } + }, + "targets": [ + { + "Library": { + "crate_name": "wild", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "wild", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "glob 0.3.3", + "target": "glob" + } + ] + } + }, + "edition": "2021", + "version": "2.2.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE" + } + }, + "binary_crates": [], + "workspace_members": { + "direct-cargo-bazel-deps 0.0.1": "" + }, + "conditions": { + "aarch64-apple-darwin": [ + "aarch64-apple-darwin" + ], + "cfg(windows)": [], + "x86_64-apple-darwin": [ + "x86_64-apple-darwin" + ], + "x86_64-unknown-linux-gnu": [ + "x86_64-unknown-linux-gnu" + ] + }, + "direct_deps": [ + "cc 1.2.61", + "dunce 1.0.5", + "getopts 0.2.24", + "imagequant-sys 4.1.0", + "libc 0.2.186", + "libpng-sys 1.1.11", + "libz-sys 1.1.28", + "wild 2.2.1" + ], + "direct_dev_deps": [], + "unused_patches": [] +} diff --git a/third-party/pngquant/pngquant.BUILD b/third-party/pngquant/pngquant.BUILD new file mode 100644 index 00000000..4736a186 --- /dev/null +++ b/third-party/pngquant/pngquant.BUILD @@ -0,0 +1,94 @@ +load("@rules_rust//cargo:defs.bzl", "cargo_build_script", "cargo_toml_env_vars") +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +cargo_toml_env_vars( + name = "cargo_toml_env_vars", + src = "Cargo.toml", +) + +cargo_build_script( + name = "pngquant_build_script", + srcs = ["rust/build.rs"], + compile_data = [ + "pngquant.c", + "pngquant_opts.h", + "rwpng.c", + "rwpng.h", + ], + data = [ + "pngquant.c", + "pngquant_opts.h", + "rwpng.c", + "rwpng.h", + ], + link_deps = [ + "@pngquant_crates//:imagequant-sys", + "@pngquant_crates//:libpng-sys", + ], + build_script_env = { + "CFLAGS": "-I$${pwd}/external/pngquant_crates__imagequant-sys-4.1.0 -I$${pwd}/external/pngquant_crates__libpng-sys-1.1.11/vendor -I$${pwd}/external/pngquant_crates__libz-sys-1.1.28/src/zlib", + }, + crate_name = "build_script_build", + deps = [ + "@pngquant_crates//:cc", + "@pngquant_crates//:dunce", + ], +) + +rust_binary( + name = "pngquant", + srcs = glob(["rust/**/*.rs"]), + crate_features = [], + crate_name = "pngquant", + crate_root = "rust/bin.rs", + edition = "2021", + rustc_env_files = [":cargo_toml_env_vars"], + deps = [ + ":pngquant_build_script", + "@pngquant_crates//:getopts", + "@pngquant_crates//:imagequant-sys", + "@pngquant_crates//:libc", + "@pngquant_crates//:libpng-sys", + "@pngquant_crates//:wild", + ], +) + +genrule( + name = "pnglibconf_h", + srcs = ["@pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt"], + outs = ["pnglibconf.h"], + cmd = "cp $(location @pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt) $@", +) + +cc_library( + name = "pngquant_lib", + srcs = [ + "pngquant.c", + "rwpng.c", + ], + hdrs = [ + "pngquant_opts.h", + "rwpng.h", + ":pnglibconf_h", + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + ], + copts = [ + "-w", + "-DPNGQUANT_NO_MAIN=1", + "-DNDEBUG=1", + "-Iexternal/pngquant_crates__imagequant-sys-4.1.0", + "-Iexternal/pngquant_crates__libpng-sys-1.1.11/vendor", + ], + includes = [ + ".", + "../pngquant_crates__imagequant-sys-4.1.0", + "../pngquant_crates__libpng-sys-1.1.11/vendor", + ], + visibility = ["//visibility:public"], + deps = [ + "@pngquant_crates//:imagequant-sys", + "@pngquant_crates//:libpng-sys", + ], +) diff --git a/valdi/compiler/toolbox/BUILD.bazel b/valdi/compiler/toolbox/BUILD.bazel index b7b82888..3567d4ec 100644 --- a/valdi/compiler/toolbox/BUILD.bazel +++ b/valdi/compiler/toolbox/BUILD.bazel @@ -21,6 +21,7 @@ cc_library( strip_include_prefix = "src", deps = [ ":stamp", + "@pngquant//:pngquant_lib", "@valdi//libs/image_toolbox:lib", "@valdi//valdi:valdi_jsbridge_for_toolbox", "@valdi//valdi:valdi_standalone_runtime", diff --git a/valdi/compiler/toolbox/src/valdi/compiler_toolbox/CompilerToolbox.cpp b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/CompilerToolbox.cpp index dfdb2d63..590bb176 100644 --- a/valdi/compiler/toolbox/src/valdi/compiler_toolbox/CompilerToolbox.cpp +++ b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/CompilerToolbox.cpp @@ -5,11 +5,13 @@ #include "image_toolbox/ImageToolbox.hpp" #include "valdi/compiler_toolbox/CompilerToolbox.hpp" +#include "valdi/compiler_toolbox/PNGQuant.hpp" #include "valdi/compiler_toolbox/RewriteHeader.hpp" #include "valdi/stamp/Stamp.hpp" #include "valdi_core/cpp/Utils/Format.hpp" #include +#include using namespace snap::valdi_core; @@ -24,6 +26,7 @@ Available commands: precompile Precompile a JavaScript file into JS ByteCode image_info Retrieves the info of an image image_convert Convert an image into a different format and or size + pngquant Optimize PNG images rewrite_header Rewrites imports of a C or Objective-C header )D3LIM" << std::endl; return -1; @@ -135,6 +138,22 @@ static int imageConvert(Arguments& arguments) { return EXIT_SUCCESS; } +static int pngquant(Arguments& arguments) { + ArgumentsParser parser; + auto input = parser.addArgument("-i")->setDescription("The input PNG file to optimize")->setRequired(); + + auto result = parser.parse(arguments); + if (!result) { + return printErrorAndUsage(result.error(), parser, "pngquant"); + } + + result = optimizePNG(input->value()); + if (!result) { + return onError(result.error()); + } + return EXIT_SUCCESS; +} + static int rewriteHeader(Arguments& arguments) { ArgumentsParser parser; auto input = parser.addArgument("-i")->setDescription("The input header file to process")->setRequired(); @@ -184,6 +203,8 @@ int runCompilerToolbox(int argc, const char** argv) { return imageInfo(arguments); } else if (command == "image_convert") { return imageConvert(arguments); + } else if (command == "pngquant") { + return pngquant(arguments); } else if (command == "version") { return version(arguments); } else if (command == "rewrite_header") { diff --git a/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.cpp b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.cpp new file mode 100644 index 00000000..28b1a487 --- /dev/null +++ b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.cpp @@ -0,0 +1,53 @@ +#include "valdi/compiler_toolbox/PNGQuant.hpp" + +#include "libimagequant.h" +extern "C" { +#include "rwpng.h" +#include "pngquant_opts.h" +pngquant_error pngquant_main_internal(struct pngquant_options* options, liq_attr* liq); +} + +#include + +namespace Valdi { + +Result optimizePNG(const StringBox& inputPath) { + liq_attr* liq = liq_attr_create(); + if (!liq) { + return Error("SSE-capable CPU is required for this pngquant build"); + } + + if (LIQ_OK != liq_set_quality(liq, 70, 90)) { + liq_attr_destroy(liq); + return Error("Invalid pngquant quality"); + } + + if (LIQ_OK != liq_set_speed(liq, 1)) { + liq_attr_destroy(liq); + return Error("Speed should be between 1 (slow) and 11 (fast)"); + } + + std::string inputFilePath = inputPath.slowToString(); + char* files[] = {inputFilePath.data()}; + pngquant_options options = { + .extension = ".png", + .files = files, + .num_files = 1, + .speed = 1, + .floyd = 1.f, + .force = true, + .min_quality_limit = true, + .skip_if_larger = true, + .strip = false, + }; + + auto pngquantResult = pngquant_main_internal(&options, liq); + liq_attr_destroy(liq); + if (pngquantResult == SUCCESS || pngquantResult == TOO_LARGE_FILE) { + return Void(); + } + + return Error("pngquant failed"); +} + +} // namespace Valdi diff --git a/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.hpp b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.hpp new file mode 100644 index 00000000..f1b0ad86 --- /dev/null +++ b/valdi/compiler/toolbox/src/valdi/compiler_toolbox/PNGQuant.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "valdi_core/cpp/Utils/Result.hpp" +#include "valdi_core/cpp/Utils/StringBox.hpp" + +namespace Valdi { + +Result optimizePNG(const StringBox& inputPath); + +} // namespace Valdi From 1bf17f93cb9536c1dc77fb11e83ebcd894b0516b Mon Sep 17 00:00:00 2001 From: Simon Corsin Date: Thu, 14 May 2026 14:09:51 -0500 Subject: [PATCH 6/6] Fix source-built resvg and pngquant Bazel wiring --- MODULE.bazel | 84 +++++--- bzl/dependencies.bzl | 2 + bzl/workspace_rust_init.bzl | 33 ++- third-party/pngquant/Cargo.lock | 24 ++- third-party/pngquant/cargo-bazel-lock.json | 203 +++++++++++++----- .../pngquant-build-script-bazel-headers.patch | 28 +++ third-party/pngquant/pngquant.BUILD | 82 +++++-- third-party/resvg/cargo-bazel-lock.json | 8 +- 8 files changed, 354 insertions(+), 110 deletions(-) create mode 100644 third-party/pngquant/pngquant-build-script-bazel-headers.patch diff --git a/MODULE.bazel b/MODULE.bazel index 6561de77..03ddeae0 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -266,6 +266,8 @@ http_archive( build_file = "@valdi//third-party/pngquant:pngquant.BUILD", strip_prefix = "pngquant-5b4e91f5dd6af27c928474ffd526bb69e17b0f37", integrity = "sha256-CqMGdZFDTa+6gyxJN3Wvr6d5O4ezR9xvyBd1tzUNR+8=", + patch_args = ["-p1"], + patches = ["@valdi//third-party/pngquant:pngquant-build-script-bazel-headers.patch"], ) rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") @@ -285,31 +287,34 @@ use_repo(rust, "rust_toolchains") register_toolchains("@rust_toolchains//:all") resvg_crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") -resvg_crate.spec(package = "base64", version = "0.22.1") -resvg_crate.spec(package = "data-url", version = "0.3.2") -resvg_crate.spec(package = "flate2", default_features = False, features = ["rust_backend"], version = "1.1.5") -resvg_crate.spec(package = "imagesize", version = "0.14.0") -resvg_crate.spec(package = "kurbo", version = "0.13.0") -resvg_crate.spec(package = "log", version = "0.4.29") -resvg_crate.spec(package = "pico-args", features = ["eq-separator"], version = "0.5.0") -resvg_crate.spec(package = "rgb", version = "0.8.52") -resvg_crate.spec(package = "roxmltree", version = "0.21.1") -resvg_crate.spec(package = "simplecss", version = "0.2.2") -resvg_crate.spec(package = "siphasher", version = "1.0.1") -resvg_crate.spec(package = "strict-num", version = "0.1.1") -resvg_crate.spec(package = "svgtypes", version = "0.16.1") -resvg_crate.spec(package = "tiny-skia", version = "0.12.0") -resvg_crate.spec(package = "tiny-skia-path", version = "0.12.0") -resvg_crate.spec(package = "xmlwriter", version = "0.1.0") +resvg_crate.spec(package = "base64", repositories = ["resvg_crates"], version = "0.22.1") +resvg_crate.spec(package = "data-url", repositories = ["resvg_crates"], version = "0.3.2") +resvg_crate.spec(package = "flate2", default_features = False, features = ["rust_backend"], repositories = ["resvg_crates"], version = "1.1.5") +resvg_crate.spec(package = "imagesize", repositories = ["resvg_crates"], version = "0.14.0") +resvg_crate.spec(package = "kurbo", repositories = ["resvg_crates"], version = "0.13.0") +resvg_crate.spec(package = "log", repositories = ["resvg_crates"], version = "0.4.29") +resvg_crate.spec(package = "pico-args", features = ["eq-separator"], repositories = ["resvg_crates"], version = "0.5.0") +resvg_crate.spec(package = "rgb", repositories = ["resvg_crates"], version = "0.8.52") +resvg_crate.spec(package = "roxmltree", repositories = ["resvg_crates"], version = "0.21.1") +resvg_crate.spec(package = "simplecss", repositories = ["resvg_crates"], version = "0.2.2") +resvg_crate.spec(package = "siphasher", repositories = ["resvg_crates"], version = "1.0.1") +resvg_crate.spec(package = "strict-num", repositories = ["resvg_crates"], version = "0.1.1") +resvg_crate.spec(package = "svgtypes", repositories = ["resvg_crates"], version = "0.16.1") +resvg_crate.spec(package = "tiny-skia", repositories = ["resvg_crates"], version = "0.12.0") +resvg_crate.spec(package = "tiny-skia-path", repositories = ["resvg_crates"], version = "0.12.0") +resvg_crate.spec(package = "xmlwriter", repositories = ["resvg_crates"], version = "0.1.0") resvg_crate.render_config( default_package_name = "", repositories = ["resvg_crates"], ) resvg_crate.from_specs( name = "resvg_crates", + cargo_lockfile = "@resvg//:Cargo.lock", lockfile = "//third-party/resvg:cargo-bazel-lock.json", supported_platform_triples = [ "aarch64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-apple-ios", @@ -320,21 +325,39 @@ resvg_crate.from_specs( ) use_repo(resvg_crate, "resvg_crates") +rust_host_tools = use_extension("@rules_rust//rust:extensions.bzl", "rust_host_tools") +rust_host_tools.host_tools( + name = "rust_host_tools_nightly", + version = "nightly", +) +use_repo(rust_host_tools, "rust_host_tools_nightly") + pngquant_crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") -pngquant_crate.spec(package = "cc", version = "1.0.72") -pngquant_crate.spec(package = "dunce", version = "1.0.4") -pngquant_crate.spec(package = "getopts", version = "0.2.21") -pngquant_crate.spec(package = "imagequant-sys", version = "4.1.0") -pngquant_crate.spec(package = "libc", version = "0.2.112") -pngquant_crate.spec(package = "libpng-sys", version = "1.1.9") -pngquant_crate.spec(package = "libz-sys", version = "1.1.28") -pngquant_crate.spec(package = "wild", version = "2.2.0") +pngquant_crate.spec(package = "pngquant", artifact = "bin", default_features = False, features = ["png-static", "z-static"], repositories = ["pngquant_crates"], version = "=3.0.0") +pngquant_crate.annotation( + crate = "pngquant", + build_script_data = [ + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h", + ], + build_script_env = { + "DEP_IMAGEQUANT_INCLUDE_FILE": "$(execpath @pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h)", + "DEP_PNG_INCLUDE_FILE": "$(execpath @pngquant_crates__libpng-sys-1.1.11//:vendor/png.h)", + }, + gen_all_binaries = True, + patch_args = ["-p1"], + patches = ["//third-party/pngquant:pngquant-build-script-bazel-headers.patch"], + repositories = ["pngquant_crates"], +) pngquant_crate.render_config( default_package_name = "", repositories = ["pngquant_crates"], ) pngquant_crate.from_specs( name = "pngquant_crates", + cargo_lockfile = "//third-party/pngquant:Cargo.lock", + host_tools = "@rust_host_tools_nightly", lockfile = "//third-party/pngquant:cargo-bazel-lock.json", supported_platform_triples = [ "aarch64-apple-darwin", @@ -342,7 +365,18 @@ pngquant_crate.from_specs( "x86_64-unknown-linux-gnu", ], ) -use_repo(pngquant_crate, "pngquant_crates") +use_repo( + pngquant_crate, + "pngquant_crates", + "pngquant_crates__cc-1.2.61", + "pngquant_crates__dunce-1.0.5", + "pngquant_crates__getopts-0.2.24", + "pngquant_crates__imagequant-sys-4.1.0", + "pngquant_crates__libc-0.2.186", + "pngquant_crates__libpng-sys-1.1.11", + "pngquant_crates__libz-sys-1.1.28", + "pngquant_crates__wild-2.2.1", +) bazel_dep(name = "rules_license", version = "1.0.0") diff --git a/bzl/dependencies.bzl b/bzl/dependencies.bzl index bbb44ee5..20eae83a 100644 --- a/bzl/dependencies.bzl +++ b/bzl/dependencies.bzl @@ -458,6 +458,8 @@ def setup_dependencies(workspace_root = None): build_file = "@valdi//third-party/pngquant:pngquant.BUILD", strip_prefix = "pngquant-5b4e91f5dd6af27c928474ffd526bb69e17b0f37", integrity = "sha256-CqMGdZFDTa+6gyxJN3Wvr6d5O4ezR9xvyBd1tzUNR+8=", + patch_args = ["-p1"], + patches = ["@valdi//third-party/pngquant:pngquant-build-script-bazel-headers.patch"], ) http_archive( diff --git a/bzl/workspace_rust_init.bzl b/bzl/workspace_rust_init.bzl index 36632d6c..1a5a7943 100644 --- a/bzl/workspace_rust_init.bzl +++ b/bzl/workspace_rust_init.bzl @@ -14,7 +14,10 @@ def valdi_initialize_rust_workspace(): "x86_64-apple-ios", "x86_64-linux-android", ], - versions = ["1.87.0"], + versions = [ + "1.87.0", + "nightly/2025-04-03", + ], ) crate_universe_dependencies() @@ -43,6 +46,8 @@ def valdi_initialize_rust_workspace(): render_config = render_config(default_package_name = ""), supported_platform_triples = [ "aarch64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-apple-ios", @@ -54,19 +59,29 @@ def valdi_initialize_rust_workspace(): crates_repository( name = "pngquant_crates", + annotations = { + "pngquant": [crate.annotation( + build_script_data = [ + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h", + ], + build_script_env = { + "DEP_IMAGEQUANT_INCLUDE_FILE": "$(execpath @pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h)", + "DEP_PNG_INCLUDE_FILE": "$(execpath @pngquant_crates__libpng-sys-1.1.11//:vendor/png.h)", + }, + gen_binaries = True, + patch_args = ["-p1"], + patches = ["@valdi//third-party/pngquant:pngquant-build-script-bazel-headers.patch"], + )], + }, cargo_lockfile = "@valdi//third-party/pngquant:Cargo.lock", lockfile = "@valdi//third-party/pngquant:cargo-bazel-lock.json", packages = { - "cc": crate.spec(version = "1.0.72"), - "dunce": crate.spec(version = "1.0.4"), - "getopts": crate.spec(version = "0.2.21"), - "imagequant-sys": crate.spec(version = "4.1.0"), - "libc": crate.spec(version = "0.2.112"), - "libpng-sys": crate.spec(version = "1.1.9"), - "libz-sys": crate.spec(version = "1.1.28"), - "wild": crate.spec(version = "2.2.0"), + "pngquant": crate.spec(artifact = "bin", default_features = False, features = ["png-static", "z-static"], version = "=3.0.0"), }, render_config = render_config(default_package_name = ""), + rust_version = "nightly/2025-04-03", supported_platform_triples = [ "aarch64-apple-darwin", "x86_64-apple-darwin", diff --git a/third-party/pngquant/Cargo.lock b/third-party/pngquant/Cargo.lock index 40659d1b..2d7e8554 100644 --- a/third-party/pngquant/Cargo.lock +++ b/third-party/pngquant/Cargo.lock @@ -65,14 +65,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" name = "direct-cargo-bazel-deps" version = "0.0.1" dependencies = [ - "cc", - "dunce", - "getopts", - "imagequant-sys", - "libc", - "libpng-sys", - "libz-sys", - "wild", + "pngquant", ] [[package]] @@ -175,6 +168,21 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "pngquant" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea98a6bcdba2e4b93ef7f49241643cef3d117ad09fef1d0fefe1a427de234a4c" +dependencies = [ + "cc", + "dunce", + "getopts", + "imagequant-sys", + "libc", + "libpng-sys", + "wild", +] + [[package]] name = "rayon" version = "1.12.0" diff --git a/third-party/pngquant/cargo-bazel-lock.json b/third-party/pngquant/cargo-bazel-lock.json index 63e29ccd..3fd806e3 100644 --- a/third-party/pngquant/cargo-bazel-lock.json +++ b/third-party/pngquant/cargo-bazel-lock.json @@ -1,5 +1,5 @@ { - "checksum": "6508ed96a535fcc3a286d2e1474c5ea78577bccbd159088005a1f99effd88e37", + "checksum": "a473cc5ec76eb0c45a5f883af326157afb91095a00ae79e375ab8dca73a7008a", "crates": { "arrayvec 0.7.6": { "name": "arrayvec", @@ -433,43 +433,6 @@ "compile_data_glob": [ "**" ], - "deps": { - "common": [ - { - "id": "cc 1.2.61", - "target": "cc" - }, - { - "id": "dunce 1.0.5", - "target": "dunce" - }, - { - "id": "getopts 0.2.24", - "target": "getopts" - }, - { - "id": "imagequant-sys 4.1.0", - "target": "imagequant_sys" - }, - { - "id": "libc 0.2.186", - "target": "libc" - }, - { - "id": "libpng-sys 1.1.11", - "target": "libpng_sys" - }, - { - "id": "libz-sys 1.1.28", - "target": "libz_sys" - }, - { - "id": "wild 2.2.1", - "target": "wild" - } - ], - "selects": {} - }, "edition": "2018", "version": "0.0.1" }, @@ -971,7 +934,9 @@ "crate_features": { "common": [ "default", - "libz-sys" + "libz-sys", + "static", + "static-libz" ], "selects": {} }, @@ -1084,6 +1049,7 @@ "common": [ "default", "libc", + "static", "stock-zlib" ], "selects": {} @@ -1227,6 +1193,150 @@ ], "license_file": "LICENSE-APACHE" }, + "pngquant 3.0.0": { + "name": "pngquant", + "version": "3.0.0", + "package_url": "https://github.com/kornelski/pngquant.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/pngquant/3.0.0/download", + "sha256": "ea98a6bcdba2e4b93ef7f49241643cef3d117ad09fef1d0fefe1a427de234a4c", + "patch_args": [ + "-p1" + ], + "patches": [ + "@@//third-party/pngquant:pngquant-build-script-bazel-headers.patch" + ] + } + }, + "targets": [ + { + "Binary": { + "crate_name": "pngquant", + "crate_root": "rust/bin.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "rust/build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": null, + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "png-static", + "z-static" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "getopts 0.2.24", + "target": "getopts" + }, + { + "id": "imagequant-sys 4.1.0", + "target": "imagequant_sys" + }, + { + "id": "libc 0.2.186", + "target": "libc" + }, + { + "id": "libpng-sys 1.1.11", + "target": "libpng_sys" + }, + { + "id": "pngquant 3.0.0", + "target": "build_script_build" + }, + { + "id": "wild 2.2.1", + "target": "wild" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "3.0.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data": { + "common": [ + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h" + ], + "selects": {} + }, + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.61", + "target": "cc" + }, + { + "id": "dunce 1.0.5", + "target": "dunce" + } + ], + "selects": {} + }, + "link_deps": { + "common": [ + { + "id": "imagequant-sys 4.1.0", + "target": "imagequant_sys" + }, + { + "id": "libpng-sys 1.1.11", + "target": "libpng_sys" + } + ], + "selects": {} + }, + "build_script_env": { + "common": { + "DEP_IMAGEQUANT_INCLUDE_FILE": "$(execpath @pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h)", + "DEP_PNG_INCLUDE_FILE": "$(execpath @pngquant_crates__libpng-sys-1.1.11//:vendor/png.h)" + }, + "selects": {} + } + }, + "license": "GPL-3.0-or-later", + "license_ids": [ + "GPL-3.0" + ], + "license_file": null + }, "rayon 1.12.0": { "name": "rayon", "version": "1.12.0", @@ -1642,7 +1752,9 @@ "license_file": "LICENSE" } }, - "binary_crates": [], + "binary_crates": [ + "pngquant 3.0.0" + ], "workspace_members": { "direct-cargo-bazel-deps 0.0.1": "" }, @@ -1658,16 +1770,7 @@ "x86_64-unknown-linux-gnu" ] }, - "direct_deps": [ - "cc 1.2.61", - "dunce 1.0.5", - "getopts 0.2.24", - "imagequant-sys 4.1.0", - "libc 0.2.186", - "libpng-sys 1.1.11", - "libz-sys 1.1.28", - "wild 2.2.1" - ], + "direct_deps": [], "direct_dev_deps": [], "unused_patches": [] } diff --git a/third-party/pngquant/pngquant-build-script-bazel-headers.patch b/third-party/pngquant/pngquant-build-script-bazel-headers.patch new file mode 100644 index 00000000..78610b8e --- /dev/null +++ b/third-party/pngquant/pngquant-build-script-bazel-headers.patch @@ -0,0 +1,28 @@ +diff --git a/rust/build.rs b/rust/build.rs +index 055b872..695cd64 100644 +--- a/rust/build.rs ++++ b/rust/build.rs +@@ -38,6 +38,10 @@ fn main() { +- if let Ok(p) = env::var("DEP_IMAGEQUANT_INCLUDE") { ++ if let Ok(p) = env::var("DEP_IMAGEQUANT_INCLUDE_FILE") { ++ if let Some(parent) = dunce::simplified(Path::new(&p)).parent() { ++ cc.include(parent); ++ } ++ } else if let Ok(p) = env::var("DEP_IMAGEQUANT_INCLUDE") { + cc.include(dunce::simplified(Path::new(&p))); + } else { + cc.include("lib"); + cc.include("lib/imagequant-sys"); + } +@@ -45,5 +49,10 @@ fn main() { +- if let Ok(p) = env::var("DEP_PNG_INCLUDE") { ++ if let Ok(p) = env::var("DEP_PNG_INCLUDE_FILE") { ++ if let Some(parent) = dunce::simplified(Path::new(&p)).parent() { ++ cc.include(parent); ++ } ++ } ++ if let Ok(p) = env::var("DEP_PNG_INCLUDE") { + for p in env::split_paths(&p) { + cc.include(dunce::simplified(&p)); + } + } diff --git a/third-party/pngquant/pngquant.BUILD b/third-party/pngquant/pngquant.BUILD index 4736a186..218bec38 100644 --- a/third-party/pngquant/pngquant.BUILD +++ b/third-party/pngquant/pngquant.BUILD @@ -16,24 +16,37 @@ cargo_build_script( "pngquant_opts.h", "rwpng.c", "rwpng.h", + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt", + "@pngquant_crates__libz-sys-1.1.28//:src/zlib/zconf.h", + "@pngquant_crates__libz-sys-1.1.28//:src/zlib/zlib.h", ], data = [ "pngquant.c", "pngquant_opts.h", "rwpng.c", "rwpng.h", + "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h", + "@pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt", + "@pngquant_crates__libz-sys-1.1.28//:src/zlib/zconf.h", + "@pngquant_crates__libz-sys-1.1.28//:src/zlib/zlib.h", ], link_deps = [ - "@pngquant_crates//:imagequant-sys", - "@pngquant_crates//:libpng-sys", + "@pngquant_crates__imagequant-sys-4.1.0//:imagequant_sys", + "@pngquant_crates__libpng-sys-1.1.11//:libpng_sys", ], build_script_env = { - "CFLAGS": "-I$${pwd}/external/pngquant_crates__imagequant-sys-4.1.0 -I$${pwd}/external/pngquant_crates__libpng-sys-1.1.11/vendor -I$${pwd}/external/pngquant_crates__libz-sys-1.1.28/src/zlib", + "DEP_IMAGEQUANT_INCLUDE_FILE": "$(execpath @pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h)", + "DEP_PNG_INCLUDE_FILE": "$(execpath @pngquant_crates__libpng-sys-1.1.11//:vendor/png.h)", }, crate_name = "build_script_build", deps = [ - "@pngquant_crates//:cc", - "@pngquant_crates//:dunce", + "@pngquant_crates__cc-1.2.61//:cc", + "@pngquant_crates__dunce-1.0.5//:dunce", ], ) @@ -47,14 +60,35 @@ rust_binary( rustc_env_files = [":cargo_toml_env_vars"], deps = [ ":pngquant_build_script", - "@pngquant_crates//:getopts", - "@pngquant_crates//:imagequant-sys", - "@pngquant_crates//:libc", - "@pngquant_crates//:libpng-sys", - "@pngquant_crates//:wild", + "@pngquant_crates__getopts-0.2.24//:getopts", + "@pngquant_crates__imagequant-sys-4.1.0//:imagequant_sys", + "@pngquant_crates__libc-0.2.186//:libc", + "@pngquant_crates__libpng-sys-1.1.11//:libpng_sys", + "@pngquant_crates__wild-2.2.1//:wild", ], ) +genrule( + name = "libimagequant_h", + srcs = ["@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h"], + outs = ["libimagequant.h"], + cmd = "cp $(location @pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h) $@", +) + +genrule( + name = "png_h", + srcs = ["@pngquant_crates__libpng-sys-1.1.11//:vendor/png.h"], + outs = ["png.h"], + cmd = "cp $(location @pngquant_crates__libpng-sys-1.1.11//:vendor/png.h) $@", +) + +genrule( + name = "pngconf_h", + srcs = ["@pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h"], + outs = ["pngconf.h"], + cmd = "cp $(location @pngquant_crates__libpng-sys-1.1.11//:vendor/pngconf.h) $@", +) + genrule( name = "pnglibconf_h", srcs = ["@pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt"], @@ -62,6 +96,20 @@ genrule( cmd = "cp $(location @pngquant_crates__libpng-sys-1.1.11//:vendor/scripts/pnglibconf.h.prebuilt) $@", ) +genrule( + name = "zconf_h", + srcs = ["@pngquant_crates__libz-sys-1.1.28//:src/zlib/zconf.h"], + outs = ["zconf.h"], + cmd = "cp $(location @pngquant_crates__libz-sys-1.1.28//:src/zlib/zconf.h) $@", +) + +genrule( + name = "zlib_h", + srcs = ["@pngquant_crates__libz-sys-1.1.28//:src/zlib/zlib.h"], + outs = ["zlib.h"], + cmd = "cp $(location @pngquant_crates__libz-sys-1.1.28//:src/zlib/zlib.h) $@", +) + cc_library( name = "pngquant_lib", srcs = [ @@ -71,24 +119,24 @@ cc_library( hdrs = [ "pngquant_opts.h", "rwpng.h", + ":libimagequant_h", + ":png_h", + ":pngconf_h", ":pnglibconf_h", - "@pngquant_crates__imagequant-sys-4.1.0//:libimagequant.h", + ":zconf_h", + ":zlib_h", ], copts = [ "-w", "-DPNGQUANT_NO_MAIN=1", "-DNDEBUG=1", - "-Iexternal/pngquant_crates__imagequant-sys-4.1.0", - "-Iexternal/pngquant_crates__libpng-sys-1.1.11/vendor", ], includes = [ ".", - "../pngquant_crates__imagequant-sys-4.1.0", - "../pngquant_crates__libpng-sys-1.1.11/vendor", ], visibility = ["//visibility:public"], deps = [ - "@pngquant_crates//:imagequant-sys", - "@pngquant_crates//:libpng-sys", + "@pngquant_crates__imagequant-sys-4.1.0//:imagequant_sys", + "@pngquant_crates__libpng-sys-1.1.11//:libpng_sys", ], ) diff --git a/third-party/resvg/cargo-bazel-lock.json b/third-party/resvg/cargo-bazel-lock.json index 138f9cd1..08d6cc19 100644 --- a/third-party/resvg/cargo-bazel-lock.json +++ b/third-party/resvg/cargo-bazel-lock.json @@ -1,5 +1,5 @@ { - "checksum": "67a51ae2af037351f4d514b8ad1cc478d7d50895963b481743c76a6f2035847d", + "checksum": "6d17b28f8de314389d0e6c52a5e722291c6237f5bd6cd70e713138b93c375d60", "crates": { "adler2 2.0.1": { "name": "adler2", @@ -1827,6 +1827,12 @@ "aarch64-apple-darwin": [ "aarch64-apple-darwin" ], + "aarch64-apple-ios": [ + "aarch64-apple-ios" + ], + "aarch64-apple-ios-sim": [ + "aarch64-apple-ios-sim" + ], "aarch64-linux-android": [ "aarch64-linux-android" ],