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