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 
125     TextBlobBuilder::RunBuffer runBuffer;
126     std::shared_ptr<TextBlobBuilder> textBlobBuilder = std::make_shared<TextBlobBuilder>();
127     if (argc == ARGC_TWO) {
128         runBuffer = textBlobBuilder->AllocRunPos(*font, size);
129     } else {
130         Rect drawingRect;
131         napi_valuetype isRectNullptr;
132         if (!OnMakeDrawingRect(env, argv[ARGC_TWO], drawingRect, isRectNullptr)) {
133             ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer Argv[2] is invalid");
134             return nullptr;
135         }
136         runBuffer = textBlobBuilder->AllocRunPos(*font, size, isRectNullptr == napi_null ? nullptr : &drawingRect);
137     }
138     if (!OnMakeRunBuffer(env, runBuffer, size, array)) {
139         ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer Argv[0] is invalid");
140         return nullptr;
141     }
142 
143     std::shared_ptr<TextBlob> textBlob = textBlobBuilder->Make();
144     if (textBlob == nullptr) {
145         ROSEN_LOGE("JsTextBlob::MakeFromRunBuffer textBlob is nullptr");
146         return nullptr;
147     }
148     return JsTextBlob::CreateJsTextBlob(env, textBlob);
149 }
150 
OnMakeDrawingRect(napi_env & env,napi_value & argv,Rect & drawingRect,napi_valuetype & isRectNullptr)151 bool JsTextBlob::OnMakeDrawingRect(napi_env& env, napi_value& argv, Rect& drawingRect, napi_valuetype& isRectNullptr)
152 {
153     napi_typeof(env, argv, &isRectNullptr);
154     if (isRectNullptr != napi_null) {
155         double ltrb[ARGC_FOUR] = {0};
156         if (!ConvertFromJsRect(env, argv, ltrb, ARGC_FOUR)) {
157             return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM,
158                 "Incorrect parameter2 type. The type of left, top, right and bottom must be number.");
159         }
160 
161         drawingRect.SetLeft(ltrb[ARGC_ZERO]);
162         drawingRect.SetTop(ltrb[ARGC_ONE]);
163         drawingRect.SetRight(ltrb[ARGC_TWO]);
164         drawingRect.SetBottom(ltrb[ARGC_THREE]);
165     }
166     return true;
167 }
168 
OnMakeRunBuffer(napi_env & env,TextBlobBuilder::RunBuffer & runBuffer,uint32_t size,napi_value & array)169 bool JsTextBlob::OnMakeRunBuffer(napi_env& env, TextBlobBuilder::RunBuffer& runBuffer, uint32_t size, napi_value& array)
170 {
171     for (uint32_t i = 0; i < size; i++) {
172         napi_value tempRunBuffer = nullptr;
173         napi_get_element(env, array, i, &tempRunBuffer);
174         napi_value tempValue = nullptr;
175         uint32_t glyph = 0;
176         double positionX = 0.0;
177         double positionY = 0.0;
178         napi_get_named_property(env, tempRunBuffer, "glyph", &tempValue);
179         bool isGlyphOk = ConvertFromJsValue(env, tempValue, glyph);
180         napi_get_named_property(env, tempRunBuffer, "positionX", &tempValue);
181         bool isPositionXOk = ConvertFromJsValue(env, tempValue, positionX);
182         napi_get_named_property(env, tempRunBuffer, "positionY", &tempValue);
183         bool isPositionYOk = ConvertFromJsValue(env, tempValue, positionY);
184         if (!(isGlyphOk && isPositionXOk && isPositionYOk)) {
185             return false;
186         }
187 
188         runBuffer.glyphs[i] = (uint16_t)glyph;
189         runBuffer.pos[2 * i] = positionX; // 2: double
190         runBuffer.pos[2 * i + 1] = positionY; // 2: double
191     }
192     return true;
193 }
194 
MakeFromString(napi_env env,napi_callback_info info)195 napi_value JsTextBlob::MakeFromString(napi_env env, napi_callback_info info)
196 {
197     size_t argc = ARGC_THREE;
198     napi_value argv[ARGC_THREE] = {nullptr};
199     CHECK_PARAM_NUMBER_WITH_OPTIONAL_PARAMS(argv, argc, ARGC_TWO, ARGC_THREE);
200 
201     JsFont* jsFont = nullptr;
202     GET_UNWRAP_PARAM(ARGC_ONE, jsFont);
203 
204     std::shared_ptr<Font> font = jsFont->GetFont();
205     if (font == nullptr) {
206         ROSEN_LOGE("JsTextBlob::MakeFromString font is nullptr");
207         return nullptr;
208     }
209 
210     // Chinese characters need to be encoded with UTF16
211     size_t len = 0;
212     if (napi_get_value_string_utf16(env, argv[ARGC_ZERO], nullptr, 0, &len) != napi_ok) {
213         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
214     }
215     char16_t buffer[len + 1];
216     if (napi_get_value_string_utf16(env, argv[ARGC_ZERO], buffer, len + 1, &len) != napi_ok) {
217         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
218     }
219     std::shared_ptr<TextBlob> textBlob = TextBlob::MakeFromText(buffer, CHAR16_SIZE * len, *font, TextEncoding::UTF16);
220 
221     if (textBlob == nullptr) {
222         ROSEN_LOGE("JsTextBlob::MakeFromString textBlob is nullptr");
223         return nullptr;
224     }
225     napi_value jsTextBlob = JsTextBlob::CreateJsTextBlob(env, textBlob);
226     if (jsTextBlob == nullptr) {
227         ROSEN_LOGE("JsTextBlob::MakeFromString jsTextBlob is nullptr");
228         return nullptr;
229     }
230     return jsTextBlob;
231 }
232 
UniqueID(napi_env env,napi_callback_info info)233 napi_value JsTextBlob::UniqueID(napi_env env, napi_callback_info info)
234 {
235     JsTextBlob* me = CheckParamsAndGetThis<JsTextBlob>(env, info);
236     return (me != nullptr) ? me->OnUniqueID(env, info) : nullptr;
237 }
238 
OnUniqueID(napi_env env,napi_callback_info info)239 napi_value JsTextBlob::OnUniqueID(napi_env env, napi_callback_info info)
240 {
241     if (m_textBlob == nullptr) {
242         ROSEN_LOGE("JsTextBlob::OnUniqueID textBlob is nullptr");
243         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Invalid params.");
244     }
245     return CreateJsNumber(env, m_textBlob->UniqueID());
246 }
247 
MakePoints(napi_env & env,Point * point,uint32_t size,napi_value & array)248 static bool MakePoints(napi_env& env, Point* point, uint32_t size, napi_value& array)
249 {
250     napi_status status = napi_invalid_arg;
251     for (uint32_t i = 0; i < size; i++) {
252         napi_value tempNumber = nullptr;
253         status = napi_get_element(env, array, i, &tempNumber);
254         if (status != napi_ok) {
255             return false;
256         }
257         napi_value tempValue = nullptr;
258         status = napi_get_named_property(env, tempNumber, "x", &tempValue);
259         if (status != napi_ok) {
260             return false;
261         }
262         double pointX = 0.0;
263         const bool isPointXOk = ConvertFromJsValue(env, tempValue, pointX);
264         status = napi_get_named_property(env, tempNumber, "y", &tempValue);
265         if (status != napi_ok) {
266             return false;
267         }
268         double pointY = 0.0;
269         const bool isPointYOk = ConvertFromJsValue(env, tempValue, pointY);
270         if (!(isPointXOk && isPointYOk)) {
271             return false;
272         }
273         point[i] = Point(pointX, pointY);
274     }
275     return true;
276 }
277 
getJsTextBlob(const char * buffer,size_t bufferLen,const Point points[],const std::shared_ptr<Font> & font,napi_env env)278 static napi_value getJsTextBlob(const char* buffer, size_t bufferLen, const Point points[],
279                                 const std::shared_ptr<Font>& font, napi_env env)
280 {
281     std::shared_ptr<TextBlob> textBlob =
282         TextBlob::MakeFromPosText(buffer, bufferLen, points, *font, TextEncoding::UTF8);
283     if (textBlob == nullptr) {
284         ROSEN_LOGE("getJsTextBlob: textBlob is nullptr");
285         return nullptr;
286     }
287     napi_value jsTextBlob = JsTextBlob::CreateJsTextBlob(env, textBlob);
288     if (jsTextBlob == nullptr) {
289         ROSEN_LOGE("getJsTextBlob: jsTextBlob is nullptr");
290         return nullptr;
291     }
292     return jsTextBlob;
293 }
294 
MakeFromPosText(napi_env env,napi_callback_info info)295 napi_value JsTextBlob::MakeFromPosText(napi_env env, napi_callback_info info)
296 {
297     napi_value argv[ARGC_FOUR] = {nullptr};
298     CHECK_PARAM_NUMBER_WITHOUT_OPTIONAL_PARAMS(argv, ARGC_FOUR);
299 
300     uint32_t len = 0;
301     GET_UINT32_PARAM(ARGC_ONE, len);
302 
303     size_t bufferLen = 0;
304     if (napi_get_value_string_utf8(env, argv[ARGC_ZERO], nullptr, 0, &bufferLen) != napi_ok) {
305         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect parameter0 type.");
306     }
307 
308     char* buffer = new(std::nothrow) char[bufferLen + 1];
309     if (!buffer) {
310         ROSEN_LOGE("JsTextBlob::MakeFromPosText: failed to create buffer");
311         return nullptr;
312     }
313     if (napi_get_value_string_utf8(env, argv[ARGC_ZERO], buffer, bufferLen + 1, &bufferLen) != napi_ok) {
314         delete[] buffer;
315         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect Argv[0] type.");
316     }
317 
318     napi_value array = argv[ARGC_TWO];
319     uint32_t pointsSize = 0;
320     if (napi_get_array_length(env, array, &pointsSize) != napi_ok) {
321         delete[] buffer;
322         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Incorrect Argv[2].");
323     }
324     if (pointsSize == 0 || bufferLen == 0) {
325         delete[] buffer;
326         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Argv[0] is empty.");
327     }
328     if (len != pointsSize) {
329         delete[] buffer;
330         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM,
331             "string length does not match points array length.");
332     }
333 
334     JsFont* jsFont = nullptr;
335     GET_UNWRAP_PARAM(ARGC_THREE, jsFont);
336     std::shared_ptr<Font> font = jsFont->GetFont();
337     if (font == nullptr) {
338         delete[] buffer;
339         ROSEN_LOGE("JsTextBlob::MakeFromPosText: font is nullptr");
340         return nullptr;
341     }
342 
343     Point* points = new(std::nothrow) Point[pointsSize];
344     if (!points) {
345         delete[] buffer;
346         ROSEN_LOGE("JsTextBlob::MakeFromPosText: failed to create Point");
347         return nullptr;
348     }
349     if (!MakePoints(env, points, pointsSize, array)) {
350         delete[] buffer;
351         delete[] points;
352         ROSEN_LOGE("JsTextBlob::MakeFromPosText: Argv[2] is invalid");
353         return nullptr;
354     }
355     return getJsTextBlob(buffer, bufferLen, points, font, env);
356 }
357 
CreateJsTextBlob(napi_env env,const std::shared_ptr<TextBlob> textBlob)358 napi_value JsTextBlob::CreateJsTextBlob(napi_env env, const std::shared_ptr<TextBlob> textBlob)
359 {
360     napi_value constructor = nullptr;
361     napi_value result = nullptr;
362     napi_status status = napi_get_reference_value(env, constructor_, &constructor);
363     if (status == napi_ok) {
364         drawingTextBlob = textBlob;
365         status = napi_new_instance(env, constructor, 0, nullptr, &result);
366         if (status == napi_ok) {
367             return result;
368         } else {
369             ROSEN_LOGE("JsTextBlob::CreateJsTextBlob New instance could not be obtained");
370         }
371     }
372     return result;
373 }
374 
Bounds(napi_env env,napi_callback_info info)375 napi_value JsTextBlob::Bounds(napi_env env, napi_callback_info info)
376 {
377     JsTextBlob* me = CheckParamsAndGetThis<JsTextBlob>(env, info);
378     return (me != nullptr) ? me->OnBounds(env, info) : nullptr;
379 }
380 
OnBounds(napi_env env,napi_callback_info info)381 napi_value JsTextBlob::OnBounds(napi_env env, napi_callback_info info)
382 {
383     if (m_textBlob == nullptr) {
384         ROSEN_LOGE("JsTextBlob::OnBounds textBlob is nullptr");
385         return NapiThrowError(env, DrawingErrorCode::ERROR_INVALID_PARAM, "Invalid params.");
386     }
387     std::shared_ptr<Rect> rect = m_textBlob->Bounds();
388 
389     if (!rect) {
390         ROSEN_LOGE("JsTextBlob::OnBounds rect is nullptr");
391         return nullptr;
392     }
393     return GetRectAndConvertToJsValue(env, rect);
394 }
395 
GetTextBlob()396 std::shared_ptr<TextBlob> JsTextBlob::GetTextBlob()
397 {
398     return m_textBlob;
399 }
400 } // namespace Drawing
401 } // namespace OHOS::Rosen
402