1 /*
2  * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
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 "frameworks/bridge/declarative_frontend/jsview/js_span.h"
17 #include "frameworks/bridge/declarative_frontend/jsview/js_container_span.h"
18 
19 #include <optional>
20 #include <sstream>
21 #include <string>
22 #include <vector>
23 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
24 #include "interfaces/inner_api/ui_session/ui_session_manager.h"
25 #endif
26 
27 #include "base/geometry/dimension.h"
28 #include "base/log/ace_scoring_log.h"
29 #include "base/log/ace_trace.h"
30 #include "base/utils/utils.h"
31 #include "bridge/common/utils/utils.h"
32 #include "bridge/declarative_frontend/engine/functions/js_click_function.h"
33 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
34 #include "bridge/declarative_frontend/jsview/js_utils.h"
35 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
36 #include "bridge/declarative_frontend/jsview/models/span_model_impl.h"
37 #include "bridge/declarative_frontend/jsview/models/text_model_impl.h"
38 #include "bridge/declarative_frontend/jsview/js_view_common_def.h"
39 #ifndef NG_BUILD
40 #include "bridge/declarative_frontend/view_stack_processor.h"
41 #endif
42 #include "bridge/declarative_frontend/jsview/js_text.h"
43 #include "core/common/container.h"
44 #include "core/components_ng/pattern/text/span_model.h"
45 #include "core/components_ng/pattern/text/span_model_ng.h"
46 #include "core/components_ng/pattern/text/text_model.h"
47 
48 namespace OHOS::Ace {
49 
50 std::unique_ptr<SpanModel> SpanModel::instance_ = nullptr;
51 std::mutex SpanModel::mutex_;
52 
GetInstance()53 SpanModel* SpanModel::GetInstance()
54 {
55 #ifdef NG_BUILD
56     static NG::SpanModelNG instance;
57     return &instance;
58 #else
59     if (Container::IsCurrentUseNewPipeline()) {
60         static NG::SpanModelNG instance;
61         return &instance;
62     } else {
63         static Framework::SpanModelImpl instance;
64         return &instance;
65     }
66 #endif
67 }
68 
69 } // namespace OHOS::Ace
70 
71 namespace OHOS::Ace::Framework {
72 namespace {
73 
74 const std::vector<FontStyle> FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC };
75 const std::vector<TextCase> TEXT_CASES = { TextCase::NORMAL, TextCase::LOWERCASE, TextCase::UPPERCASE };
76 constexpr TextDecorationStyle DEFAULT_TEXT_DECORATION_STYLE = TextDecorationStyle::SOLID;
77 
78 } // namespace
79 
SetFont(const JSCallbackInfo& info)80 void JSSpan::SetFont(const JSCallbackInfo& info)
81 {
82     if (info.Length() < 1) {
83         return;
84     }
85     auto infoZero = info[0];
86     if (infoZero->IsUndefined() || infoZero->IsNull()) {
87         SpanModel::GetInstance()->ResetFont();
88         return;
89     }
90     Font font;
91     JSText::GetFontInfo(info, font);
92     SpanModel::GetInstance()->SetFont(font);
93 }
94 
SetFontSize(const JSCallbackInfo& info)95 void JSSpan::SetFontSize(const JSCallbackInfo& info)
96 {
97     if (info.Length() < 1) {
98         return;
99     }
100     CalcDimension fontSize;
101     if (!ParseJsDimensionFpNG(info[0], fontSize, false) || fontSize.IsNegative()) {
102         SpanModel::GetInstance()->ResetFontSize();
103         return;
104     }
105     SpanModel::GetInstance()->SetFontSize(fontSize);
106 }
107 
SetFontWeight(const std::string& value)108 void JSSpan::SetFontWeight(const std::string& value)
109 {
110     SpanModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(value));
111 }
112 
SetTextColor(const JSCallbackInfo& info)113 void JSSpan::SetTextColor(const JSCallbackInfo& info)
114 {
115     Color textColor;
116     auto infoZero = info[0];
117     if (infoZero->IsUndefined() || infoZero->IsNull()) {
118         SpanModel::GetInstance()->ResetTextColor();
119         return;
120     }
121     if (!ParseJsColor(infoZero, textColor)) {
122         SpanModel::GetInstance()->ResetTextColor();
123         return;
124     }
125     SpanModel::GetInstance()->SetTextColor(textColor);
126 }
127 
SetFontStyle(int32_t value)128 void JSSpan::SetFontStyle(int32_t value)
129 {
130     if (value >= 0 && value < static_cast<int32_t>(FONT_STYLES.size())) {
131         auto style = FONT_STYLES[value];
132         SpanModel::GetInstance()->SetItalicFontStyle(style);
133     } else {
134         SpanModel::GetInstance()->ResetItalicFontStyle();
135     }
136 }
137 
SetFontFamily(const JSCallbackInfo& info)138 void JSSpan::SetFontFamily(const JSCallbackInfo& info)
139 {
140     if (info.Length() < 1) {
141         return;
142     }
143     std::vector<std::string> fontFamilies;
144     if (!ParseJsFontFamilies(info[0], fontFamilies)) {
145         SpanModel::GetInstance()->ResetFontFamily();
146         return;
147     }
148     SpanModel::GetInstance()->SetFontFamily(fontFamilies);
149 }
150 
SetLetterSpacing(const JSCallbackInfo& info)151 void JSSpan::SetLetterSpacing(const JSCallbackInfo& info)
152 {
153     if (info.Length() < 1) {
154         return;
155     }
156     CalcDimension value;
157     if (!ParseJsDimensionFpNG(info[0], value, false)) {
158         value.Reset();
159         SpanModel::GetInstance()->SetLetterSpacing(value);
160         return;
161     }
162     SpanModel::GetInstance()->SetLetterSpacing(value);
163 }
164 
SetBaselineOffset(const JSCallbackInfo& info)165 void JSSpan::SetBaselineOffset(const JSCallbackInfo& info)
166 {
167     if (info.Length() < 1) {
168         return;
169     }
170     NG::CalcLength value;
171     if (ConvertFromJSValueNG(info[0], value) &&
172         value.GetDimensionContainsNegative().Unit() != DimensionUnit::PERCENT) {
173         SpanModel::GetInstance()->SetBaselineOffset(value.GetDimensionContainsNegative());
174         return;
175     }
176     value.Reset();
177     SpanModel::GetInstance()->SetBaselineOffset(value.GetDimensionContainsNegative());
178 }
179 
SetTextCase(int32_t value)180 void JSSpan::SetTextCase(int32_t value)
181 {
182     if (value >= 0 && value < static_cast<int32_t>(TEXT_CASES.size())) {
183         auto textCase = TEXT_CASES[value];
184         SpanModel::GetInstance()->SetTextCase(textCase);
185     }
186 }
187 
SetDecoration(const JSCallbackInfo& info)188 void JSSpan::SetDecoration(const JSCallbackInfo& info)
189 {
190     if (info[0]->IsUndefined()) {
191         SpanModel::GetInstance()->SetTextDecoration(TextDecoration::NONE);
192         return;
193     }
194     if (!info[0]->IsObject()) {
195         return;
196     }
197     JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
198     JSRef<JSVal> typeValue = obj->GetProperty("type");
199     JSRef<JSVal> colorValue = obj->GetProperty("color");
200     JSRef<JSVal> styleValue = obj->GetProperty("style");
201 
202     std::optional<TextDecoration> textDecoration;
203     if (typeValue->IsNumber()) {
204         textDecoration = static_cast<TextDecoration>(typeValue->ToNumber<int32_t>());
205     } else {
206         auto theme = GetTheme<TextTheme>();
207         CHECK_NULL_VOID(theme);
208         textDecoration = theme->GetTextStyle().GetTextDecoration();
209     }
210     std::optional<TextDecorationStyle> textDecorationStyle;
211     if (styleValue->IsNumber()) {
212         textDecorationStyle = static_cast<TextDecorationStyle>(styleValue->ToNumber<int32_t>());
213     } else {
214         textDecorationStyle = DEFAULT_TEXT_DECORATION_STYLE;
215     }
216     std::optional<Color> colorVal;
217     Color result;
218     if (ParseJsColor(colorValue, result)) {
219         colorVal = result;
220     } else {
221         auto theme = GetTheme<TextTheme>();
222         CHECK_NULL_VOID(theme);
223         if (SystemProperties::GetColorMode() == ColorMode::DARK) {
224             colorVal = theme->GetTextStyle().GetTextColor();
225         } else {
226             colorVal = Color::BLACK;
227         }
228     }
229     SpanModel::GetInstance()->SetTextDecoration(textDecoration.value());
230     SpanModel::GetInstance()->SetTextDecorationColor(colorVal.value());
231     if (textDecorationStyle) {
232         SpanModel::GetInstance()->SetTextDecorationStyle(textDecorationStyle.value());
233     }
234 }
235 
JsOnClick(const JSCallbackInfo& info)236 void JSSpan::JsOnClick(const JSCallbackInfo& info)
237 {
238     if (Container::IsCurrentUseNewPipeline()) {
239         if (info[0]->IsUndefined() && IsDisableEventVersion()) {
240             SpanModel::GetInstance()->ClearOnClick();
241             return;
242         }
243         if (!info[0]->IsFunction()) {
244             return;
245         }
246         auto jsOnClickFunc = AceType::MakeRefPtr<JsClickFunction>(JSRef<JSFunc>::Cast(info[0]));
247         auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
248         auto onClick = [execCtx = info.GetExecutionContext(), func = jsOnClickFunc, node = targetNode](
249                            BaseEventInfo* info) {
250             auto* clickInfo = TypeInfoHelper::DynamicCast<GestureEvent>(info);
251             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
252             ACE_SCORING_EVENT("onClick");
253             PipelineContext::SetCallBackNode(node);
254             func->Execute(*clickInfo);
255 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
256             JSInteractableView::ReportClickEvent(node);
257 #endif
258         };
259         SpanModel::GetInstance()->SetOnClick(std::move(onClick));
260         return;
261     }
262 #ifndef NG_BUILD
263     if (info[0]->IsFunction()) {
264         auto inspector = ViewStackProcessor::GetInstance()->GetInspectorComposedComponent();
265         CHECK_NULL_VOID(inspector);
266         auto impl = inspector->GetInspectorFunctionImpl();
267         RefPtr<JsClickFunction> jsOnClickFunc = AceType::MakeRefPtr<JsClickFunction>(JSRef<JSFunc>::Cast(info[0]));
268         auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
269         auto clickFunc = [execCtx = info.GetExecutionContext(), func = std::move(jsOnClickFunc), impl,
270                              node = targetNode](const BaseEventInfo* info) {
271             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
272             const auto* clickInfo = TypeInfoHelper::DynamicCast<ClickInfo>(info);
273             auto newInfo = *clickInfo;
274             if (impl) {
275                 impl->UpdateEventInfo(newInfo);
276             }
277             ACE_SCORING_EVENT("Span.onClick");
278             PipelineContext::SetCallBackNode(node);
279             func->Execute(newInfo);
280         };
281         SpanModel::GetInstance()->SetOnClick(std::move(clickFunc));
282     }
283 #endif
284 }
285 
JsRemoteMessage(const JSCallbackInfo& info)286 void JSSpan::JsRemoteMessage(const JSCallbackInfo& info)
287 {
288 #ifndef NG_BUILD
289     RemoteCallback remoteCallback;
290     JSInteractableView::JsRemoteMessage(info, remoteCallback);
291     EventMarker remoteMessageEventId(std::move(remoteCallback));
292     auto* stack = ViewStackProcessor::GetInstance();
293     auto textSpanComponent = AceType::DynamicCast<TextSpanComponent>(stack->GetMainComponent());
294     textSpanComponent->SetRemoteMessageEventId(remoteMessageEventId);
295 #endif
296 }
297 
SetLineHeight(const JSCallbackInfo& info)298 void JSSpan::SetLineHeight(const JSCallbackInfo& info)
299 {
300     CalcDimension value;
301     if (!ParseJsDimensionFpNG(info[0], value)) {
302         value.Reset();
303         SpanModel::GetInstance()->SetLineHeight(value);
304         return;
305     }
306     if (value.IsNegative()) {
307         value.Reset();
308     }
309     SpanModel::GetInstance()->SetLineHeight(value);
310 }
311 
SetTextShadow(const JSCallbackInfo& info)312 void JSSpan::SetTextShadow(const JSCallbackInfo& info)
313 {
314     if (info.Length() < 1) {
315         return;
316     }
317     std::vector<Shadow> shadows;
318     ParseTextShadowFromShadowObject(info[0], shadows);
319     SpanModel::GetInstance()->SetTextShadow(shadows);
320 }
321 
322 
SetAccessibilityText(const JSCallbackInfo& info)323 void JSSpan::SetAccessibilityText(const JSCallbackInfo& info)
324 {
325     std::string text;
326     if ((info.Length() > 0) && info[0]->IsString()) {
327         text = info[0]->ToString();
328     }
329     SpanModel::GetInstance()->SetAccessibilityText(text);
330 }
331 
SetAccessibilityDescription(const JSCallbackInfo& info)332 void JSSpan::SetAccessibilityDescription(const JSCallbackInfo& info)
333 {
334     std::string description;
335     if ((info.Length() > 0) && info[0]->IsString()) {
336         description = info[0]->ToString();
337     }
338     SpanModel::GetInstance()->SetAccessibilityDescription(description);
339 }
340 
SetAccessibilityLevel(const JSCallbackInfo& info)341 void JSSpan::SetAccessibilityLevel(const JSCallbackInfo& info)
342 {
343     std::string level;
344     if ((info.Length() > 0) && info[0]->IsString()) {
345         level = info[0]->ToString();
346     }
347     SpanModel::GetInstance()->SetAccessibilityImportance(level);
348 }
349 
JSBind(BindingTarget globalObj)350 void JSSpan::JSBind(BindingTarget globalObj)
351 {
352     JSClass<JSSpan>::Declare("Span");
353     MethodOptions opt = MethodOptions::NONE;
354     JSClass<JSSpan>::StaticMethod("create", &JSSpan::Create, opt);
355     JSClass<JSSpan>::StaticMethod("font", &JSSpan::SetFont, opt);
356     JSClass<JSSpan>::StaticMethod("fontColor", &JSSpan::SetTextColor, opt);
357     JSClass<JSSpan>::StaticMethod("fontSize", &JSSpan::SetFontSize, opt);
358     JSClass<JSSpan>::StaticMethod("fontWeight", &JSSpan::SetFontWeight, opt);
359     JSClass<JSSpan>::StaticMethod("fontStyle", &JSSpan::SetFontStyle, opt);
360     JSClass<JSSpan>::StaticMethod("fontFamily", &JSSpan::SetFontFamily, opt);
361     JSClass<JSSpan>::StaticMethod("letterSpacing", &JSSpan::SetLetterSpacing, opt);
362     JSClass<JSSpan>::StaticMethod("baselineOffset", &JSSpan::SetBaselineOffset, opt);
363     JSClass<JSSpan>::StaticMethod("textCase", &JSSpan::SetTextCase, opt);
364     JSClass<JSSpan>::StaticMethod("textShadow", &JSSpan::SetTextShadow, opt);
365     JSClass<JSSpan>::StaticMethod("decoration", &JSSpan::SetDecoration);
366     JSClass<JSSpan>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
367     JSClass<JSSpan>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
368     JSClass<JSSpan>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
369     JSClass<JSSpan>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
370     JSClass<JSSpan>::StaticMethod("remoteMessage", &JSSpan::JsRemoteMessage);
371     JSClass<JSSpan>::StaticMethod("onClick", &JSSpan::JsOnClick);
372     JSClass<JSSpan>::StaticMethod("lineHeight", &JSSpan::SetLineHeight, opt);
373     JSClass<JSSpan>::StaticMethod("textBackgroundStyle", &JSContainerSpan::SetTextBackgroundStyle, opt);
374     JSClass<JSSpan>::StaticMethod("accessibilityText", &JSSpan::SetAccessibilityText, opt);
375     JSClass<JSSpan>::StaticMethod("accessibilityDescription", &JSSpan::SetAccessibilityDescription, opt);
376     JSClass<JSSpan>::StaticMethod("accessibilityLevel", &JSSpan::SetAccessibilityLevel, opt);
377     JSClass<JSSpan>::InheritAndBind<JSContainerBase>(globalObj);
378 }
379 
Create(const JSCallbackInfo& info)380 void JSSpan::Create(const JSCallbackInfo& info)
381 {
382     std::string label;
383     if (info.Length() > 0) {
384         ParseJsString(info[0], label);
385     }
386     SpanModel::GetInstance()->Create(label);
387 }
388 
389 } // namespace OHOS::Ace::Framework
390