| /* |
| * Copyright 2022 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "modules/skottie/src/text/Font.h" |
| |
| #include "include/core/SkPath.h" |
| #include "modules/skottie/src/SkottieJson.h" |
| #include "modules/skottie/src/SkottiePriv.h" |
| #include "modules/sksg/include/SkSGPath.h" |
| #include "modules/sksg/include/SkSGTransform.h" |
| |
| namespace skottie::internal { |
| |
| bool CustomFont::Builder::parseGlyph(const AnimationBuilder* abuilder, |
| const skjson::ObjectValue& jchar) { |
| // Glyph encoding: |
| // { |
| // "ch": "t", |
| // "data": <glyph data>, // Glyph path or composition data |
| // "size": 50, // apparently ignored |
| // "w": 32.67, // width/advance (1/100 units) |
| // "t": 1 // Marker for composition glyphs only. |
| // } |
| const skjson::StringValue* jch = jchar["ch"]; |
| const skjson::ObjectValue* jdata = jchar["data"]; |
| if (!jch || !jdata) { |
| return false; |
| } |
| |
| const auto* ch_ptr = jch->begin(); |
| const auto ch_len = jch->size(); |
| if (SkUTF::CountUTF8(ch_ptr, ch_len) != 1) { |
| return false; |
| } |
| |
| const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len); |
| SkASSERT(uni != -1); |
| if (!SkTFitsIn<SkGlyphID>(uni)) { |
| // Custom font keys are SkGlyphIDs. We could implement a remapping scheme if needed, |
| // but for now direct mapping seems to work well enough. |
| return false; |
| } |
| const auto glyph_id = SkTo<SkGlyphID>(uni); |
| |
| // Normalize the path and advance for 1pt. |
| static constexpr float kPtScale = 0.01f; |
| const auto advance = ParseDefault(jchar["w"], 0.0f) * kPtScale; |
| |
| // Custom glyphs are either compositions... |
| SkSize glyph_size; |
| if (auto comp_node = ParseGlyphComp(abuilder, *jdata, &glyph_size)) { |
| // With glyph comps, we use the SkCustomTypeface only for shaping -- not for rendering. |
| // We still need accurate glyph bounds though, for visual alignment. |
| |
| // TODO: This assumes the glyph origin is always in the lower-left corner. |
| // Lottie may need to add an origin property, to allow designers full control over |
| // glyph comp positioning. |
| const auto glyph_bounds = SkRect::MakeLTRB(0, -glyph_size.fHeight, glyph_size.fWidth, 0); |
| fCustomBuilder.setGlyph(glyph_id, advance, SkPath::Rect(glyph_bounds)); |
| |
| // Rendering is handled explicitly, post shaping, |
| // based on info tracked in this GlyphCompMap. |
| fGlyphComps.set(glyph_id, std::move(comp_node)); |
| |
| return true; |
| } |
| |
| // ... or paths. |
| SkPath path; |
| if (!ParseGlyphPath(abuilder, *jdata, &path)) { |
| return false; |
| } |
| |
| path.transform(SkMatrix::Scale(kPtScale, kPtScale)); |
| |
| fCustomBuilder.setGlyph(glyph_id, advance, path); |
| |
| return true; |
| } |
| |
| bool CustomFont::Builder::ParseGlyphPath(const skottie::internal::AnimationBuilder* abuilder, |
| const skjson::ObjectValue& jdata, |
| SkPath* path) { |
| // Glyph path encoding: |
| // |
| // "data": { |
| // "shapes": [ // follows the shape layer format |
| // { |
| // "ty": "gr", // group shape type |
| // "it": [ // group items |
| // { |
| // "ty": "sh", // actual shape |
| // "ks": <path data> // animatable path format, but always static |
| // }, |
| // ... |
| // ] |
| // }, |
| // ... |
| // ] |
| // } |
| |
| const skjson::ArrayValue* jshapes = jdata["shapes"]; |
| if (!jshapes) { |
| // Space/empty glyph. |
| return true; |
| } |
| |
| for (const skjson::ObjectValue* jgrp : *jshapes) { |
| if (!jgrp) { |
| return false; |
| } |
| |
| const skjson::ArrayValue* jit = (*jgrp)["it"]; |
| if (!jit) { |
| return false; |
| } |
| |
| for (const skjson::ObjectValue* jshape : *jit) { |
| if (!jshape) { |
| return false; |
| } |
| |
| // Glyph paths should never be animated. But they are encoded as |
| // animatable properties, so we use the appropriate helpers. |
| skottie::internal::AnimationBuilder::AutoScope ascope(abuilder); |
| auto path_node = abuilder->attachPath((*jshape)["ks"]); |
| auto animators = ascope.release(); |
| |
| if (!path_node || !animators.empty()) { |
| return false; |
| } |
| |
| path->addPath(path_node->getPath()); |
| } |
| } |
| |
| return true; |
| } |
| |
| sk_sp<sksg::RenderNode> |
| CustomFont::Builder::ParseGlyphComp(const AnimationBuilder* abuilder, |
| const skjson::ObjectValue& jdata, |
| SkSize* glyph_size) { |
| // Glyph comp encoding: |
| // |
| // "data": { // Follows the precomp layer format. |
| // "ip": <in point>, |
| // "op": <out point>, |
| // "refId": <comp ID>, |
| // "sr": <time remap info>, |
| // "st": <time remap info>, |
| // "ks": <transform info> |
| // } |
| |
| AnimationBuilder::LayerInfo linfo{ |
| {0,0}, |
| ParseDefault<float>(jdata["ip"], 0.0f), |
| ParseDefault<float>(jdata["op"], 0.0f) |
| }; |
| |
| if (!linfo.fInPoint && !linfo.fOutPoint) { |
| // Not a comp glyph. |
| return nullptr; |
| } |
| |
| // Since the glyph composition encoding matches the precomp layer encoding, we can pretend |
| // we're attaching a precomp here. |
| auto comp_node = abuilder->attachPrecompLayer(jdata, &linfo); |
| |
| // Normalize for 1pt. |
| static constexpr float kPtScale = 0.01f; |
| |
| // For bounds/alignment purposes, we use a glyph size matching the normalized glyph comp size. |
| *glyph_size = {linfo.fSize.fWidth * kPtScale, linfo.fSize.fHeight * kPtScale}; |
| |
| sk_sp<sksg::Transform> glyph_transform = |
| sksg::Matrix<SkMatrix>::Make(SkMatrix::Scale(kPtScale, kPtScale)); |
| |
| // Additional/explicit glyph transform (not handled in attachPrecompLayer). |
| if (const skjson::ObjectValue* jtransform = jdata["ks"]) { |
| glyph_transform = abuilder->attachMatrix2D(*jtransform, std::move(glyph_transform)); |
| } |
| |
| return sksg::TransformEffect::Make(abuilder->attachPrecompLayer(jdata, &linfo), |
| std::move(glyph_transform)); |
| } |
| |
| std::unique_ptr<CustomFont> CustomFont::Builder::detach() { |
| return std::unique_ptr<CustomFont>(new CustomFont(std::move(fGlyphComps), |
| fCustomBuilder.detach())); |
| } |
| |
| CustomFont::CustomFont(GlyphCompMap&& glyph_comps, sk_sp<SkTypeface> tf) |
| : fGlyphComps(std::move(glyph_comps)) |
| , fTypeface(std::move(tf)) |
| {} |
| |
| CustomFont::~CustomFont() = default; |
| |
| sk_sp<sksg::RenderNode> CustomFont::GlyphCompMapper::getGlyphComp(const SkTypeface* tf, |
| SkGlyphID gid) const { |
| for (const auto& font : fFonts) { |
| if (font->typeface().get() == tf) { |
| auto* comp_node = font->fGlyphComps.find(gid); |
| return comp_node ? *comp_node : nullptr; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace skottie::internal |