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     Font font;
83     JSText::GetFontInfo(info, font);
84     SpanModel::GetInstance()->SetFont(font);
85 }
86 
SetFontSize(const JSCallbackInfo & info)87 void JSSpan::SetFontSize(const JSCallbackInfo& info)
88 {
89     if (info.Length() < 1) {
90         return;
91     }
92     CalcDimension fontSize;
93     if (!ParseJsDimensionFpNG(info[0], fontSize, false) || fontSize.IsNegative()) {
94         auto pipelineContext = PipelineBase::GetCurrentContext();
95         CHECK_NULL_VOID(pipelineContext);
96         auto theme = pipelineContext->GetTheme<TextTheme>();
97         CHECK_NULL_VOID(theme);
98         fontSize = theme->GetTextStyle().GetFontSize();
99         SpanModel::GetInstance()->SetFontSize(fontSize);
100         return;
101     }
102 
103     SpanModel::GetInstance()->SetFontSize(fontSize);
104 }
105 
SetFontWeight(const std::string & value)106 void JSSpan::SetFontWeight(const std::string& value)
107 {
108     SpanModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(value));
109 }
110 
SetTextColor(const JSCallbackInfo & info)111 void JSSpan::SetTextColor(const JSCallbackInfo& info)
112 {
113     Color textColor;
114     if (!ParseJsColor(info[0], textColor)) {
115         auto pipelineContext = PipelineBase::GetCurrentContext();
116         CHECK_NULL_VOID(pipelineContext);
117         auto theme = pipelineContext->GetTheme<TextTheme>();
118         CHECK_NULL_VOID(theme);
119         textColor = theme->GetTextStyle().GetTextColor();
120     }
121     SpanModel::GetInstance()->SetTextColor(textColor);
122 }
123 
SetFontStyle(int32_t value)124 void JSSpan::SetFontStyle(int32_t value)
125 {
126     if (value >= 0 && value < static_cast<int32_t>(FONT_STYLES.size())) {
127         auto style = FONT_STYLES[value];
128         SpanModel::GetInstance()->SetItalicFontStyle(style);
129     }
130 }
131 
SetFontFamily(const JSCallbackInfo & info)132 void JSSpan::SetFontFamily(const JSCallbackInfo& info)
133 {
134     if (info.Length() < 1) {
135         return;
136     }
137     std::vector<std::string> fontFamilies;
138     if (!ParseJsFontFamilies(info[0], fontFamilies)) {
139         return;
140     }
141     SpanModel::GetInstance()->SetFontFamily(fontFamilies);
142 }
143 
SetLetterSpacing(const JSCallbackInfo & info)144 void JSSpan::SetLetterSpacing(const JSCallbackInfo& info)
145 {
146     if (info.Length() < 1) {
147         return;
148     }
149     CalcDimension value;
150     if (!ParseJsDimensionFpNG(info[0], value, false)) {
151         value.Reset();
152         SpanModel::GetInstance()->SetLetterSpacing(value);
153         return;
154     }
155     SpanModel::GetInstance()->SetLetterSpacing(value);
156 }
157 
SetBaselineOffset(const JSCallbackInfo & info)158 void JSSpan::SetBaselineOffset(const JSCallbackInfo& info)
159 {
160     if (info.Length() < 1) {
161         return;
162     }
163     NG::CalcLength value;
164     if (ConvertFromJSValueNG(info[0], value) &&
165         value.GetDimensionContainsNegative().Unit() != DimensionUnit::PERCENT) {
166         SpanModel::GetInstance()->SetBaselineOffset(value.GetDimensionContainsNegative());
167         return;
168     }
169     value.Reset();
170     SpanModel::GetInstance()->SetBaselineOffset(value.GetDimensionContainsNegative());
171 }
172 
SetTextCase(int32_t value)173 void JSSpan::SetTextCase(int32_t value)
174 {
175     if (value >= 0 && value < static_cast<int32_t>(TEXT_CASES.size())) {
176         auto textCase = TEXT_CASES[value];
177         SpanModel::GetInstance()->SetTextCase(textCase);
178     }
179 }
180 
SetDecoration(const JSCallbackInfo & info)181 void JSSpan::SetDecoration(const JSCallbackInfo& info)
182 {
183     if (info[0]->IsUndefined()) {
184         SpanModel::GetInstance()->SetTextDecoration(TextDecoration::NONE);
185         return;
186     }
187     if (!info[0]->IsObject()) {
188         return;
189     }
190     JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
191     JSRef<JSVal> typeValue = obj->GetProperty("type");
192     JSRef<JSVal> colorValue = obj->GetProperty("color");
193     JSRef<JSVal> styleValue = obj->GetProperty("style");
194 
195     std::optional<TextDecoration> textDecoration;
196     if (typeValue->IsNumber()) {
197         textDecoration = static_cast<TextDecoration>(typeValue->ToNumber<int32_t>());
198     } else {
199         auto theme = GetTheme<TextTheme>();
200         CHECK_NULL_VOID(theme);
201         textDecoration = theme->GetTextStyle().GetTextDecoration();
202     }
203     std::optional<TextDecorationStyle> textDecorationStyle;
204     if (styleValue->IsNumber()) {
205         textDecorationStyle = static_cast<TextDecorationStyle>(styleValue->ToNumber<int32_t>());
206     } else {
207         textDecorationStyle = DEFAULT_TEXT_DECORATION_STYLE;
208     }
209     std::optional<Color> colorVal;
210     Color result;
211     if (ParseJsColor(colorValue, result)) {
212         colorVal = result;
213     } else {
214         auto theme = GetTheme<TextTheme>();
215         CHECK_NULL_VOID(theme);
216         if (SystemProperties::GetColorMode() == ColorMode::DARK) {
217             colorVal = theme->GetTextStyle().GetTextColor();
218         } else {
219             colorVal = Color::BLACK;
220         }
221     }
222     SpanModel::GetInstance()->SetTextDecoration(textDecoration.value());
223     SpanModel::GetInstance()->SetTextDecorationColor(colorVal.value());
224     if (textDecorationStyle) {
225         SpanModel::GetInstance()->SetTextDecorationStyle(textDecorationStyle.value());
226     }
227 }
228 
JsOnClick(const JSCallbackInfo & info)229 void JSSpan::JsOnClick(const JSCallbackInfo& info)
230 {
231     if (Container::IsCurrentUseNewPipeline()) {
232         if (info[0]->IsUndefined() && IsDisableEventVersion()) {
233             SpanModel::GetInstance()->ClearOnClick();
234             return;
235         }
236         if (!info[0]->IsFunction()) {
237             return;
238         }
239         auto jsOnClickFunc = AceType::MakeRefPtr<JsClickFunction>(JSRef<JSFunc>::Cast(info[0]));
240         auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
241         auto onClick = [execCtx = info.GetExecutionContext(), func = jsOnClickFunc, node = targetNode](
242                            BaseEventInfo* info) {
243             auto* clickInfo = TypeInfoHelper::DynamicCast<GestureEvent>(info);
244             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
245             ACE_SCORING_EVENT("onClick");
246             PipelineContext::SetCallBackNode(node);
247             func->Execute(*clickInfo);
248 #if !defined(PREVIEW) && defined(OHOS_PLATFORM)
249             JSInteractableView::ReportClickEvent(node);
250 #endif
251         };
252         SpanModel::GetInstance()->SetOnClick(std::move(onClick));
253         return;
254     }
255 #ifndef NG_BUILD
256     if (info[0]->IsFunction()) {
257         auto inspector = ViewStackProcessor::GetInstance()->GetInspectorComposedComponent();
258         CHECK_NULL_VOID(inspector);
259         auto impl = inspector->GetInspectorFunctionImpl();
260         RefPtr<JsClickFunction> jsOnClickFunc = AceType::MakeRefPtr<JsClickFunction>(JSRef<JSFunc>::Cast(info[0]));
261         auto targetNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
262         auto clickFunc = [execCtx = info.GetExecutionContext(), func = std::move(jsOnClickFunc), impl,
263                              node = targetNode](const BaseEventInfo* info) {
264             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
265             const auto* clickInfo = TypeInfoHelper::DynamicCast<ClickInfo>(info);
266             auto newInfo = *clickInfo;
267             if (impl) {
268                 impl->UpdateEventInfo(newInfo);
269             }
270             ACE_SCORING_EVENT("Span.onClick");
271             PipelineContext::SetCallBackNode(node);
272             func->Execute(newInfo);
273         };
274         SpanModel::GetInstance()->SetOnClick(std::move(clickFunc));
275     }
276 #endif
277 }
278 
JsRemoteMessage(const JSCallbackInfo & info)279 void JSSpan::JsRemoteMessage(const JSCallbackInfo& info)
280 {
281 #ifndef NG_BUILD
282     RemoteCallback remoteCallback;
283     JSInteractableView::JsRemoteMessage(info, remoteCallback);
284     EventMarker remoteMessageEventId(std::move(remoteCallback));
285     auto* stack = ViewStackProcessor::GetInstance();
286     auto textSpanComponent = AceType::DynamicCast<TextSpanComponent>(stack->GetMainComponent());
287     textSpanComponent->SetRemoteMessageEventId(remoteMessageEventId);
288 #endif
289 }
290 
SetLineHeight(const JSCallbackInfo & info)291 void JSSpan::SetLineHeight(const JSCallbackInfo& info)
292 {
293     CalcDimension value;
294     if (!ParseJsDimensionFpNG(info[0], value)) {
295         value.Reset();
296         SpanModel::GetInstance()->SetLineHeight(value);
297         return;
298     }
299     if (value.IsNegative()) {
300         value.Reset();
301     }
302     SpanModel::GetInstance()->SetLineHeight(value);
303 }
304 
SetTextShadow(const JSCallbackInfo & info)305 void JSSpan::SetTextShadow(const JSCallbackInfo& info)
306 {
307     if (info.Length() < 1) {
308         return;
309     }
310     std::vector<Shadow> shadows;
311     ParseTextShadowFromShadowObject(info[0], shadows);
312     SpanModel::GetInstance()->SetTextShadow(shadows);
313 }
314 
SetAccessibilityText(const JSCallbackInfo & info)315 void JSSpan::SetAccessibilityText(const JSCallbackInfo& info)
316 {
317     std::string text;
318     if ((info.Length() > 0) && info[0]->IsString()) {
319         text = info[0]->ToString();
320     }
321     SpanModel::GetInstance()->SetAccessibilityText(text);
322 }
323 
SetAccessibilityDescription(const JSCallbackInfo & info)324 void JSSpan::SetAccessibilityDescription(const JSCallbackInfo& info)
325 {
326     std::string description;
327     if ((info.Length() > 0) && info[0]->IsString()) {
328         description = info[0]->ToString();
329     }
330     SpanModel::GetInstance()->SetAccessibilityDescription(description);
331 }
332 
SetAccessibilityLevel(const JSCallbackInfo & info)333 void JSSpan::SetAccessibilityLevel(const JSCallbackInfo& info)
334 {
335     std::string level;
336     if ((info.Length() > 0) && info[0]->IsString()) {
337         level = info[0]->ToString();
338     }
339     SpanModel::GetInstance()->SetAccessibilityImportance(level);
340 }
341 
JSBind(BindingTarget globalObj)342 void JSSpan::JSBind(BindingTarget globalObj)
343 {
344     JSClass<JSSpan>::Declare("Span");
345     MethodOptions opt = MethodOptions::NONE;
346     JSClass<JSSpan>::StaticMethod("create", &JSSpan::Create, opt);
347     JSClass<JSSpan>::StaticMethod("font", &JSSpan::SetFont, opt);
348     JSClass<JSSpan>::StaticMethod("fontColor", &JSSpan::SetTextColor, opt);
349     JSClass<JSSpan>::StaticMethod("fontSize", &JSSpan::SetFontSize, opt);
350     JSClass<JSSpan>::StaticMethod("fontWeight", &JSSpan::SetFontWeight, opt);
351     JSClass<JSSpan>::StaticMethod("fontStyle", &JSSpan::SetFontStyle, opt);
352     JSClass<JSSpan>::StaticMethod("fontFamily", &JSSpan::SetFontFamily, opt);
353     JSClass<JSSpan>::StaticMethod("letterSpacing", &JSSpan::SetLetterSpacing, opt);
354     JSClass<JSSpan>::StaticMethod("baselineOffset", &JSSpan::SetBaselineOffset, opt);
355     JSClass<JSSpan>::StaticMethod("textCase", &JSSpan::SetTextCase, opt);
356     JSClass<JSSpan>::StaticMethod("textShadow", &JSSpan::SetTextShadow, opt);
357     JSClass<JSSpan>::StaticMethod("decoration", &JSSpan::SetDecoration);
358     JSClass<JSSpan>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
359     JSClass<JSSpan>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
360     JSClass<JSSpan>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
361     JSClass<JSSpan>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
362     JSClass<JSSpan>::StaticMethod("remoteMessage", &JSSpan::JsRemoteMessage);
363     JSClass<JSSpan>::StaticMethod("onClick", &JSSpan::JsOnClick);
364     JSClass<JSSpan>::StaticMethod("lineHeight", &JSSpan::SetLineHeight, opt);
365     JSClass<JSSpan>::StaticMethod("textBackgroundStyle", &JSContainerSpan::SetTextBackgroundStyle, opt);
366     JSClass<JSSpan>::StaticMethod("accessibilityText", &JSSpan::SetAccessibilityText, opt);
367     JSClass<JSSpan>::StaticMethod("accessibilityDescription", &JSSpan::SetAccessibilityDescription, opt);
368     JSClass<JSSpan>::StaticMethod("accessibilityLevel", &JSSpan::SetAccessibilityLevel, opt);
369     JSClass<JSSpan>::InheritAndBind<JSContainerBase>(globalObj);
370 }
371 
Create(const JSCallbackInfo & info)372 void JSSpan::Create(const JSCallbackInfo& info)
373 {
374     std::string label;
375     if (info.Length() > 0) {
376         ParseJsString(info[0], label);
377     }
378     SpanModel::GetInstance()->Create(label);
379 }
380 
381 } // namespace OHOS::Ace::Framework
382