• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 Huawei Device Co., Ltd.. All rights reserved.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "text_flip_effect.h"
17 
18 #include <algorithm>
19 
20 #include "symbol_engine/text_animation_config.h"
21 #include "text/hm_symbol.h"
22 #include "text_effect.h"
23 #include "text_effect_macro.h"
24 
25 namespace OHOS::Rosen {
26 static std::string g_scaleX = "sx";
27 static std::string g_scaleY = "sy";
28 static std::string g_alphaProp = "alpha";
29 static std::string g_blurProp = "blur";
30 static std::string g_translateX = "tx";
31 static std::string g_translateY = "ty";
32 
TextFlipEffect()33 TextFlipEffect::TextFlipEffect()
34 {
35     strategy_ = TextEffectStrategy::FLIP;
36 }
37 
~TextFlipEffect()38 TextFlipEffect::~TextFlipEffect()
39 {
40     typographyConfig_.typography = nullptr;
41     typographyConfig_.rawTextRange = {0, 0};
42 }
43 
CheckInputParams(const std::unordered_map<TextEffectAttribute,std::string> & config)44 bool TextFlipEffect::CheckInputParams(const std::unordered_map<TextEffectAttribute, std::string>& config)
45 {
46     for (const auto& [key, value] : config) {
47         auto iter = std::find_if(supportAttributes_.begin(), supportAttributes_.end(),
48             [key = key](const FlipAttributeFunction& func) { return func.attribute == key; });
49         if (iter == supportAttributes_.end()) {
50             continue;
51         }
52         if (!iter->checkFunc(this, value)) {
53             return false;
54         }
55     }
56     return true;
57 }
58 
CheckDirection(const std::string & direction)59 bool TextFlipEffect::CheckDirection(const std::string& direction)
60 {
61     return direction == "up" || direction == "down";
62 }
63 
SetDirection(const std::string & direction)64 void TextFlipEffect::SetDirection(const std::string& direction)
65 {
66     if (direction == "up") {
67         direction_ = TextEffectFlipDirection::UP;
68     } else if (direction == "down") {
69         direction_ = TextEffectFlipDirection::DOWN;
70     }
71 }
72 
CheckBlurEnable(const std::string & enable)73 bool TextFlipEffect::CheckBlurEnable(const std::string& enable)
74 {
75     return enable == "true" || enable == "false";
76 }
77 
SetBlurEnable(const std::string & enable)78 void TextFlipEffect::SetBlurEnable(const std::string& enable)
79 {
80     blurEnable_ = enable == "true";
81 }
82 
UpdateEffectConfig(const std::unordered_map<TextEffectAttribute,std::string> & config)83 int TextFlipEffect::UpdateEffectConfig(const std::unordered_map<TextEffectAttribute, std::string>& config)
84 {
85     if (config.empty()) {
86         return TEXT_EFFECT_SUCCESS;
87     }
88 
89     if (!CheckInputParams(config)) {
90         return TEXT_EFFECT_INVALID_INPUT;
91     }
92 
93     for (const auto& [key, value] : config) {
94         auto iter = std::find_if(supportAttributes_.begin(), supportAttributes_.end(),
95             [key = key](const FlipAttributeFunction& func) { return func.attribute == key; });
96         if (iter == supportAttributes_.end()) {
97             continue;
98         }
99         iter->setFunc(this, value);
100     }
101     return TEXT_EFFECT_SUCCESS;
102 }
103 
AppendTypography(const std::vector<TypographyConfig> & typographyConfigs)104 int TextFlipEffect::AppendTypography(const std::vector<TypographyConfig>& typographyConfigs)
105 {
106     if (typographyConfigs.empty() || typographyConfigs[0].typography == nullptr) {
107         return TEXT_EFFECT_INVALID_INPUT;
108     }
109 
110     if (typographyConfig_.typography != nullptr) {
111         return TEXT_EFFECT_UNKNOWN;
112     }
113 
114     typographyConfig_.typography = typographyConfigs[0].typography;
115     typographyConfig_.rawTextRange = typographyConfigs[0].rawTextRange;
116     typographyConfig_.typography->SetTextEffectAssociation(true);
117     return TEXT_EFFECT_SUCCESS;
118 }
119 
UpdateTypography(std::vector<std::pair<TypographyConfig,TypographyConfig>> & typographyConfigs)120 int TextFlipEffect::UpdateTypography(std::vector<std::pair<TypographyConfig, TypographyConfig>>& typographyConfigs)
121 {
122     if (typographyConfigs.empty() || typographyConfigs[0].first.typography != typographyConfig_.typography ||
123         typographyConfigs[0].second.typography == nullptr) {
124         return TEXT_EFFECT_INVALID_INPUT;
125     }
126     if (typographyConfig_.typography == nullptr) {
127         return TEXT_EFFECT_UNKNOWN;
128     }
129     typographyConfig_.typography->SetTextEffectAssociation(false);
130     typographyConfig_.typography = typographyConfigs[0].second.typography;
131     typographyConfig_.rawTextRange = typographyConfigs[0].second.rawTextRange;
132     typographyConfig_.typography->SetTextEffectAssociation(true);
133     return TEXT_EFFECT_SUCCESS;
134 }
135 
RemoveTypography(const std::vector<TypographyConfig> & typographyConfigs)136 void TextFlipEffect::RemoveTypography(const std::vector<TypographyConfig>& typographyConfigs)
137 {
138     if (typographyConfigs.empty()) {
139         ClearTypography();
140         return;
141     }
142     if (typographyConfig_.typography != typographyConfigs[0].typography) {
143         return;
144     }
145     ClearTypography();
146 }
147 
StartEffect(Drawing::Canvas * canvas,double x,double y)148 void TextFlipEffect::StartEffect(Drawing::Canvas* canvas, double x, double y)
149 {
150     if (typographyConfig_.typography == nullptr) {
151         return;
152     }
153     typographyConfig_.typography->SetSkipTextBlobDrawing(true);
154     typographyConfig_.typography->Paint(canvas, x, y);
155     std::vector<TextBlobRecordInfo> textBlobRecordInfos = typographyConfig_.typography->GetTextBlobRecordInfo();
156     DrawTextFlip(textBlobRecordInfos, canvas, x, y);
157 
158     lastAllBlobGlyphIds_.swap(currentBlobGlyphIds_);
159     std::vector<uint16_t>().swap(currentBlobGlyphIds_);
160     currentGlyphIndex_ = 0;
161     changeNumber_ = 0;
162 }
163 
StopEffect()164 void TextFlipEffect::StopEffect()
165 {
166     if (typographyConfig_.typography == nullptr) {
167         return;
168     }
169 
170     typographyConfig_.typography->SetSkipTextBlobDrawing(false);
171     SPText::TextAnimationConfig textEffectConfig;
172     auto animationFunc = typographyConfig_.typography->GetAnimation();
173     textEffectConfig.SetAnimation(animationFunc);
174     textEffectConfig.ClearAllTextAnimation();
175     std::vector<uint16_t>().swap(lastAllBlobGlyphIds_);
176 }
177 
NoEffect(Drawing::Canvas * canvas,double x,double y)178 void TextFlipEffect::NoEffect(Drawing::Canvas* canvas, double x, double y)
179 {
180     if (typographyConfig_.typography == nullptr) {
181         return;
182     }
183     bool lastSkipState = typographyConfig_.typography->HasSkipTextBlobDrawing();
184     typographyConfig_.typography->SetSkipTextBlobDrawing(false);
185     typographyConfig_.typography->Paint(canvas, x, y);
186     typographyConfig_.typography->SetSkipTextBlobDrawing(lastSkipState);
187 }
188 
189 // LCOV_EXCL_START
DrawTextFlip(std::vector<TextBlobRecordInfo> & infos,Drawing::Canvas * canvas,double x,double y)190 void TextFlipEffect::DrawTextFlip(std::vector<TextBlobRecordInfo>& infos, Drawing::Canvas* canvas, double x, double y)
191 {
192     if (canvas == nullptr) {
193         return;
194     }
195     auto animationFunc = typographyConfig_.typography->GetAnimation();
196     double height = 0;
197     for (auto& info : infos) {
198         std::vector<std::vector<TextEngine::TextEffectElement>> effectElements = GenerateChangeElements(info.blob,
199             x + info.offset.fX, y + info.offset.fY);
200         if (effectElements.empty()) {
201             Drawing::Brush brush;
202             brush.SetColor(info.color);
203             canvas->AttachBrush(brush);
204             canvas->DrawTextBlob(info.blob.get(), x + info.offset.fX, y + info.offset.fY);
205             canvas->DetachBrush();
206             continue;
207         }
208         if (info.blob == nullptr || info.blob->Bounds() == nullptr) {
209             continue;
210         }
211         height = info.blob->Bounds()->GetHeight();
212         DrawTextFlipElements(canvas, height, info.color, animationFunc, effectElements);
213     }
214     DrawResidualText(canvas, height, animationFunc);
215 }
216 
DrawResidualText(Drawing::Canvas * canvas,double height,const std::function<bool (const std::shared_ptr<TextEngine::SymbolAnimationConfig> &)> & func)217 void TextFlipEffect::DrawResidualText(Drawing::Canvas* canvas, double height,
218     const std::function<bool(const std::shared_ptr<TextEngine::SymbolAnimationConfig>&)>& func)
219 {
220     if (currentGlyphIndex_ >= lastAllBlobGlyphIds_.size()) {
221         return;
222     }
223 
224     std::vector<std::vector<TextEngine::TextEffectElement>> effectElements;
225     std::vector<TextEngine::TextEffectElement> unEffectElements;
226     std::vector<TextEngine::TextEffectElement> inEffectElements;
227     for (size_t index = currentGlyphIndex_; index < lastAllBlobGlyphIds_.size(); index++) {
228         TextEngine::TextEffectElement effectElement;
229         effectElement.uniqueId = index;
230         effectElement.delay = 0;
231         inEffectElements.emplace_back(effectElement);
232     }
233     effectElements.emplace_back(unEffectElements); // index 0 is unchanged
234     effectElements.emplace_back(inEffectElements); // index 1 is changed
235     DrawTextFlipElements(canvas, height, Drawing::Color::COLOR_BLACK, func, effectElements);
236 }
237 
DrawTextFlipElements(Drawing::Canvas * canvas,double height,const Drawing::Color & color,const std::function<bool (const std::shared_ptr<TextEngine::SymbolAnimationConfig> &)> & func,const std::vector<std::vector<TextEngine::TextEffectElement>> & effectElements)238 void TextFlipEffect::DrawTextFlipElements(Drawing::Canvas* canvas, double height, const Drawing::Color& color,
239     const std::function<bool(const std::shared_ptr<TextEngine::SymbolAnimationConfig>&)>& func,
240     const std::vector<std::vector<TextEngine::TextEffectElement>>& effectElements)
241 {
242     const size_t unchangeIndex = 0;
243     const size_t changeIndex = 1;
244     std::vector<std::vector<Drawing::DrawingPiecewiseParameter>> parameters = GenerateFlipConfig(height);
245     SPText::TextAnimationConfig textEffectConfig;
246     textEffectConfig.SetAnimation(func);
247     textEffectConfig.SetAnimationStart(true);
248     textEffectConfig.SetColor(color);
249     textEffectConfig.SetEffectStrategy(Drawing::DrawingEffectStrategy::TEXT_FLIP);
250     textEffectConfig.SetEffectConfig(parameters);
251     textEffectConfig.AnimationUnchange(true);
252     textEffectConfig.DrawTextEffect(canvas, effectElements[unchangeIndex]);
253     textEffectConfig.AnimationUnchange(false);
254     textEffectConfig.DrawTextEffect(canvas, effectElements[changeIndex]);
255 }
256 
ClearTypography()257 void TextFlipEffect::ClearTypography()
258 {
259     if (typographyConfig_.typography == nullptr) {
260         return;
261     }
262     typographyConfig_.typography->SetTextEffectAssociation(false);
263     typographyConfig_.typography = nullptr;
264     typographyConfig_.rawTextRange = {0, 0};
265 }
266 
GenerateChangeElements(const std::shared_ptr<Drawing::TextBlob> & blob,double x,double y)267 std::vector<std::vector<TextEngine::TextEffectElement>> TextFlipEffect::GenerateChangeElements(
268     const std::shared_ptr<Drawing::TextBlob>& blob, double x, double y)
269 {
270     const int delayTimeMs = 100;
271     std::vector<std::vector<TextEngine::TextEffectElement>> effectElements;
272     std::vector<TextEngine::TextEffectElement> unEffectElements;
273     std::vector<TextEngine::TextEffectElement> inEffectElements;
274     std::vector<Drawing::Point> points;
275     Drawing::TextBlob::GetDrawingPointsForTextBlob(blob.get(), points);
276     std::vector<uint16_t> glyphIds;
277     Drawing::TextBlob::GetDrawingGlyphIDforTextBlob(blob.get(), glyphIds);
278     currentBlobGlyphIds_.insert(currentBlobGlyphIds_.end(), glyphIds.begin(), glyphIds.end());
279 
280     for (size_t index = 0; index < glyphIds.size(); index++) {
281         TextEngine::TextEffectElement effectElement;
282         effectElement.path = Drawing::TextBlob::GetDrawingPathforTextBlob(glyphIds[index], blob.get());
283         if (!effectElement.path.IsValid()) {
284             return {};
285         }
286         effectElement.offset = Drawing::Point{x + points[index].GetX(), y + points[index].GetY()};
287         auto rect = effectElement.path.GetBounds();
288         effectElement.width = rect.GetWidth();
289         effectElement.height = rect.GetHeight();
290         effectElement.uniqueId = currentGlyphIndex_ + index;
291         effectElement.delay = changeNumber_ * delayTimeMs;
292         if (currentGlyphIndex_ + index < lastAllBlobGlyphIds_.size() &&
293             lastAllBlobGlyphIds_[currentGlyphIndex_ + index] == glyphIds[index]) {
294             unEffectElements.emplace_back(effectElement);
295         } else {
296             inEffectElements.emplace_back(effectElement);
297             changeNumber_++;
298         }
299     }
300     effectElements.emplace_back(unEffectElements); // index 0 is unchanged
301     effectElements.emplace_back(inEffectElements); // index 1 is changed
302     currentGlyphIndex_ += glyphIds.size();
303     return effectElements;
304 }
305 
GenerateFlipConfig(double height)306 std::vector<std::vector<Drawing::DrawingPiecewiseParameter>> TextFlipEffect::GenerateFlipConfig(double height)
307 {
308     std::vector<std::vector<Drawing::DrawingPiecewiseParameter>> parameters;
309     size_t paramLength = 2; // index 0 is out, index 1 is in
310     parameters.resize(paramLength);
311     std::map<std::string, float> inFrictionCurve = {{"ctrlX1", 0.2}, {"ctrlY1", 0}, {"ctrlX2", 0.2}, {"ctrlY2", 1}};
312     std::map<std::string, float> inFrictionCurve2 = {{"ctrlX1", 0}, {"ctrlY1", 0}, {"ctrlX2", 0}, {"ctrlY2", 1}};
313     std::map<std::string, float> sharpCurve = {{"ctrlX1", 0.33}, {"ctrlY1", 0}, {"ctrlX2", 0.67}, {"ctrlY2", 1}};
314     std::map<std::string, float> outFrictionCurve = {
315         {"ctrlX1", 0.83}, {"ctrlY1", 0}, {"ctrlX2", 0.95}, {"ctrlY2", 0.77}
316     };
317     int factor = direction_ == TextEffectFlipDirection::DOWN ? 1 : -1;
318 
319     Drawing::DrawingPiecewiseParameter outTranslateParameter = { Drawing::DrawingCurveType::FRICTION,
320         outFrictionCurve, 100, 0, {{g_translateX, {0, 0}}, {g_translateY, {0, 0.5 * height * factor}}}};
321     parameters[0].emplace_back(outTranslateParameter);
322 
323     Drawing::DrawingPiecewiseParameter outScaleParameter = { Drawing::DrawingCurveType::FRICTION,
324         outFrictionCurve, 100, 0, {{g_scaleX, {1, 0.6}}, {g_scaleY, {1, 0.6}}}};
325     parameters[0].emplace_back(outScaleParameter);
326 
327     Drawing::DrawingPiecewiseParameter outAlphaParameter = { Drawing::DrawingCurveType::LINEAR,
328         sharpCurve, 100, 0, {{g_alphaProp, {1, 0}}}};
329     parameters[0].emplace_back(outAlphaParameter);
330 
331     Drawing::DrawingPiecewiseParameter inTranslateParameter1 = {
332         Drawing::DrawingCurveType::FRICTION, inFrictionCurve, 175, 0,
333         {{g_translateX, {0, 0}}, {g_translateY, {-0.6 * height * factor, 0.1 * height * factor}}}
334     };
335     parameters[1].emplace_back(inTranslateParameter1);
336 
337     Drawing::DrawingPiecewiseParameter inTranslateParameter2 = { Drawing::DrawingCurveType::FRICTION,
338         inFrictionCurve2, 175, 175, {{g_translateX, {0, 0}}, {g_translateY, {0.1 * height * factor, 0}}}};
339     parameters[1].emplace_back(inTranslateParameter2);
340 
341     Drawing::DrawingPiecewiseParameter inScaleParameter = { Drawing::DrawingCurveType::FRICTION,
342         inFrictionCurve, 350, 0, {{g_scaleX, {0.8, 1.0}}, {g_scaleY, {0.8, 1.0}}}};
343     parameters[1].emplace_back(inScaleParameter);
344 
345     Drawing::DrawingPiecewiseParameter inAlphaParameter = { Drawing::DrawingCurveType::SHARP,
346         sharpCurve, 150, 0, {{g_alphaProp, {0, 1}}}};
347     parameters[1].emplace_back(inAlphaParameter);
348 
349     if (blurEnable_) {
350         Drawing::DrawingPiecewiseParameter outBlurParameter = {
351             Drawing::DrawingCurveType::LINEAR, sharpCurve, 100, 0, {{g_blurProp, {0, 25}}}
352         };
353         parameters[0].emplace_back(outBlurParameter);
354 
355         Drawing::DrawingPiecewiseParameter inBlurParameter = {
356             Drawing::DrawingCurveType::SHARP, sharpCurve, 150, 0, {{g_blurProp, {25, 0}}}
357         };
358         parameters[1].emplace_back(inBlurParameter);
359     }
360     return parameters;
361 }
362 // LCOV_EXCL_STOP
363 
364 REGISTER_TEXT_EFFECT_FACTORY_IMPL(Flip, TextEffectStrategy::FLIP);
365 } // namespace OHOS::Rosen