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 "base/geometry/dimension.h"
17
18#include "core/pipeline/pipeline_base.h"
19
20namespace OHOS::Ace {
21
22namespace {
23struct CalcDimensionParam {
24    float value = 0.0f;
25    float vpScale = 0.0f;
26    float fpScale = 0.0f;
27    float lpxScale = 0.0f;
28    float parentLength = 0.0f;
29};
30
31using CalcDimensionFunc = std::function<bool(const CalcDimensionParam& param, double& result)>;
32bool CalcDimensionNone(const CalcDimensionParam& param, double& result)
33{
34    result = param.value;
35    return true;
36}
37
38bool CalcDimensionPx(const CalcDimensionParam& param, double& result)
39{
40    result = param.value;
41    return true;
42}
43
44bool CalcDimensionPercent(const CalcDimensionParam& param, double& result)
45{
46    if (NonNegative(param.parentLength)) {
47        result = param.value * param.parentLength;
48        return true;
49    }
50    return false;
51}
52
53bool CalcDimensionVp(const CalcDimensionParam& param, double& result)
54{
55    if (Positive(param.vpScale)) {
56        result = param.value * param.vpScale;
57        return true;
58    }
59    return false;
60}
61
62bool CalcDimensionFp(const CalcDimensionParam& param, double& result)
63{
64    if (Positive(param.fpScale) && Positive(param.vpScale)) {
65        result = param.value * param.fpScale * param.vpScale;
66        return true;
67    }
68    return false;
69}
70
71bool CalcDimensionLpx(const CalcDimensionParam& param, double& result)
72{
73    if (Positive(param.lpxScale)) {
74        result = param.value * param.lpxScale;
75        return true;
76    }
77    return false;
78}
79
80std::unordered_map<DimensionUnit, CalcDimensionFunc> calcDimensionFuncMap_ = {
81    { DimensionUnit::NONE, &CalcDimensionNone }, { DimensionUnit::PX, &CalcDimensionPx },
82    { DimensionUnit::PERCENT, &CalcDimensionPercent }, { DimensionUnit::VP, &CalcDimensionVp },
83    { DimensionUnit::FP, &CalcDimensionFp }, { DimensionUnit::LPX, &CalcDimensionLpx }
84};
85} // namespace
86
87double Dimension::ConvertToVp() const
88{
89    if (unit_ == DimensionUnit::VP) {
90        return value_;
91    }
92
93    auto pipeline = PipelineBase::GetCurrentContextSafely();
94    CHECK_NULL_RETURN(pipeline, 0.0);
95    if (unit_ == DimensionUnit::NONE) {
96        return value_ / pipeline->GetDipScale();
97    }
98    if (unit_ == DimensionUnit::PX) {
99        return value_ / pipeline->GetDipScale();
100    }
101    if (unit_ == DimensionUnit::FP) {
102        return ConvertToVpByAppFontScale();
103    }
104    if (unit_ == DimensionUnit::LPX) {
105        return value_ * pipeline->GetLogicScale() / pipeline->GetDipScale();
106    }
107    return 0.0;
108}
109
110double Dimension::ConvertToPx() const
111{
112    if (unit_ == DimensionUnit::NONE) {
113        return value_;
114    }
115    if (unit_ == DimensionUnit::PX) {
116        return value_;
117    }
118
119    auto pipeline = PipelineBase::GetCurrentContextSafely();
120    CHECK_NULL_RETURN(pipeline, 0.0);
121    if (unit_ == DimensionUnit::VP) {
122        return value_ * pipeline->GetDipScale();
123    }
124    if (unit_ == DimensionUnit::FP) {
125        return ConvertToPxByAppFontScale(0.0f);
126    }
127    if (unit_ == DimensionUnit::LPX) {
128        return value_ * pipeline->GetLogicScale();
129    }
130    return 0.0;
131}
132
133double Dimension::ConvertToFp() const
134{
135    if (unit_ == DimensionUnit::FP) {
136        return value_;
137    }
138    auto pipeline = PipelineBase::GetCurrentContextSafely();
139    CHECK_NULL_RETURN(pipeline, 0.0);
140    auto fontScale = std::clamp(pipeline->GetFontScale(), 0.0f, pipeline->GetMaxAppFontScale());
141    if (LessOrEqual(fontScale, 0.0)) {
142        return 0.0;
143    }
144    if (unit_ == DimensionUnit::NONE) {
145        return value_ / pipeline->GetDipScale() / fontScale;
146    }
147    if (unit_ == DimensionUnit::PX) {
148        return value_ / pipeline->GetDipScale() / fontScale;
149    }
150    if (unit_ == DimensionUnit::VP) {
151        return value_ / fontScale;
152    }
153    if (unit_ == DimensionUnit::LPX) {
154        return value_ * pipeline->GetLogicScale() / pipeline->GetDipScale() / fontScale;
155    }
156    return 0.0;
157}
158
159double Dimension::ConvertToPxWithSize(double size) const
160{
161    if (unit_ == DimensionUnit::PERCENT) {
162        return value_ * size;
163    }
164    return ConvertToPx();
165}
166
167DimensionUnit Dimension::GetAdaptDimensionUnit(const Dimension& dimension)
168{
169    return static_cast<int32_t>(unit_) <= static_cast<int32_t>(dimension.unit_) ? unit_ : dimension.unit_;
170}
171
172double Dimension::ConvertToPxDistribute(
173    std::optional<float> minOptional, std::optional<float> maxOptional, bool allowScale) const
174{
175    if (unit_ != DimensionUnit::FP) {
176        return ConvertToPx();
177    }
178    auto pipeline = PipelineBase::GetCurrentContextSafely();
179    CHECK_NULL_RETURN(pipeline, value_);
180    if (!allowScale) {
181        return value_ * pipeline->GetDipScale();
182    }
183    auto minFontScale = minOptional.value_or(0.0f);
184    auto maxFontScale = maxOptional.value_or(static_cast<float>(INT32_MAX));
185    if (!maxOptional.has_value()) {
186        return ConvertToPxByAppFontScale(minFontScale);
187    }
188    return ConvertToPxByCustomFontScale(minFontScale, maxFontScale);
189}
190
191double Dimension::ConvertToPxByCustomFontScale(float minFontScale, float maxFontScale) const
192{
193    auto pipeline = PipelineBase::GetCurrentContextSafely();
194    CHECK_NULL_RETURN(pipeline, value_);
195    float fontScale = std::clamp(pipeline->GetFontScale(), minFontScale, maxFontScale);
196    return value_ * pipeline->GetDipScale() * fontScale;
197}
198
199double Dimension::ConvertToPxByAppFontScale(float minFontScale) const
200{
201    auto pipeline = PipelineBase::GetCurrentContextSafely();
202    CHECK_NULL_RETURN(pipeline, value_);
203    float maxFontScale = pipeline->GetMaxAppFontScale();
204    float fontScale = std::clamp(pipeline->GetFontScale(), minFontScale, maxFontScale);
205    return value_ * pipeline->GetDipScale() * fontScale;
206}
207
208double Dimension::ConvertToVpByAppFontScale() const
209{
210    auto pipeline = PipelineBase::GetCurrentContextSafely();
211    CHECK_NULL_RETURN(pipeline, value_);
212    CHECK_NULL_RETURN(pipeline->IsFollowSystem(), value_);
213    float maxFontScale = pipeline->GetMaxAppFontScale();
214    float fontScale = std::clamp(pipeline->GetFontScale(), 0.0f, maxFontScale);
215    return value_ * fontScale;
216}
217
218std::string Dimension::ToString() const
219{
220    static const int32_t unitsNum = 6;
221    static const int32_t percentIndex = 3;
222    static const int32_t percentUnit = 100;
223    static std::array<std::string, unitsNum> units = { "px", "vp", "fp", "%", "lpx", "auto" };
224    if (static_cast<int32_t>(unit_) >= unitsNum ||
225        static_cast<int32_t>(unit_) < static_cast<int32_t>(DimensionUnit::INVALID)) {
226        return StringUtils::DoubleToString(value_).append("px");
227    }
228    if (unit_ == DimensionUnit::NONE) {
229        return StringUtils::DoubleToString(value_).append("none");
230    }
231    if (unit_ == DimensionUnit::INVALID) {
232        return StringUtils::DoubleToString(value_).append("invalid");
233    }
234    if (units[static_cast<int>(unit_)] == units[percentIndex]) {
235        return StringUtils::DoubleToString(value_ * percentUnit).append(units[static_cast<int>(unit_)]);
236    }
237    return StringUtils::DoubleToString(value_).append(units[static_cast<int>(unit_)]);
238}
239
240// for example str = 0.00px
241Dimension Dimension::FromString(const std::string& str)
242{
243    static const int32_t percentUnit = 100;
244    static const std::unordered_map<std::string, DimensionUnit> uMap {
245        { "px", DimensionUnit::PX },
246        { "vp", DimensionUnit::VP },
247        { "fp", DimensionUnit::FP },
248        { "%", DimensionUnit::PERCENT },
249        { "lpx", DimensionUnit::LPX },
250        { "auto", DimensionUnit::AUTO },
251    };
252
253    double value = 0.0;
254    DimensionUnit unit = DimensionUnit::FP;
255
256    if (str.empty()) {
257        LOGE("UITree |ERROR| empty string");
258        return Dimension(value, unit);
259    }
260
261    for (int32_t i = static_cast<int32_t>(str.length() - 1); i >= 0; --i) {
262        if (str[i] >= '0' && str[i] <= '9') {
263            value = StringUtils::StringToDouble(str.substr(0, i + 1));
264            auto subStr = str.substr(i + 1);
265            auto iter = uMap.find(subStr);
266            if (iter != uMap.end()) {
267                unit = iter->second;
268            }
269            value = unit == DimensionUnit::PERCENT ? value / percentUnit : value;
270            break;
271        }
272    }
273    return Dimension(value, unit);
274}
275
276bool Dimension::NormalizeToPx(
277    double vpScale, double fpScale, double lpxScale, double parentLength, double& result) const
278{
279    auto func = calcDimensionFuncMap_.find(unit_);
280    if (func != calcDimensionFuncMap_.end()) {
281        CalcDimensionParam param = { value_, vpScale, fpScale, lpxScale, parentLength };
282        return func->second(param, result);
283    }
284    return false;
285}
286} // namespace OHOS::Ace
287