/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkottieJson.h" #include "SkData.h" #include "SkScalar.h" #include "SkPath.h" #include "SkPoint.h" #include "SkStream.h" #include "SkString.h" #include "SkottieValue.h" #include "rapidjson/error/en.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" #include namespace skottie { namespace json { template <> bool ValueRef::to(SkScalar* v) const { if (!fValue) return false; // Some versions wrap values as single-element arrays. if (fValue->IsArray() && fValue->Size() == 1) { return ValueRef(fValue->operator[](0)).to(v); } if (!fValue->IsNumber()) return false; *v = static_cast(fValue->GetDouble()); return true; } template <> bool ValueRef::to(bool* v) const { if (!fValue) return false; switch(fValue->GetType()) { case rapidjson::kNumberType: *v = SkToBool(fValue->GetDouble()); return true; case rapidjson::kFalseType: case rapidjson::kTrueType: *v = fValue->GetBool(); return true; default: break; } return false; } template <> bool ValueRef::to(int* v) const { if (!fValue || !fValue->IsInt()) return false; *v = fValue->GetInt(); return true; } template <> bool ValueRef::to(SkString* v) const { if (!fValue || !fValue->IsString()) return false; v->set(fValue->GetString()); return true; } template <> bool ValueRef::to(SkPoint* v) const { if (!fValue || !fValue->IsObject()) return false; const auto jvx = ValueRef(this->operator[]("x")), jvy = ValueRef(this->operator[]("y")); // Some BM versions seem to store x/y as single-element arrays. return ValueRef(jvx.isArray() ? jvx.operator[](size_t(0)) : jvx).to(&v->fX) && ValueRef(jvy.isArray() ? jvy.operator[](size_t(0)) : jvy).to(&v->fY); } template <> bool ValueRef::to>(std::vector* v) const { if (!fValue || !fValue->IsArray()) return false; v->resize(fValue->Size()); for (size_t i = 0; i < fValue->Size(); ++i) { if (!ValueRef(fValue->operator[](i)).to(v->data() + i)) { return false; } } return true; } namespace { bool ParsePointVec(const ValueRef& jv, std::vector* pts) { if (!jv.isArray()) return false; pts->clear(); pts->reserve(jv.size()); std::vector vec; for (size_t i = 0; i < jv.size(); ++i) { if (!jv[i].to(&vec) || vec.size() != 2) return false; pts->push_back(SkPoint::Make(vec[0], vec[1])); } return true; } } // namespace template <> bool ValueRef::to(ShapeValue* v) const { SkASSERT(v->fVertices.empty()); if (!fValue) return false; // Some versions wrap values as single-element arrays. if (fValue->IsArray() && fValue->Size() == 1) { return ValueRef(fValue->operator[](0)).to(v); } std::vector inPts, // Cubic Bezier "in" control points, relative to vertices. outPts, // Cubic Bezier "out" control points, relative to vertices. verts; // Cubic Bezier vertices. if (!fValue->IsObject() || !ParsePointVec(this->operator[]("i"), &inPts) || !ParsePointVec(this->operator[]("o"), &outPts) || !ParsePointVec(this->operator[]("v"), &verts) || inPts.size() != outPts.size() || inPts.size() != verts.size()) { return false; } v->fVertices.reserve(inPts.size()); for (size_t i = 0; i < inPts.size(); ++i) { v->fVertices.push_back(BezierVertex({inPts[i], outPts[i], verts[i]})); } v->fClosed = this->operator[]("c").toDefault(false); return true; } size_t ValueRef::size() const { return this->isArray() ? fValue->Size() : 0; } ValueRef ValueRef::operator[](size_t i) const { return i < this->size() ? ValueRef(fValue->operator[](i)) : ValueRef(); } ValueRef ValueRef::operator[](const char* key) const { if (!this->isObject()) return ValueRef(); const auto m = fValue->FindMember(key); return m == fValue->MemberEnd() ? ValueRef() : ValueRef(m->value); } const rapidjson::Value* ValueRef::begin() const { return this->isArray() ? fValue->Begin() : nullptr; } const rapidjson::Value* ValueRef::end() const { return this->isArray() ? fValue->End() : nullptr; } SkString ValueRef::toString() const { #ifdef SK_DEBUG rapidjson::StringBuffer buf; if (fValue) { rapidjson::PrettyWriter writer(buf); fValue->Accept(writer); } return SkString(buf.GetString()); #else return SkString(); #endif // SK_DEBUG } Document::Document(SkStream* stream) { if (!stream->hasLength()) { SkDebugf("!! unsupported unseekable json stream\n"); return; } // RapidJSON provides three DOM-builder approaches: // // 1) in-place : all data buffered, constructs the DOM in-place -- this is the fastest // 2) from buffer: all data buffered, copies to DOM -- this is slightly slower // 3) from stream: streamed data, reads/copies to DOM -- this is *significantly* slower // // We like fast, so #1 it is. // The buffer needs to be C-string. const auto size = stream->getLength(); fData = SkData::MakeUninitialized(size + 1); if (stream->read(fData->writable_data(), size) < size) { SkDebugf("!! could not read JSON stream\n"); return; } auto data = static_cast(fData->writable_data()); data[size] = '\0'; fDocument.ParseInsitu(data); #ifdef SK_DEBUG if (fDocument.HasParseError()) { SkDebugf("!! failed to parse json: %s\n", rapidjson::GetParseError_En(fDocument.GetParseError())); } #endif } } // namespace json } // namespace skottie