1 /*
2 * Copyright (c) 2021-2022 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_text.h"
17
18 #include <sstream>
19 #include <string>
20 #include <vector>
21
22 #include "base/geometry/dimension.h"
23 #include "base/log/ace_scoring_log.h"
24 #include "base/log/ace_trace.h"
25 #include "base/utils/utils.h"
26 #include "bridge/common/utils/utils.h"
27 #include "bridge/declarative_frontend/engine/functions/js_click_function.h"
28 #include "bridge/declarative_frontend/engine/functions/js_drag_function.h"
29 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
30 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
31 #include "bridge/declarative_frontend/jsview/models/text_model_impl.h"
32 #include "bridge/declarative_frontend/view_stack_processor.h"
33 #include "core/common/container.h"
34 #include "core/components/text/text_theme.h"
35 #include "core/components_ng/event/gesture_event_hub.h"
36 #include "core/components_ng/pattern/text/text_model.h"
37 #include "core/components_ng/pattern/text/text_model_ng.h"
38 #include "core/event/ace_event_handler.h"
39
40 namespace OHOS::Ace {
41
42 std::unique_ptr<TextModel> TextModel::instance_ = nullptr;
43
GetInstance()44 TextModel* TextModel::GetInstance()
45 {
46 if (!instance_) {
47 #ifdef NG_BUILD
48 instance_.reset(new NG::TextModelNG());
49 #else
50 if (Container::IsCurrentUseNewPipeline()) {
51 instance_.reset(new NG::TextModelNG());
52 } else {
53 instance_.reset(new Framework::TextModelImpl());
54 }
55 #endif
56 }
57 return instance_.get();
58 }
59
60 } // namespace OHOS::Ace
61
62 namespace OHOS::Ace::Framework {
63 namespace {
64
65 const std::vector<TextCase> TEXT_CASES = { TextCase::NORMAL, TextCase::LOWERCASE, TextCase::UPPERCASE };
66 const std::vector<TextOverflow> TEXT_OVERFLOWS = { TextOverflow::CLIP, TextOverflow::ELLIPSIS, TextOverflow::NONE };
67 const std::vector<FontStyle> FONT_STYLES = { FontStyle::NORMAL, FontStyle::ITALIC };
68 const std::vector<TextAlign> TEXT_ALIGNS = { TextAlign::START, TextAlign::CENTER, TextAlign::END, TextAlign::LEFT,
69 TextAlign::RIGHT, TextAlign::JUSTIFY };
70
71 }; // namespace
72
SetWidth(const JSCallbackInfo & info)73 void JSText::SetWidth(const JSCallbackInfo& info)
74 {
75 JSViewAbstract::JsWidth(info);
76 TextModel::GetInstance()->OnSetWidth();
77 }
78
SetHeight(const JSCallbackInfo & info)79 void JSText::SetHeight(const JSCallbackInfo& info)
80 {
81 JSViewAbstract::JsHeight(info);
82 TextModel::GetInstance()->OnSetHeight();
83 }
84
SetFontSize(const JSCallbackInfo & info)85 void JSText::SetFontSize(const JSCallbackInfo& info)
86 {
87 if (info.Length() < 1) {
88 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
89 return;
90 }
91 Dimension fontSize;
92 if (!ParseJsDimensionFp(info[0], fontSize)) {
93 return;
94 }
95 if (fontSize.IsNegative() || fontSize.Unit() == DimensionUnit::PERCENT) {
96 auto pipelineContext = PipelineContext::GetCurrentContext();
97 CHECK_NULL_VOID_NOLOG(pipelineContext);
98 auto theme = pipelineContext->GetTheme<TextTheme>();
99 CHECK_NULL_VOID_NOLOG(theme);
100 TextModel::GetInstance()->SetFontSize(theme->GetTextStyle().GetFontSize());
101 return;
102 }
103 TextModel::GetInstance()->SetFontSize(fontSize);
104 }
105
SetFontWeight(const std::string & value)106 void JSText::SetFontWeight(const std::string& value)
107 {
108 TextModel::GetInstance()->SetFontWeight(ConvertStrToFontWeight(value));
109 }
110
SetTextColor(const JSCallbackInfo & info)111 void JSText::SetTextColor(const JSCallbackInfo& info)
112 {
113 if (info.Length() < 1) {
114 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
115 return;
116 }
117 Color textColor;
118 if (!ParseJsColor(info[0], textColor)) {
119 return;
120 }
121 TextModel::GetInstance()->SetTextColor(textColor);
122 }
123
SetTextOverflow(const JSCallbackInfo & info)124 void JSText::SetTextOverflow(const JSCallbackInfo& info)
125 {
126 do {
127 if (!info[0]->IsObject()) {
128 LOGE("info[0] not is Object");
129 break;
130 }
131 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
132 JSRef<JSVal> overflowValue = obj->GetProperty("overflow");
133 if (!overflowValue->IsNumber()) {
134 LOGE("overflow value is not a number");
135 break;
136 }
137 auto overflow = overflowValue->ToNumber<int32_t>();
138 if (overflow < 0 || overflow >= static_cast<int32_t>(TEXT_OVERFLOWS.size())) {
139 LOGE("Text: textOverflow(%{public}d) illegal value", overflow);
140 break;
141 }
142 TextModel::GetInstance()->SetTextOverflow(TEXT_OVERFLOWS[overflow]);
143 } while (false);
144
145 info.SetReturnValue(info.This());
146 }
147
SetMaxLines(const JSCallbackInfo & info)148 void JSText::SetMaxLines(const JSCallbackInfo& info)
149 {
150 int32_t value;
151 if (info[0]->ToString() == "Infinity") {
152 value = Infinity<uint32_t>();
153 } else if (!info[0]->IsNumber()) {
154 return;
155 } else {
156 ParseJsInt32(info[0], value);
157 }
158 TextModel::GetInstance()->SetMaxLines(value);
159 }
160
SetFontStyle(int32_t value)161 void JSText::SetFontStyle(int32_t value)
162 {
163 if (value < 0 || value >= static_cast<int32_t>(FONT_STYLES.size())) {
164 LOGE("Text fontStyle(%{public}d) illegal value", value);
165 return;
166 }
167 TextModel::GetInstance()->SetItalicFontStyle(FONT_STYLES[value]);
168 }
169
SetTextAlign(int32_t value)170 void JSText::SetTextAlign(int32_t value)
171 {
172 if (value < 0 || value >= static_cast<int32_t>(TEXT_ALIGNS.size())) {
173 LOGE("Text: TextAlign(%d) expected positive number", value);
174 return;
175 }
176 TextModel::GetInstance()->SetTextAlign(TEXT_ALIGNS[value]);
177 }
178
SetAlign(const JSCallbackInfo & info)179 void JSText::SetAlign(const JSCallbackInfo& info)
180 {
181 JSViewAbstract::JsAlign(info);
182 if (!info[0]->IsNumber()) {
183 return;
184 }
185 TextModel::GetInstance()->OnSetAlign();
186 }
187
SetLineHeight(const JSCallbackInfo & info)188 void JSText::SetLineHeight(const JSCallbackInfo& info)
189 {
190 if (info.Length() < 1) {
191 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
192 return;
193 }
194 Dimension value;
195 if (!ParseJsDimensionFp(info[0], value)) {
196 return;
197 }
198 TextModel::GetInstance()->SetLineHeight(value);
199 }
200
SetFontFamily(const JSCallbackInfo & info)201 void JSText::SetFontFamily(const JSCallbackInfo& info)
202 {
203 if (info.Length() < 1) {
204 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
205 return;
206 }
207 std::vector<std::string> fontFamilies;
208 if (!ParseJsFontFamilies(info[0], fontFamilies)) {
209 LOGE("Parse FontFamilies failed");
210 return;
211 }
212 TextModel::GetInstance()->SetFontFamily(fontFamilies);
213 }
214
SetMinFontSize(const JSCallbackInfo & info)215 void JSText::SetMinFontSize(const JSCallbackInfo& info)
216 {
217 if (info.Length() < 1) {
218 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
219 return;
220 }
221 Dimension fontSize;
222 if (!ParseJsDimensionFp(info[0], fontSize)) {
223 return;
224 }
225 TextModel::GetInstance()->SetAdaptMinFontSize(fontSize);
226 }
227
SetMaxFontSize(const JSCallbackInfo & info)228 void JSText::SetMaxFontSize(const JSCallbackInfo& info)
229 {
230 if (info.Length() < 1) {
231 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
232 return;
233 }
234 Dimension fontSize;
235 if (!ParseJsDimensionFp(info[0], fontSize)) {
236 return;
237 }
238 TextModel::GetInstance()->SetAdaptMaxFontSize(fontSize);
239 }
240
SetLetterSpacing(const JSCallbackInfo & info)241 void JSText::SetLetterSpacing(const JSCallbackInfo& info)
242 {
243 if (info.Length() < 1) {
244 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
245 return;
246 }
247 Dimension value;
248 if (!ParseJsDimensionFp(info[0], value)) {
249 return;
250 }
251 TextModel::GetInstance()->SetLetterSpacing(value);
252 }
253
SetTextCase(int32_t value)254 void JSText::SetTextCase(int32_t value)
255 {
256 if (value < 0 || value >= static_cast<int32_t>(TEXT_CASES.size())) {
257 LOGE("Text textCase(%{public}d) illegal value", value);
258 return;
259 }
260 TextModel::GetInstance()->SetTextCase(TEXT_CASES[value]);
261 }
262
SetBaselineOffset(const JSCallbackInfo & info)263 void JSText::SetBaselineOffset(const JSCallbackInfo& info)
264 {
265 if (info.Length() < 1) {
266 LOGE("The argv is wrong, it is supposed to have at least 1 argument");
267 return;
268 }
269 Dimension value;
270 if (!ParseJsDimensionFp(info[0], value)) {
271 return;
272 }
273 TextModel::GetInstance()->SetBaselineOffset(value);
274 }
275
SetDecoration(const JSCallbackInfo & info)276 void JSText::SetDecoration(const JSCallbackInfo& info)
277 {
278 do {
279 if (!info[0]->IsObject()) {
280 LOGE("info[0] not is Object");
281 break;
282 }
283 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
284 JSRef<JSVal> typeValue = obj->GetProperty("type");
285 JSRef<JSVal> colorValue = obj->GetProperty("color");
286
287 std::optional<TextDecoration> textDecoration;
288 if (typeValue->IsNumber()) {
289 textDecoration = static_cast<TextDecoration>(typeValue->ToNumber<int32_t>());
290 }
291 std::optional<Color> colorVal;
292 Color result;
293 if (ParseJsColor(colorValue, result)) {
294 colorVal = result;
295 }
296
297 if (textDecoration) {
298 TextModel::GetInstance()->SetTextDecoration(textDecoration.value());
299 }
300 if (colorVal) {
301 TextModel::GetInstance()->SetTextDecorationColor(colorVal.value());
302 }
303 } while (false);
304 info.SetReturnValue(info.This());
305 }
306
JsOnClick(const JSCallbackInfo & info)307 void JSText::JsOnClick(const JSCallbackInfo& info)
308 {
309 if (Container::IsCurrentUseNewPipeline()) {
310 JSInteractableView::JsOnClick(info);
311 } else {
312 #ifndef NG_BUILD
313 if (info[0]->IsFunction()) {
314 auto inspector = ViewStackProcessor::GetInstance()->GetInspectorComposedComponent();
315 auto impl = inspector ? inspector->GetInspectorFunctionImpl() : nullptr;
316 RefPtr<JsClickFunction> jsOnClickFunc = AceType::MakeRefPtr<JsClickFunction>(JSRef<JSFunc>::Cast(info[0]));
317 auto onClickId = [execCtx = info.GetExecutionContext(), func = std::move(jsOnClickFunc), impl](
318 const BaseEventInfo* info) {
319 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
320 LOGD("About to call onclick method on js");
321 const auto* clickInfo = TypeInfoHelper::DynamicCast<ClickInfo>(info);
322 auto newInfo = *clickInfo;
323 if (impl) {
324 impl->UpdateEventInfo(newInfo);
325 }
326 ACE_SCORING_EVENT("Text.onClick");
327 func->Execute(newInfo);
328 };
329 TextModel::GetInstance()->SetOnClick(std::move(onClickId));
330 }
331 #endif
332 }
333 }
334
JsRemoteMessage(const JSCallbackInfo & info)335 void JSText::JsRemoteMessage(const JSCallbackInfo& info)
336 {
337 JSInteractableView::JsCommonRemoteMessage(info);
338 auto callback = JSInteractableView::GetRemoteMessageEventCallback(info);
339 TextModel::GetInstance()->SetRemoteMessage(std::move(callback));
340 }
341
Create(const JSCallbackInfo & info)342 void JSText::Create(const JSCallbackInfo& info)
343 {
344 std::string data;
345 if (info.Length() > 0) {
346 ParseJsString(info[0], data);
347 }
348
349 TextModel::GetInstance()->Create(data);
350 }
351
SetCopyOption(const JSCallbackInfo & info)352 void JSText::SetCopyOption(const JSCallbackInfo& info)
353 {
354 if (info.Length() == 0) {
355 return;
356 }
357 auto copyOptions = CopyOptions::None;
358 if (info[0]->IsNumber()) {
359 auto emunNumber = info[0]->ToNumber<int>();
360 copyOptions = static_cast<CopyOptions>(emunNumber);
361 }
362 TextModel::GetInstance()->SetCopyOption(copyOptions);
363 }
364
JsOnDragStart(const JSCallbackInfo & info)365 void JSText::JsOnDragStart(const JSCallbackInfo& info)
366 {
367 CHECK_NULL_VOID(info[0]->IsFunction());
368 RefPtr<JsDragFunction> jsOnDragStartFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
369 auto onDragStart = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragStartFunc)](
370 const RefPtr<DragEvent>& info, const std::string& extraParams) -> NG::DragDropBaseInfo {
371 NG::DragDropBaseInfo itemInfo;
372 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, itemInfo);
373
374 auto ret = func->Execute(info, extraParams);
375 if (!ret->IsObject()) {
376 LOGE("builder param is not an object.");
377 return itemInfo;
378 }
379 auto node = ParseDragNode(ret);
380 if (node) {
381 LOGI("use custom builder param.");
382 itemInfo.node = node;
383 return itemInfo;
384 }
385
386 auto builderObj = JSRef<JSObject>::Cast(ret);
387 #if defined(PIXEL_MAP_SUPPORTED)
388 auto pixmap = builderObj->GetProperty("pixelMap");
389 itemInfo.pixelMap = CreatePixelMapFromNapiValue(pixmap);
390 #endif
391 auto extraInfo = builderObj->GetProperty("extraInfo");
392 ParseJsString(extraInfo, itemInfo.extraInfo);
393 node = ParseDragNode(builderObj->GetProperty("builder"));
394 itemInfo.node = node;
395 return itemInfo;
396 };
397
398 TextModel::GetInstance()->SetOnDragStart(std::move(onDragStart));
399 }
400
JsOnDragEnter(const JSCallbackInfo & info)401 void JSText::JsOnDragEnter(const JSCallbackInfo& info)
402 {
403 CHECK_NULL_VOID(info[0]->IsFunction());
404 RefPtr<JsDragFunction> jsOnDragEnterFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
405 auto onDragEnterId = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragEnterFunc)](
406 const RefPtr<DragEvent>& info, const std::string& extraParams) {
407 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
408 ACE_SCORING_EVENT("onDragEnter");
409 func->Execute(info, extraParams);
410 };
411 TextModel::GetInstance()->SetOnDragEnter(std::move(onDragEnterId));
412 }
413
JsOnDragMove(const JSCallbackInfo & info)414 void JSText::JsOnDragMove(const JSCallbackInfo& info)
415 {
416 CHECK_NULL_VOID(info[0]->IsFunction());
417 RefPtr<JsDragFunction> jsOnDragMoveFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
418 auto onDragMoveId = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragMoveFunc)](
419 const RefPtr<DragEvent>& info, const std::string& extraParams) {
420 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
421 ACE_SCORING_EVENT("onDragMove");
422 func->Execute(info, extraParams);
423 };
424 TextModel::GetInstance()->SetOnDragMove(std::move(onDragMoveId));
425 }
426
JsOnDragLeave(const JSCallbackInfo & info)427 void JSText::JsOnDragLeave(const JSCallbackInfo& info)
428 {
429 CHECK_NULL_VOID(info[0]->IsFunction());
430 RefPtr<JsDragFunction> jsOnDragLeaveFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
431 auto onDragLeaveId = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDragLeaveFunc)](
432 const RefPtr<DragEvent>& info, const std::string& extraParams) {
433 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
434 ACE_SCORING_EVENT("onDragLeave");
435 func->Execute(info, extraParams);
436 };
437 TextModel::GetInstance()->SetOnDragLeave(std::move(onDragLeaveId));
438 }
439
JsOnDrop(const JSCallbackInfo & info)440 void JSText::JsOnDrop(const JSCallbackInfo& info)
441 {
442 CHECK_NULL_VOID(info[0]->IsFunction());
443 RefPtr<JsDragFunction> jsOnDropFunc = AceType::MakeRefPtr<JsDragFunction>(JSRef<JSFunc>::Cast(info[0]));
444 auto onDropId = [execCtx = info.GetExecutionContext(), func = std::move(jsOnDropFunc)](
445 const RefPtr<DragEvent>& info, const std::string& extraParams) {
446 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
447 ACE_SCORING_EVENT("onDrop");
448 func->Execute(info, extraParams);
449 };
450 TextModel::GetInstance()->SetOnDrop(std::move(onDropId));
451 }
452
JsFocusable(const JSCallbackInfo & info)453 void JSText::JsFocusable(const JSCallbackInfo& info)
454 {
455 if (!info[0]->IsBoolean()) {
456 LOGE("The info is wrong, it is supposed to be an boolean");
457 return;
458 }
459 JSInteractableView::SetFocusable(info[0]->ToBoolean());
460 JSInteractableView::SetFocusNode(false);
461 }
462
JSBind(BindingTarget globalObj)463 void JSText::JSBind(BindingTarget globalObj)
464 {
465 JSClass<JSText>::Declare("Text");
466 MethodOptions opt = MethodOptions::NONE;
467 JSClass<JSText>::StaticMethod("create", &JSText::Create, opt);
468 JSClass<JSText>::StaticMethod("width", &JSText::SetWidth);
469 JSClass<JSText>::StaticMethod("height", &JSText::SetHeight);
470 JSClass<JSText>::StaticMethod("fontColor", &JSText::SetTextColor, opt);
471 JSClass<JSText>::StaticMethod("fontSize", &JSText::SetFontSize, opt);
472 JSClass<JSText>::StaticMethod("fontWeight", &JSText::SetFontWeight, opt);
473 JSClass<JSText>::StaticMethod("maxLines", &JSText::SetMaxLines, opt);
474 JSClass<JSText>::StaticMethod("textOverflow", &JSText::SetTextOverflow, opt);
475 JSClass<JSText>::StaticMethod("fontStyle", &JSText::SetFontStyle, opt);
476 JSClass<JSText>::StaticMethod("align", &JSText::SetAlign, opt);
477 JSClass<JSText>::StaticMethod("textAlign", &JSText::SetTextAlign, opt);
478 JSClass<JSText>::StaticMethod("lineHeight", &JSText::SetLineHeight, opt);
479 JSClass<JSText>::StaticMethod("fontFamily", &JSText::SetFontFamily, opt);
480 JSClass<JSText>::StaticMethod("minFontSize", &JSText::SetMinFontSize, opt);
481 JSClass<JSText>::StaticMethod("maxFontSize", &JSText::SetMaxFontSize, opt);
482 JSClass<JSText>::StaticMethod("letterSpacing", &JSText::SetLetterSpacing, opt);
483 JSClass<JSText>::StaticMethod("textCase", &JSText::SetTextCase, opt);
484 JSClass<JSText>::StaticMethod("baselineOffset", &JSText::SetBaselineOffset, opt);
485 JSClass<JSText>::StaticMethod("decoration", &JSText::SetDecoration);
486 JSClass<JSText>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
487 JSClass<JSText>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
488 JSClass<JSText>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
489 JSClass<JSText>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
490 JSClass<JSText>::StaticMethod("remoteMessage", &JSText::JsRemoteMessage);
491 JSClass<JSText>::StaticMethod("copyOption", &JSText::SetCopyOption);
492 JSClass<JSText>::StaticMethod("onClick", &JSText::JsOnClick);
493 JSClass<JSText>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
494 JSClass<JSText>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
495 JSClass<JSText>::StaticMethod("onDragStart", &JSText::JsOnDragStart);
496 JSClass<JSText>::StaticMethod("onDragEnter", &JSText::JsOnDragEnter);
497 JSClass<JSText>::StaticMethod("onDragMove", &JSText::JsOnDragMove);
498 JSClass<JSText>::StaticMethod("onDragLeave", &JSText::JsOnDragLeave);
499 JSClass<JSText>::StaticMethod("onDrop", &JSText::JsOnDrop);
500 JSClass<JSText>::StaticMethod("focusable", &JSText::JsFocusable);
501 JSClass<JSText>::Inherit<JSContainerBase>();
502 JSClass<JSText>::Inherit<JSViewAbstract>();
503 JSClass<JSText>::Bind<>(globalObj);
504 }
505
506 } // namespace OHOS::Ace::Framework
507