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