1/*
2 * Copyright (c) 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
17#include "interfaces/napi/kits/utils/napi_utils.h"
18
19#include "bridge/common/utils/engine_helper.h"
20
21extern const char _binary_measure_js_start[];
22extern const char _binary_measure_abc_start[];
23#if !defined(IOS_PLATFORM)
24extern const char _binary_measure_js_end[];
25extern const char _binary_measure_abc_end[];
26#else
27extern const char* _binary_measure_js_end;
28extern const char* _binary_measure_abc_end;
29#endif
30
31namespace OHOS::Ace::Napi {
32namespace {
33Dimension MeasureStringToDimensionWithUnit(const std::string& value, bool& useDefaultUnit,
34    DimensionUnit defaultUnit = DimensionUnit::PX, float defaultValue = 0.0f, bool isCalc = false)
35{
36    errno = 0;
37    if (std::strcmp(value.c_str(), "auto") == 0) {
38        return Dimension(defaultValue, DimensionUnit::AUTO);
39    }
40    char* pEnd = nullptr;
41    double result = std::strtod(value.c_str(), &pEnd);
42    if (pEnd == value.c_str() || errno == ERANGE) {
43        useDefaultUnit = true;
44        return Dimension(defaultValue, defaultUnit);
45    }
46    if (pEnd != nullptr) {
47        if (std::strcmp(pEnd, "%") == 0) {
48            // Parse percent, transfer from [0, 100] to [0, 1]
49            return Dimension(result / 100.0, DimensionUnit::PERCENT);
50        }
51        if (std::strcmp(pEnd, "px") == 0) {
52            return Dimension(result, DimensionUnit::PX);
53        }
54        if (std::strcmp(pEnd, "vp") == 0) {
55            return Dimension(result, DimensionUnit::VP);
56        }
57        if (std::strcmp(pEnd, "fp") == 0) {
58            return Dimension(result, DimensionUnit::FP);
59        }
60        if (std::strcmp(pEnd, "lpx") == 0) {
61            return Dimension(result, DimensionUnit::LPX);
62        }
63        if ((std::strcmp(pEnd, "\0") == 0) && isCalc) {
64            return Dimension(result, DimensionUnit::NONE);
65        }
66        if (isCalc) {
67            return Dimension(result, DimensionUnit::INVALID);
68        }
69    }
70    useDefaultUnit = true;
71    return Dimension(result, defaultUnit);
72}
73} // namespace
74static int32_t HandleIntStyle(napi_value fontStyleNApi, napi_env env)
75{
76    size_t ret = 0;
77    int32_t fontStyleInt = 0;
78    std::string fontStyleStr;
79    napi_valuetype valueType = napi_undefined;
80    napi_typeof(env, fontStyleNApi, &valueType);
81    if (valueType == napi_string) {
82        size_t fontStyleLen = GetParamLen(env, fontStyleNApi) + 1;
83        std::unique_ptr<char[]> fontStyleTemp = std::make_unique<char[]>(fontStyleLen);
84        napi_get_value_string_utf8(env, fontStyleNApi, fontStyleTemp.get(), fontStyleLen, &ret);
85        fontStyleStr = fontStyleTemp.get();
86        fontStyleInt = StringUtils::StringToInt(fontStyleStr);
87    } else if (valueType == napi_number) {
88        napi_get_value_int32(env, fontStyleNApi, &fontStyleInt);
89    } else if (valueType == napi_object) {
90        ResourceInfo recv;
91        if (!ParseResourceParam(env, fontStyleNApi, recv)) {
92            return fontStyleInt;
93        }
94        if (!ParseString(recv, fontStyleStr)) {
95            return fontStyleInt;
96        }
97        fontStyleInt = StringUtils::StringToInt(fontStyleStr);
98    } else {
99        return fontStyleInt;
100    }
101    return fontStyleInt;
102}
103
104static std::string HandleStringType(napi_value ParameterNApi, napi_env env)
105{
106    size_t ret = 0;
107    std::string ParameterStr;
108    int32_t ParameterInt = 0;
109    napi_valuetype valueType = napi_undefined;
110    napi_typeof(env, ParameterNApi, &valueType);
111    if (valueType == napi_string) {
112        size_t ParameterLen = GetParamLen(env, ParameterNApi) + 1;
113        std::unique_ptr<char[]> Parameter = std::make_unique<char[]>(ParameterLen);
114        napi_get_value_string_utf8(env, ParameterNApi, Parameter.get(), ParameterLen, &ret);
115        ParameterStr = Parameter.get();
116    } else if (valueType == napi_number) {
117        napi_get_value_int32(env, ParameterNApi, &ParameterInt);
118        ParameterStr = std::to_string(ParameterInt);
119    } else if (valueType == napi_object) {
120        ResourceInfo recv;
121        if (!ParseResourceParam(env, ParameterNApi, recv)) {
122            return ParameterStr;
123        }
124        if (!ParseString(recv, ParameterStr)) {
125            return ParameterStr;
126        }
127    } else {
128        return ParameterStr;
129    }
130    return ParameterStr;
131}
132
133static std::optional<Dimension> HandleDimensionType(
134    napi_value ParameterNApi, napi_env env, DimensionUnit defaultUnit, bool& useDefaultUnit)
135{
136    size_t ret = 0;
137    std::string ParameterStr;
138    napi_valuetype valueType = napi_undefined;
139    napi_typeof(env, ParameterNApi, &valueType);
140    Dimension Parameter;
141    if (valueType == napi_number) {
142        double ParameterValue;
143        napi_get_value_double(env, ParameterNApi, &ParameterValue);
144        Parameter.SetValue(ParameterValue);
145        Parameter.SetUnit(defaultUnit);
146        useDefaultUnit = true;
147    } else if (valueType == napi_string) {
148        size_t ParameterLen = GetParamLen(env, ParameterNApi) + 1;
149        std::unique_ptr<char[]> ParameterTemp = std::make_unique<char[]>(ParameterLen);
150        napi_get_value_string_utf8(env, ParameterNApi, ParameterTemp.get(), ParameterLen, &ret);
151        ParameterStr = ParameterTemp.get();
152        Parameter = MeasureStringToDimensionWithUnit(ParameterStr, useDefaultUnit, defaultUnit);
153    } else if (valueType == napi_object) {
154        ResourceInfo recv;
155        if (!ParseResourceParam(env, ParameterNApi, recv)) {
156            return std::nullopt;
157        }
158        if (!ParseString(recv, ParameterStr)) {
159            return std::nullopt;
160        }
161        if (!ParseIntegerToString(recv, ParameterStr)) {
162            return std::nullopt;
163        }
164        Parameter = MeasureStringToDimensionWithUnit(ParameterStr, useDefaultUnit, defaultUnit);
165    } else {
166        return std::nullopt;
167    }
168    return Parameter;
169}
170
171static napi_value JSMeasureText(napi_env env, napi_callback_info info)
172{
173    size_t argc = 1;
174    napi_value result = nullptr;
175    napi_value argv = nullptr;
176    napi_value thisvar = nullptr;
177    void* data = nullptr;
178    napi_get_cb_info(env, info, &argc, &argv, &thisvar, &data);
179
180    napi_value textContentNApi = nullptr;
181    napi_value fontSizeNApi = nullptr;
182    napi_value fontStyleNApi = nullptr;
183    napi_value fontWeightNApi = nullptr;
184    napi_value fontFamilyNApi = nullptr;
185    napi_value letterSpacingNApi = nullptr;
186
187    napi_valuetype valueType = napi_undefined;
188    napi_typeof(env, argv, &valueType);
189    if (valueType == napi_object) {
190        napi_get_named_property(env, argv, "textContent", &textContentNApi);
191        napi_get_named_property(env, argv, "fontSize", &fontSizeNApi);
192        napi_get_named_property(env, argv, "fontStyle", &fontStyleNApi);
193        napi_get_named_property(env, argv, "fontWeight", &fontWeightNApi);
194        napi_get_named_property(env, argv, "fontFamily", &fontFamilyNApi);
195        napi_get_named_property(env, argv, "letterSpacing", &letterSpacingNApi);
196    } else {
197        return nullptr;
198    }
199    MeasureContext context;
200    auto isFontSizeUseDefaultUnit = false;
201    std::optional<Dimension> fontSizeNum =
202        HandleDimensionType(fontSizeNApi, env, DimensionUnit::FP, isFontSizeUseDefaultUnit);
203    context.isFontSizeUseDefaultUnit = isFontSizeUseDefaultUnit;
204    std::optional<Dimension> letterSpace =
205        HandleDimensionType(letterSpacingNApi, env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
206    int32_t fontStyle = HandleIntStyle(fontStyleNApi, env);
207    std::string textContent = HandleStringType(textContentNApi, env);
208    std::string fontWeight = HandleStringType(fontWeightNApi, env);
209    std::string fontFamily = HandleStringType(fontFamilyNApi, env);
210    context.textContent = textContent;
211    context.fontSize = fontSizeNum;
212    context.fontStyle = static_cast<FontStyle>(fontStyle);
213    context.fontWeight = fontWeight;
214    context.fontFamily = fontFamily;
215    context.letterSpacing = letterSpace;
216    auto delegate = EngineHelper::GetCurrentDelegateSafely();
217    if (!delegate) {
218        return nullptr;
219    }
220    double textWidth = delegate->MeasureText(context);
221    napi_create_double(env, textWidth, &result);
222    return result;
223}
224
225static void CreateMeasureTextSizeParamMap(std::map<std::string, napi_value>& contextParamMap)
226{
227    napi_value textContentNApi = nullptr;
228    napi_value constraintWidthNApi = nullptr;
229    napi_value fontSizeNApi = nullptr;
230    napi_value fontStyleNApi = nullptr;
231    napi_value fontWeightNApi = nullptr;
232    napi_value fontFamilyNApi = nullptr;
233    napi_value letterSpacingNApi = nullptr;
234    napi_value textAlignNApi = nullptr;
235    napi_value textOverFlowNApi = nullptr;
236    napi_value maxLinesNApi = nullptr;
237    napi_value lineHeightNApi = nullptr;
238    napi_value baselineOffsetNApi = nullptr;
239    napi_value textCaseNApi = nullptr;
240    napi_value textIndentNApi = nullptr;
241    napi_value wordBreakNApi = nullptr;
242    contextParamMap["textContentNApi"] = textContentNApi;
243    contextParamMap["constraintWidthNApi"] = constraintWidthNApi;
244    contextParamMap["fontSizeNApi"] = fontSizeNApi;
245    contextParamMap["fontStyleNApi"] = fontStyleNApi;
246    contextParamMap["fontWeightNApi"] = fontWeightNApi;
247    contextParamMap["fontFamilyNApi"] = fontFamilyNApi;
248    contextParamMap["letterSpacingNApi"] = letterSpacingNApi;
249    contextParamMap["textAlignNApi"] = textAlignNApi;
250    contextParamMap["textOverFlowNApi"] = textOverFlowNApi;
251    contextParamMap["maxLinesNApi"] = maxLinesNApi;
252    contextParamMap["lineHeightNApi"] = lineHeightNApi;
253    contextParamMap["baselineOffsetNApi"] = baselineOffsetNApi;
254    contextParamMap["textCaseNApi"] = textCaseNApi;
255    contextParamMap["textIndentNApi"] = textIndentNApi;
256    contextParamMap["wordBreakNApi"] = wordBreakNApi;
257}
258
259static void SetMeasureTextNapiProperty(
260    std::map<std::string, napi_value>& contextParamMap, napi_value& argv, napi_env& env)
261{
262    napi_get_named_property(env, argv, "textContent", &contextParamMap["textContentNApi"]);
263    napi_get_named_property(env, argv, "constraintWidth", &contextParamMap["constraintWidthNApi"]);
264    napi_get_named_property(env, argv, "fontSize", &contextParamMap["fontSizeNApi"]);
265    napi_get_named_property(env, argv, "fontStyle", &contextParamMap["fontStyleNApi"]);
266    napi_get_named_property(env, argv, "fontWeight", &contextParamMap["fontWeightNApi"]);
267    napi_get_named_property(env, argv, "fontFamily", &contextParamMap["fontFamilyNApi"]);
268    napi_get_named_property(env, argv, "letterSpacing", &contextParamMap["letterSpacingNApi"]);
269    napi_get_named_property(env, argv, "textAlign", &contextParamMap["textAlignNApi"]);
270    napi_get_named_property(env, argv, "overflow", &contextParamMap["textOverFlowNApi"]);
271    napi_get_named_property(env, argv, "maxLines", &contextParamMap["maxLinesNApi"]);
272    napi_get_named_property(env, argv, "lineHeight", &contextParamMap["lineHeightNApi"]);
273    napi_get_named_property(env, argv, "baselineOffset", &contextParamMap["baselineOffsetNApi"]);
274    napi_get_named_property(env, argv, "textCase", &contextParamMap["textCaseNApi"]);
275    napi_get_named_property(env, argv, "textIndent", &contextParamMap["textIndentNApi"]);
276    bool hasElement = false;
277    napi_has_named_property(env, argv, "wordBreak", &hasElement);
278    if (hasElement) {
279        napi_get_named_property(env, argv, "wordBreak", &contextParamMap["wordBreakNApi"]);
280    }
281}
282
283static void SetContextProperty(
284    std::map<std::string, napi_value>& contextParamMap, MeasureContext& context, napi_env& env)
285{
286    auto isFontSizeUseDefaultUnit = false;
287    std::optional<Dimension> fontSizeNum =
288        HandleDimensionType(contextParamMap["fontSizeNApi"], env, DimensionUnit::FP, isFontSizeUseDefaultUnit);
289    context.isFontSizeUseDefaultUnit = isFontSizeUseDefaultUnit;
290    std::optional<Dimension> letterSpace =
291        HandleDimensionType(contextParamMap["letterSpacingNApi"], env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
292    std::optional<Dimension> constraintWidth =
293        HandleDimensionType(contextParamMap["constraintWidthNApi"], env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
294    std::optional<Dimension> lineHeight =
295        HandleDimensionType(contextParamMap["lineHeightNApi"], env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
296    std::optional<Dimension> baselineOffset =
297        HandleDimensionType(contextParamMap["baselineOffsetNApi"], env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
298    std::optional<Dimension> textIndent =
299        HandleDimensionType(contextParamMap["textIndentNApi"], env, DimensionUnit::VP, isFontSizeUseDefaultUnit);
300
301    int32_t fontStyle = HandleIntStyle(contextParamMap["fontStyleNApi"], env);
302    int32_t textAlign = HandleIntStyle(contextParamMap["textAlignNApi"], env);
303    int32_t textOverFlow = HandleIntStyle(contextParamMap["textOverFlowNApi"], env);
304    int32_t maxlines = HandleIntStyle(contextParamMap["maxLinesNApi"], env);
305    int32_t textCase = HandleIntStyle(contextParamMap["textCaseNApi"], env);
306
307    if (contextParamMap["wordBreakNApi"] != nullptr) {
308        napi_valuetype jsValueType = napi_undefined;
309        napi_typeof(env, contextParamMap["wordBreakNApi"], &jsValueType);
310        if (jsValueType != napi_undefined) {
311            int32_t wordBreak = HandleIntStyle(contextParamMap["wordBreakNApi"], env);
312            context.wordBreak = static_cast<WordBreak>(wordBreak);
313        }
314    }
315
316    std::string textContent = HandleStringType(contextParamMap["textContentNApi"], env);
317    std::string fontWeight = HandleStringType(contextParamMap["fontWeightNApi"], env);
318    std::string fontFamily = HandleStringType(contextParamMap["fontFamilyNApi"], env);
319
320    context.textContent = textContent;
321    context.constraintWidth = constraintWidth;
322    context.fontSize = fontSizeNum;
323    context.fontStyle = static_cast<FontStyle>(fontStyle);
324    context.fontWeight = fontWeight;
325    context.fontFamily = fontFamily;
326    context.letterSpacing = letterSpace;
327    context.textAlign = static_cast<TextAlign>(textAlign);
328    context.textOverlayFlow = static_cast<TextOverflow>(textOverFlow);
329    context.maxlines = maxlines;
330    context.lineHeight = lineHeight;
331    context.baselineOffset = baselineOffset;
332    context.textCase = static_cast<TextCase>(textCase);
333    context.textIndent = textIndent;
334}
335
336static napi_value JSMeasureTextSize(napi_env env, napi_callback_info info)
337{
338    size_t argc = 1;
339    napi_value result = nullptr;
340    napi_value argv = nullptr;
341    napi_value thisvar = nullptr;
342    void* data = nullptr;
343    napi_get_cb_info(env, info, &argc, &argv, &thisvar, &data);
344
345    std::map<std::string, napi_value> contextParamMap;
346    CreateMeasureTextSizeParamMap(contextParamMap);
347    napi_valuetype valueType = napi_undefined;
348    napi_typeof(env, argv, &valueType);
349    MeasureContext context;
350    if (valueType == napi_object) {
351        SetMeasureTextNapiProperty(contextParamMap, argv, env);
352    } else {
353        return nullptr;
354    }
355    SetContextProperty(contextParamMap, context, env);
356    auto delegate = EngineHelper::GetCurrentDelegateSafely();
357    if (!delegate) {
358        return nullptr;
359    }
360    Size textSize = delegate->MeasureTextSize(context);
361
362    napi_escapable_handle_scope scope = nullptr;
363    napi_open_escapable_handle_scope(env, &scope);
364    if (scope == nullptr) {
365        return result;
366    }
367
368    napi_value resultArray[2] = { 0 };
369    napi_create_double(env, textSize.Width(), &resultArray[0]);
370    napi_create_double(env, textSize.Height(), &resultArray[1]);
371
372    napi_create_object(env, &result);
373    napi_set_named_property(env, result, "width", resultArray[0]);
374    napi_set_named_property(env, result, "height", resultArray[1]);
375
376    napi_value newResult = nullptr;
377    napi_escape_handle(env, scope, result, &newResult);
378    napi_close_escapable_handle_scope(env, scope);
379    return result;
380}
381
382static napi_value MeasureExport(napi_env env, napi_value exports)
383{
384    napi_property_descriptor measureDesc[] = {
385        DECLARE_NAPI_FUNCTION("measureText", JSMeasureText),
386        DECLARE_NAPI_FUNCTION("measureTextSize", JSMeasureTextSize),
387    };
388    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(measureDesc) / sizeof(measureDesc[0]), measureDesc));
389    return exports;
390}
391
392extern "C" __attribute__((visibility("default"))) void NAPI_measure_GetJSCode(const char** buf, int* bufLen)
393{
394    if (buf != nullptr) {
395        *buf = _binary_measure_js_start;
396    }
397
398    if (bufLen != nullptr) {
399        *bufLen = _binary_measure_js_end - _binary_measure_js_start;
400    }
401}
402
403extern "C" __attribute__((visibility("default"))) void NAPI_measure_GetABCCode(const char** buf, int* buflen)
404{
405    if (buf != nullptr) {
406        *buf = _binary_measure_abc_start;
407    }
408    if (buflen != nullptr) {
409        *buflen = _binary_measure_abc_end - _binary_measure_abc_start;
410    }
411}
412
413static napi_module_with_js measureModule = {
414    .nm_version = 1,
415    .nm_flags = 0,
416    .nm_filename = "libmeasure.z.so/measure.js",
417    .nm_register_func = MeasureExport,
418    .nm_modname = "measure",
419    .nm_priv = ((void*)0),
420    .nm_get_abc_code = NAPI_measure_GetABCCode,
421    .nm_get_js_code = NAPI_measure_GetJSCode,
422};
423
424extern "C" __attribute__((constructor)) void MeasureRegister()
425{
426    napi_module_with_js_register(&measureModule);
427}
428} // namespace OHOS::Ace::Napi
429