1 /*
2 * Copyright (c) 2024 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 "js_text_blob.h"
17
18 #include "native_value.h"
19
20 #include "js_drawing_utils.h"
21 #include "font_napi/js_font.h"
22
23 namespace OHOS::Rosen {
24 namespace Drawing {
25 const std::string CLASS_NAME = "TextBlob";
26 thread_local napi_ref JsTextBlob::constructor_ = nullptr;
27 std::shared_ptr<TextBlob> drawingTextBlob;
28 static constexpr size_t CHAR16_SIZE = 2;
Init(napi_env env,napi_value exportObj)29 napi_value JsTextBlob::Init(napi_env env, napi_value exportObj)
30 {
31 napi_property_descriptor properties[] = {
32 DECLARE_NAPI_FUNCTION("bounds", JsTextBlob::Bounds),
33 DECLARE_NAPI_STATIC_FUNCTION("makeFromString", JsTextBlob::MakeFromString),
34 DECLARE_NAPI_STATIC_FUNCTION("makeFromRunBuffer", JsTextBlob::MakeFromRunBuffer),
35 DECLARE_NAPI_STATIC_FUNCTION("makeFromPosText", JsTextBlob::MakeFromPosText),
36 DECLARE_NAPI_FUNCTION("uniqueID", JsTextBlob::UniqueID),
37 };
38
39 napi_value constructor = nullptr;
40 napi_status status = napi_define_class(env, CLASS_NAME.c_str(), NAPI_AUTO_LENGTH, Constructor, nullptr,
41 sizeof(properties) / sizeof(properties[0]), properties, &constructor);
42 if (status != napi_ok) {
43 ROSEN_LOGE("Drawing_napi: Failed to define TextBlob class");
44 return nullptr;
45 }
46
47 status = napi_create_reference(env, constructor, 1, &constructor_);
48 if (status != napi_ok) {
49 ROSEN_LOGE("Drawing_napi: Failed to create reference of constructor");
50 return nullptr;
51 }
52
53 status = napi_set_named_property(env, exportObj, CLASS_NAME.c_str(), constructor);
54 if (status != napi_ok) {
55 ROSEN_LOGE("Drawing_napi: Failed to set constructor");
56 return nullptr;
57 }
58
59 status = napi_define_properties(env, exportObj, sizeof(properties) / sizeof(properties[0]), properties);
60 if (status != napi_ok) {
61 ROSEN_LOGE("Drawing_napi: Failed to define static function");
62 return nullptr;
63 }
64 return exportObj;
65 }
66
~JsTextBlob()67 JsTextBlob::~JsTextBlob()
68 {
69 m_textBlob = nullptr;
70 }
71
Constructor(napi_env env,napi_callback_info info)72 napi_value JsTextBlob::Constructor(napi_env env, napi_callback_info info)
73 {
74 size_t argCount = 0;
75 napi_value jsThis = nullptr;
76 napi_status status = napi_get_cb_info(env, info, &argCount, nullptr, &jsThis, nullptr);
77 if (status != napi_ok) {
78 ROSEN_LOGE("failed to napi_get_cb_info");
79 return nullptr;
80 }
81
82 JsTextBlob *jsTextBlob = new JsTextBlob(env, drawingTextBlob);
83
84 status = napi_wrap(env, jsThis, jsTextBlob,
85 JsTextBlob::Destructor, nullptr, nullptr);
86 if (status != napi_ok) {
87 delete jsTextBlob;
88 ROSEN_LOGE("Failed to wrap native instance");
89 return nullptr;
90 }
91 return jsThis;
92 }
93
Destructor(napi_env env,void * nativeObject,void * finalize)94 void JsTextBlob::Destructor(napi_env env, void *nativeObject, void *finalize)
95 {
96 (void)finalize;
97 if (nativeObject != nullptr) {
98 JsTextBlob *napi = reinterpret_cast<JsTextBlob *>(nativeObject);
99 delete napi;
100 }
101 }
102
MakeFromRunBuffer(napi_env env,napi_callback_info info)103 napi_value JsTextBlob::MakeFromRunBuffer(napi_env env, napi_callback_info info)
104 {
105 size_t argc = ARGC_THREE;
106 napi_value argv[ARGC_THREE] = {nullptr};
107 CHECK_PARAM_NUMBER_WITH_OPTIONAL_PARAMS(argv, argc, ARGC_TWO, ARGC_THREE);
108
109 napi_value array = argv[ARGC_ZERO];
110 uint32_t size = 0;
111 if (napi_get_array_length(env, array, &size) != napi_ok) {
112 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
113 }
114
115 JsFont* jsFont = nullptr;
116 GET_UNWRAP_PARAM(ARGC_ONE, jsFont);
117
118 std::shared_ptr<Font> font = jsFont->GetFont();
119 if (font == nullptr) {
120 ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer font is nullptr");
121 return nullptr;
122 }
123 std::shared_ptr<Font> themeFont = GetThemeFont(font);
124 if (themeFont != nullptr) {
125 font = themeFont;
126 }
127
128 TextBlobBuilder::RunBuffer runBuffer;
129 std::shared_ptr<TextBlobBuilder> textBlobBuilder = std::make_shared<TextBlobBuilder>();
130 if (argc == ARGC_TWO) {
131 runBuffer = textBlobBuilder->AllocRunPos(*font, size);
132 } else {
133 Rect drawingRect;
134 napi_valuetype isRectNullptr;
135 if (!OnMakeDrawingRect(env, argv[ARGC_TWO], drawingRect, isRectNullptr)) {
136 ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer Argv[2] is invalid");
137 return nullptr;
138 }
139 runBuffer = textBlobBuilder->AllocRunPos(*font, size, isRectNullptr == napi_null ? nullptr : &drawingRect);
140 }
141 if (!OnMakeRunBuffer(env, runBuffer, size, array)) {
142 ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer Argv[0] is invalid");
143 return nullptr;
144 }
145
146 std::shared_ptr<TextBlob> textBlob = textBlobBuilder->Make();
147 if (textBlob == nullptr) {
148 ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer textBlob is nullptr");
149 return nullptr;
150 }
151 return JsTextBlob::CreateJsTextBlob(env, textBlob);
152 }
153
OnMakeDrawingRect(napi_env & env,napi_value & argv,Rect & drawingRect,napi_valuetype & isRectNullptr)154 bool JsTextBlob::OnMakeDrawingRect(napi_env& env, napi_value& argv, Rect& drawingRect, napi_valuetype& isRectNullptr)
155 {
156 napi_typeof(env, argv, &isRectNullptr);
157 if (isRectNullptr != napi_null) {
158 double ltrb[ARGC_FOUR] = {0};
159 if (!ConvertFromJsRect(env, argv, ltrb, ARGC_FOUR)) {
160 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM,
161 "Incorrect parameter2 type. The type of left, top, right and bottom must be number.");
162 }
163
164 drawingRect.SetLeft(ltrb[ARGC_ZERO]);
165 drawingRect.SetTop(ltrb[ARGC_ONE]);
166 drawingRect.SetRight(ltrb[ARGC_TWO]);
167 drawingRect.SetBottom(ltrb[ARGC_THREE]);
168 }
169 return true;
170 }
171
OnMakeRunBuffer(napi_env & env,TextBlobBuilder::RunBuffer & runBuffer,uint32_t size,napi_value & array)172 bool JsTextBlob::OnMakeRunBuffer(napi_env& env, TextBlobBuilder::RunBuffer& runBuffer, uint32_t size, napi_value& array)
173 {
174 if (size > MAX_ELEMENTSIZE) {
175 ROSEN_LOGE("JsTextBlob::OnMakeRunBuffer size exceeds the upper limit");
176 return false;
177 }
178 for (uint32_t i = 0; i < size; i++) {
179 napi_value tempRunBuffer = nullptr;
180 napi_get_element(env, array, i, &tempRunBuffer);
181 napi_value tempValue = nullptr;
182 uint32_t glyph = 0;
183 double positionX = 0.0;
184 double positionY = 0.0;
185 napi_get_named_property(env, tempRunBuffer, "glyph", &tempValue);
186 bool isGlyphOk = ConvertFromJsValue(env, tempValue, glyph);
187 napi_get_named_property(env, tempRunBuffer, "positionX", &tempValue);
188 bool isPositionXOk = ConvertFromJsValue(env, tempValue, positionX);
189 napi_get_named_property(env, tempRunBuffer, "positionY", &tempValue);
190 bool isPositionYOk = ConvertFromJsValue(env, tempValue, positionY);
191 if (!(isGlyphOk && isPositionXOk && isPositionYOk)) {
192 return false;
193 }
194
195 runBuffer.glyphs[i] = (uint16_t)glyph;
196 runBuffer.pos[2 * i] = positionX; // 2: double
197 runBuffer.pos[2 * i + 1] = positionY; // 2: double
198 }
199 return true;
200 }
201
MakeFromString(napi_env env,napi_callback_info info)202 napi_value JsTextBlob::MakeFromString(napi_env env, napi_callback_info info)
203 {
204 size_t argc = ARGC_THREE;
205 napi_value argv[ARGC_THREE] = {nullptr};
206 CHECK_PARAM_NUMBER_WITH_OPTIONAL_PARAMS(argv, argc, ARGC_TWO, ARGC_THREE);
207
208 JsFont* jsFont = nullptr;
209 GET_UNWRAP_PARAM(ARGC_ONE, jsFont);
210
211 std::shared_ptr<Font> font = jsFont->GetFont();
212 if (font == nullptr) {
213 ROSEN_LOGE("JsTextBlob::MakeFromString font is nullptr");
214 return nullptr;
215 }
216 std::shared_ptr<Font> themeFont = GetThemeFont(font);
217 if (themeFont != nullptr) {
218 font = themeFont;
219 }
220
221 // Chinese characters need to be encoded with UTF16
222 size_t len = 0;
223 if (napi_get_value_string_utf16(env, argv[ARGC_ZERO], nullptr, 0, &len) != napi_ok) {
224 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
225 }
226 char16_t* buffer = new(std::nothrow) char16_t[len + 1];
227 if (!buffer) {
228 ROSEN_LOGE("JsTextBlob::MakeFromString fail to create buffer");
229 return nullptr;
230 }
231 if (napi_get_value_string_utf16(env, argv[ARGC_ZERO], buffer, len + 1, &len) != napi_ok) {
232 delete[] buffer;
233 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
234 }
235 std::shared_ptr<TextBlob> textBlob = TextBlob::MakeFromText(buffer, CHAR16_SIZE * len, *font, TextEncoding::UTF16);
236
237 if (textBlob == nullptr) {
238 delete[] buffer;
239 ROSEN_LOGE("JsTextBlob::MakeFromString textBlob is nullptr");
240 return nullptr;
241 }
242 napi_value jsTextBlob = JsTextBlob::CreateJsTextBlob(env, textBlob);
243 if (jsTextBlob == nullptr) {
244 delete[] buffer;
245 ROSEN_LOGE("JsTextBlob::MakeFromString jsTextBlob is nullptr");
246 return nullptr;
247 }
248 delete[] buffer;
249 return jsTextBlob;
250 }
251
UniqueID(napi_env env,napi_callback_info info)252 napi_value JsTextBlob::UniqueID(napi_env env, napi_callback_info info)
253 {
254 JsTextBlob* me = CheckParamsAndGetThis<JsTextBlob>(env, info);
255 return (me != nullptr) ? me->OnUniqueID(env, info) : nullptr;
256 }
257
OnUniqueID(napi_env env,napi_callback_info info)258 napi_value JsTextBlob::OnUniqueID(napi_env env, napi_callback_info info)
259 {
260 if (m_textBlob == nullptr) {
261 ROSEN_LOGE("JsTextBlob::OnUniqueID textBlob is nullptr");
262 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Invalid params.");
263 }
264 return CreateJsNumber(env, m_textBlob->UniqueID());
265 }
266
MakePoints(napi_env & env,Point * point,uint32_t size,napi_value & array)267 static bool MakePoints(napi_env& env, Point* point, uint32_t size, napi_value& array)
268 {
269 napi_status status = napi_invalid_arg;
270 for (uint32_t i = 0; i < size; i++) {
271 napi_value tempNumber = nullptr;
272 status = napi_get_element(env, array, i, &tempNumber);
273 if (status != napi_ok) {
274 return false;
275 }
276 napi_value tempValue = nullptr;
277 status = napi_get_named_property(env, tempNumber, "x", &tempValue);
278 if (status != napi_ok) {
279 return false;
280 }
281 double pointX = 0.0;
282 const bool isPointXOk = ConvertFromJsValue(env, tempValue, pointX);
283 status = napi_get_named_property(env, tempNumber, "y", &tempValue);
284 if (status != napi_ok) {
285 return false;
286 }
287 double pointY = 0.0;
288 const bool isPointYOk = ConvertFromJsValue(env, tempValue, pointY);
289 if (!(isPointXOk && isPointYOk)) {
290 return false;
291 }
292 point[i] = Point(pointX, pointY);
293 }
294 return true;
295 }
296
getJsTextBlob(const char * buffer,size_t bufferLen,const Point points[],const std::shared_ptr<Font> & font,napi_env env)297 static napi_value getJsTextBlob(const char* buffer, size_t bufferLen, const Point points[],
298 const std::shared_ptr<Font>& font, napi_env env)
299 {
300 std::shared_ptr<TextBlob> textBlob =
301 TextBlob::MakeFromPosText(buffer, bufferLen, points, *font, TextEncoding::UTF8);
302 if (textBlob == nullptr) {
303 ROSEN_LOGE("getJsTextBlob: textBlob is nullptr");
304 return nullptr;
305 }
306 napi_value jsTextBlob = JsTextBlob::CreateJsTextBlob(env, textBlob);
307 if (jsTextBlob == nullptr) {
308 ROSEN_LOGE("getJsTextBlob: jsTextBlob is nullptr");
309 return nullptr;
310 }
311 return jsTextBlob;
312 }
313
MakeFromPosText(napi_env env,napi_callback_info info)314 napi_value JsTextBlob::MakeFromPosText(napi_env env, napi_callback_info info)
315 {
316 napi_value argv[ARGC_FOUR] = {nullptr};
317 CHECK_PARAM_NUMBER_WITHOUT_OPTIONAL_PARAMS(argv, ARGC_FOUR);
318
319 uint32_t len = 0;
320 GET_UINT32_PARAM(ARGC_ONE, len);
321
322 size_t bufferLen = 0;
323 if (napi_get_value_string_utf8(env, argv[ARGC_ZERO], nullptr, 0, &bufferLen) != napi_ok) {
324 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
325 }
326
327 char* buffer = new(std::nothrow) char[bufferLen + 1];
328 if (!buffer) {
329 ROSEN_LOGE("JsTextBlob::MakeFromPosText: failed to create buffer");
330 return nullptr;
331 }
332 if (napi_get_value_string_utf8(env, argv[ARGC_ZERO], buffer, bufferLen + 1, &bufferLen) != napi_ok) {
333 delete[] buffer;
334 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect Argv[0] type.");
335 }
336
337 napi_value array = argv[ARGC_TWO];
338 uint32_t pointsSize = 0;
339 if (napi_get_array_length(env, array, &pointsSize) != napi_ok) {
340 delete[] buffer;
341 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect Argv[2].");
342 }
343 if (pointsSize == 0 || bufferLen == 0) {
344 delete[] buffer;
345 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Argv[0] is empty.");
346 }
347 if (len != pointsSize) {
348 delete[] buffer;
349 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM,
350 "string length does not match points array length.");
351 }
352
353 JsFont* jsFont = nullptr;
354 if ((napi_unwrap(env, argv[ARGC_THREE], reinterpret_cast<void**>(&jsFont)) != napi_ok) || jsFont == nullptr) {
355 delete[] buffer;
356 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM,
357 std::string("Incorrect ") + __FUNCTION__ + " parameter" + std::to_string(ARGC_THREE) + " type.");
358 }
359 std::shared_ptr<Font> font = jsFont->GetFont();
360 if (font == nullptr) {
361 delete[] buffer;
362 ROSEN_LOGE("JsTextBlob::MakeFromPosText: font is nullptr");
363 return nullptr;
364 }
365 std::shared_ptr<Font> themeFont = GetThemeFont(font);
366 if (themeFont != nullptr) {
367 font = themeFont;
368 }
369
370 Point* points = new(std::nothrow) Point[pointsSize];
371 if (!points) {
372 delete[] buffer;
373 ROSEN_LOGE("JsTextBlob::MakeFromPosText: failed to create Point");
374 return nullptr;
375 }
376 if (!MakePoints(env, points, pointsSize, array)) {
377 delete[] buffer;
378 delete[] points;
379 ROSEN_LOGE("JsTextBlob::MakeFromPosText: Argv[2] is invalid");
380 return nullptr;
381 }
382 napi_value jsTextBlob = getJsTextBlob(buffer, bufferLen, points, font, env);
383 delete[] buffer;
384 delete[] points;
385 return jsTextBlob;
386 }
387
CreateJsTextBlob(napi_env env,const std::shared_ptr<TextBlob> textBlob)388 napi_value JsTextBlob::CreateJsTextBlob(napi_env env, const std::shared_ptr<TextBlob> textBlob)
389 {
390 napi_value constructor = nullptr;
391 napi_value result = nullptr;
392 napi_status status = napi_get_reference_value(env, constructor_, &constructor);
393 if (status == napi_ok) {
394 drawingTextBlob = textBlob;
395 status = napi_new_instance(env, constructor, 0, nullptr, &result);
396 if (status == napi_ok) {
397 return result;
398 } else {
399 ROSEN_LOGE("JsTextBlob::CreateJsTextBlob New instance could not be obtained");
400 }
401 }
402 return result;
403 }
404
Bounds(napi_env env,napi_callback_info info)405 napi_value JsTextBlob::Bounds(napi_env env, napi_callback_info info)
406 {
407 JsTextBlob* me = CheckParamsAndGetThis<JsTextBlob>(env, info);
408 return (me != nullptr) ? me->OnBounds(env, info) : nullptr;
409 }
410
OnBounds(napi_env env,napi_callback_info info)411 napi_value JsTextBlob::OnBounds(napi_env env, napi_callback_info info)
412 {
413 if (m_textBlob == nullptr) {
414 ROSEN_LOGE("JsTextBlob::OnBounds textBlob is nullptr");
415 return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Invalid params.");
416 }
417 std::shared_ptr<Rect> rect = m_textBlob->Bounds();
418
419 if (!rect) {
420 ROSEN_LOGE("JsTextBlob::OnBounds rect is nullptr");
421 return nullptr;
422 }
423 return GetRectAndConvertToJsValue(env, *rect);
424 }
425
GetTextBlob()426 std::shared_ptr<TextBlob> JsTextBlob::GetTextBlob()
427 {
428 return m_textBlob;
429 }
430 } // namespace Drawing
431 } // namespace OHOS::Rosen
432