| /* | |
| * Copyright 2022 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "modules/svg/include/SkSVGOpenTypeSVGDecoder.h" | |
| #include "include/codec/SkCodec.h" | |
| #include "include/codec/SkJpegDecoder.h" | |
| #include "include/codec/SkPngDecoder.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkOpenTypeSVGDecoder.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTypes.h" | |
| #include "modules/skresources/include/SkResources.h" | |
| #include "modules/svg/include/SkSVGAttribute.h" | |
| #include "modules/svg/include/SkSVGDOM.h" | |
| #include "modules/svg/include/SkSVGRenderContext.h" | |
| #include "modules/svg/include/SkSVGTypes.h" | |
| #include "src/core/SkBase64.h" | |
| #include "src/core/SkEnumerate.h" | |
| #include "src/core/SkTHash.h" | |
| #include <array> | |
| #include <cstring> | |
| #include <memory> | |
| #include <utility> | |
| using namespace skia_private; | |
| namespace { | |
| class DataResourceProvider final : public skresources::ResourceProvider { | |
| public: | |
| static sk_sp<skresources::ResourceProvider> Make() { | |
| return sk_sp<skresources::ResourceProvider>(new DataResourceProvider()); | |
| } | |
| sk_sp<skresources::ImageAsset> loadImageAsset(const char rpath[], | |
| const char rname[], | |
| const char rid[]) const override { | |
| if (auto data = decode_datauri("data:image/", rname)) { | |
| std::unique_ptr<SkCodec> codec = nullptr; | |
| if (SkPngDecoder::IsPng(data->bytes(), data->size())) { | |
| codec = SkPngDecoder::Decode(data, nullptr); | |
| } else if (SkJpegDecoder::IsJpeg(data->bytes(), data->size())) { | |
| codec = SkJpegDecoder::Decode(data, nullptr); | |
| } else { | |
| // The spec says only JPEG or PNG should be used to encode the embedded data. | |
| // https://learn.microsoft.com/en-us/typography/opentype/spec/svg#svg-capability-requirements-and-restrictions | |
| SkDEBUGFAIL("Unsupported codec"); | |
| return nullptr; | |
| } | |
| if (!codec) { | |
| return nullptr; | |
| } | |
| return skresources::MultiFrameImageAsset::Make(std::move(codec)); | |
| } | |
| return nullptr; | |
| } | |
| private: | |
| DataResourceProvider() = default; | |
| static sk_sp<SkData> decode_datauri(const char prefix[], const char uri[]) { | |
| // We only handle B64 encoded image dataURIs: data:image/<type>;base64,<data> | |
| // (https://en.wikipedia.org/wiki/Data_URI_scheme) | |
| static constexpr char kDataURIEncodingStr[] = ";base64,"; | |
| const size_t prefixLen = strlen(prefix); | |
| if (strncmp(uri, prefix, prefixLen) != 0) { | |
| return nullptr; | |
| } | |
| const char* encoding = strstr(uri + prefixLen, kDataURIEncodingStr); | |
| if (!encoding) { | |
| return nullptr; | |
| } | |
| const char* b64Data = encoding + std::size(kDataURIEncodingStr) - 1; | |
| size_t b64DataLen = strlen(b64Data); | |
| size_t dataLen; | |
| if (SkBase64::Decode(b64Data, b64DataLen, nullptr, &dataLen) != SkBase64::kNoError) { | |
| return nullptr; | |
| } | |
| sk_sp<SkData> data = SkData::MakeUninitialized(dataLen); | |
| void* rawData = data->writable_data(); | |
| if (SkBase64::Decode(b64Data, b64DataLen, rawData, &dataLen) != SkBase64::kNoError) { | |
| return nullptr; | |
| } | |
| return data; | |
| } | |
| using INHERITED = ResourceProvider; | |
| }; | |
| } // namespace | |
| SkSVGOpenTypeSVGDecoder::SkSVGOpenTypeSVGDecoder(sk_sp<SkSVGDOM> skSvg, size_t approximateSize) | |
| : fSkSvg(std::move(skSvg)) | |
| , fApproximateSize(approximateSize) | |
| {} | |
| SkSVGOpenTypeSVGDecoder::~SkSVGOpenTypeSVGDecoder() = default; | |
| std::unique_ptr<SkOpenTypeSVGDecoder> SkSVGOpenTypeSVGDecoder::Make(const uint8_t* svg, | |
| size_t svgLength) { | |
| std::unique_ptr<SkStreamAsset> stream = SkMemoryStream::MakeDirect(svg, svgLength); | |
| if (!stream) { | |
| return nullptr; | |
| } | |
| SkSVGDOM::Builder builder; | |
| builder.setResourceProvider(DataResourceProvider::Make()); | |
| // We shouldn't need to set this builder's font manager or shaping utils because hopefully | |
| // the SVG we are decoding doesn't itself have <text> tags. | |
| sk_sp<SkSVGDOM> skSvg = builder.make(*stream); | |
| if (!skSvg) { | |
| return nullptr; | |
| } | |
| return std::unique_ptr<SkOpenTypeSVGDecoder>( | |
| new SkSVGOpenTypeSVGDecoder(std::move(skSvg), svgLength)); | |
| } | |
| size_t SkSVGOpenTypeSVGDecoder::approximateSize() { | |
| // TODO | |
| return fApproximateSize; | |
| } | |
| bool SkSVGOpenTypeSVGDecoder::render(SkCanvas& canvas, int upem, SkGlyphID glyphId, | |
| SkColor foregroundColor, SkSpan<SkColor> palette) { | |
| SkSize emSize = SkSize::Make(SkScalar(upem), SkScalar(upem)); | |
| fSkSvg->setContainerSize(emSize); | |
| SkSVGPresentationContext pctx; | |
| pctx.fInherited.fColor.set(foregroundColor); | |
| THashMap<SkString, SkSVGColorType> namedColors; | |
| if (!palette.empty()) { | |
| for (auto&& [i, color] : SkMakeEnumerate(palette)) { | |
| constexpr const size_t colorStringLen = sizeof("color") - 1; | |
| char colorIdString[colorStringLen + kSkStrAppendU32_MaxSize + 1] = "color"; | |
| *SkStrAppendU32(colorIdString + colorStringLen, i) = 0; | |
| namedColors.set(SkString(colorIdString), color); | |
| } | |
| pctx.fNamedColors = &namedColors; | |
| } | |
| constexpr const size_t glyphStringLen = sizeof("glyph") - 1; | |
| char glyphIdString[glyphStringLen + kSkStrAppendU32_MaxSize + 1] = "glyph"; | |
| *SkStrAppendU32(glyphIdString + glyphStringLen, glyphId) = 0; | |
| fSkSvg->renderNode(&canvas, pctx, glyphIdString); | |
| return true; | |
| } |