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 "core/components/transform/render_transform.h"
17 
18 #include "base/utils/utils.h"
19 #include "core/components/box/render_box_base.h"
20 #include "core/components/transform/transform_component.h"
21 
22 namespace OHOS::Ace {
23 
24 // Effect translate to Matrix at the end for percent needs to be calculated after layout.
Translate(const Dimension & x,const Dimension & y)25 void RenderTransform::Translate(const Dimension& x, const Dimension& y)
26 {
27     Translate(x, y, Dimension {});
28 }
29 
Translate(const Dimension & x,const Dimension & y,const Dimension & z)30 void RenderTransform::Translate(const Dimension& x, const Dimension& y, const Dimension& z)
31 {
32     auto dx = CovertDimensionToPxBySize(x, GetLayoutSize().Width());
33     auto dy = CovertDimensionToPxBySize(y, GetLayoutSize().Height());
34     // not support percent
35     auto dz = CovertDimensionToPxBySize(z, 0.0);
36 
37     transform_ = transform_ * Matrix4::CreateTranslate(dx, dy, dz);
38     UpdateTransformLayer();
39     auto context = context_.Upgrade();
40     if (context) {
41         context->MarkForcedRefresh();
42     }
43 #if defined(PREVIEW)
44     UpdateTranslateToAccessibilityNode(x.Value(), y.Value());
45 #endif
46 }
47 
Scale(float value)48 void RenderTransform::Scale(float value)
49 {
50     Scale(value, value);
51 }
52 
Scale(float x,float y)53 void RenderTransform::Scale(float x, float y)
54 {
55     Scale(x, y, 1.0f);
56 }
57 
Scale(float x,float y,float z)58 void RenderTransform::Scale(float x, float y, float z)
59 {
60     transform_ = transform_ * Matrix4::CreateScale(x, y, z);
61     UpdateTransformLayer();
62     auto context = context_.Upgrade();
63     if (context) {
64         context->MarkForcedRefresh();
65     }
66 #if defined(PREVIEW)
67     if (!NearEqual(maxScaleXY_, -1.0)) {
68         UpdateScaleToAccessibilityNode(maxScaleXY_);
69     }
70 #endif
71 }
72 
Skew(float x,float y)73 void RenderTransform::Skew(float x, float y)
74 {
75     if (!NearZero(x) || !NearZero(y)) {
76         Matrix4 skew = Matrix4::CreateSkew(x, y);
77         transform_ = transform_ * skew;
78     }
79     UpdateTransformLayer();
80     auto context = context_.Upgrade();
81     if (context) {
82         context->MarkForcedRefresh();
83     }
84 }
85 
Rotate(float angle,float x,float y,float z)86 void RenderTransform::Rotate(float angle, float x, float y, float z)
87 {
88     if (!NearZero(angle) && !NearZero(fmod(angle, 360.0f))) {
89         Matrix4 rotate = Matrix4::CreateRotate(angle, x, y, z);
90         transform_ = transform_ * rotate;
91     }
92     UpdateTransformLayer();
93     auto context = context_.Upgrade();
94     if (context) {
95         context->MarkForcedRefresh();
96     }
97 #if defined(PREVIEW)
98     if (!NearEqual(angle, 0.0) && !NearEqual(z, 0.0)) {
99         UpdateRotateToAccessibilityNode(angle, RotateAxis::AXIS_Z);
100     }
101 #endif
102 }
103 
RotateX(float angle)104 void RenderTransform::RotateX(float angle)
105 {
106     Rotate(angle, 1.0f, 0.0f, 0.0f);
107 }
108 
RotateY(float angle)109 void RenderTransform::RotateY(float angle)
110 {
111     Rotate(angle, 0.0f, 1.0f, 0.0f);
112 }
113 
RotateZ(float angle)114 void RenderTransform::RotateZ(float angle)
115 {
116     Rotate(angle, 0.0f, 0.0f, 1.0f);
117 #if defined(PREVIEW)
118     if (!NearEqual(angle, 0.0)) {
119         UpdateRotateToAccessibilityNode(angle, RotateAxis::AXIS_Z);
120     }
121 #endif
122 }
123 
Matrix3D(Matrix4 m)124 void RenderTransform::Matrix3D(Matrix4 m)
125 {
126     if (!m.IsIdentityMatrix()) {
127         transform_ = transform_ * m;
128         UpdateTransformLayer();
129         auto context = context_.Upgrade();
130         if (context) {
131             context->MarkForcedRefresh();
132         }
133     }
134 }
135 
Perspective(const Dimension & distance)136 void RenderTransform::Perspective(const Dimension& distance)
137 {
138     if (!NearZero(distance.Value())) {
139         auto dx = CovertDimensionToPxBySize(distance, 0);
140         transform_ = transform_ * Matrix4::CreatePerspective(static_cast<float>(dx));
141         UpdateTransformLayer();
142         auto context = context_.Upgrade();
143         if (context) {
144             context->MarkForcedRefresh();
145         }
146     }
147 }
148 
ResetTransform()149 void RenderTransform::ResetTransform()
150 {
151     transform_ = Matrix4::CreateIdentity();
152 #if defined(PREVIEW)
153     ResetTransformToAccessibilityNode();
154 #endif
155 }
156 
157 #if defined(PREVIEW)
ResetTransformToAccessibilityNode()158 void RenderTransform::ResetTransformToAccessibilityNode()
159 {
160     const auto& context = context_.Upgrade();
161     if (!context) {
162         return;
163     }
164     auto accessibilityManager = context->GetAccessibilityManager();
165     if (!accessibilityManager) {
166         LOGE("accessibilityManager is null");
167         return;
168     }
169     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(GetNodeId());
170     if (!accessibilityNode) {
171         LOGE("RenderTransform is null");
172         return;
173     }
174     accessibilityNode->SetScaleToChild(1.0);
175     accessibilityNode->SetTranslateOffsetToChild(Offset(0.0, 0.0));
176     accessibilityNode->SetRotateToChild(0.0, RotateAxis::AXIS_Z);
177     for (const auto& item : GetChildren()) {
178         item->NotifyPaintFinish();
179     }
180 }
181 
UpdateScaleToAccessibilityNode(float maxScale)182 void RenderTransform::UpdateScaleToAccessibilityNode(float maxScale)
183 {
184     const auto& context = context_.Upgrade();
185     if (!context) {
186         return;
187     }
188     auto accessibilityManager = context->GetAccessibilityManager();
189     if (!accessibilityManager) {
190         LOGE("accessibilityManager is null");
191         return;
192     }
193     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(GetNodeId());
194     if (!accessibilityNode) {
195         LOGE("RenderTransform is null");
196         return;
197     }
198 
199     if (!NearEqual(maxScale, 1.0)) {
200         Size size = GetLayoutSize();
201         Offset globalOffset = GetGlobalOffset();
202         Offset scaleCenter =
203             Offset(globalOffset.GetX() + size.Width() / 2.0, globalOffset.GetY() + size.Height() / 2.0);
204         accessibilityNode->SetScaleToChild(maxScale);
205         accessibilityNode->SetScaleCenterToChild(scaleCenter);
206         for (const auto& item : GetChildren()) {
207             item->NotifyPaintFinish();
208         }
209     }
210 }
211 
UpdateTranslateToAccessibilityNode(double translateX,double translateY)212 void RenderTransform::UpdateTranslateToAccessibilityNode(double translateX, double translateY)
213 {
214     const auto& context = context_.Upgrade();
215     if (!context) {
216         return;
217     }
218     auto accessibilityManager = context->GetAccessibilityManager();
219     if (!accessibilityManager) {
220         LOGE("accessibilityManager is null");
221         return;
222     }
223     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(GetNodeId());
224     if (!accessibilityNode) {
225         LOGE("RenderTransform is null");
226         return;
227     }
228     if (!NearEqual(translateX, 0.0) || !NearEqual(translateY, 0.0)) {
229         Offset translateOffset(translateX, translateY);
230         accessibilityNode->SetTranslateOffsetToChild(translateOffset);
231         for (const auto& child : GetChildren()) {
232             child->NotifyPaintFinish();
233         }
234     }
235 }
236 
UpdateRotateToAccessibilityNode(float angle,RotateAxis rotateAxis)237 void RenderTransform::UpdateRotateToAccessibilityNode(float angle, RotateAxis rotateAxis)
238 {
239     const auto& context = context_.Upgrade();
240     if (!context) {
241         return;
242     }
243     auto accessibilityManager = context->GetAccessibilityManager();
244     if (!accessibilityManager) {
245         LOGE("accessibilityManager is null");
246         return;
247     }
248     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(GetNodeId());
249     if (!accessibilityNode) {
250         LOGE("RenderTransform is null");
251         return;
252     }
253     if (!NearEqual(angle, 0.0)) {
254         accessibilityNode->SetRotateToChild(angle, rotateAxis);
255         Size size = GetLayoutSize();
256         Offset globalOffset = GetGlobalOffset();
257         Offset scaleCenter =
258             Offset(globalOffset.GetX() + size.Width() / 2.0, globalOffset.GetY() + size.Height() / 2.0);
259         accessibilityNode->SetScaleCenterToChild(scaleCenter);
260         for (const auto& item : GetChildren()) {
261             item->NotifyPaintFinish();
262         }
263     }
264 }
265 #endif
266 
ParseTransformEffects(const std::vector<AnimatableTransformOperation> & transformEffects,Matrix4 & transform)267 void RenderTransform::ParseTransformEffects(
268     const std::vector<AnimatableTransformOperation>& transformEffects, Matrix4& transform)
269 {
270     for (const auto& effect : transformEffects) {
271         transform = transform * ParseTransformEffect(effect);
272     }
273 }
274 
ParseTransformEffect(const TransformOperation & effect)275 Matrix4 RenderTransform::ParseTransformEffect(const TransformOperation& effect)
276 {
277     switch (effect.type_) {
278         case TransformOperationType::TRANSLATE: {
279             auto& translate = effect.translateOperation_;
280             float dx = CovertDimensionToPxBySize(translate.dx, GetLayoutSize().Width());
281             float dy = CovertDimensionToPxBySize(translate.dy, GetLayoutSize().Height());
282             // dz not support percent
283             float dz = CovertDimensionToPxBySize(translate.dz, 0.0);
284             return Matrix4::CreateTranslate(dx, dy, dz);
285         }
286         case TransformOperationType::SCALE: {
287             auto& scale = effect.scaleOperation_;
288             return Matrix4::CreateScale(scale.scaleX, scale.scaleY, scale.scaleZ);
289         }
290         case TransformOperationType::SKEW: {
291             auto& skew = effect.skewOperation_;
292             return Matrix4::CreateSkew(skew.skewX, skew.skewY);
293         }
294         case TransformOperationType::ROTATE: {
295             auto& rotate = effect.rotateOperation_;
296             return Matrix4::CreateRotate(rotate.angle, rotate.dx, rotate.dy, rotate.dz);
297         }
298         case TransformOperationType::MATRIX: {
299             auto& matrix = effect.matrix4_;
300             return matrix;
301         }
302         case TransformOperationType::PERSPECTIVE: {
303             auto& perspective = effect.perspectiveOperation_;
304             double distance = CovertDimensionToPxBySize(perspective.distance, 0.0);
305             return Matrix4::CreatePerspective(distance);
306         }
307         case TransformOperationType::UNDEFINED:
308         default:
309             LOGE("unknown transform operation type %{public}d", static_cast<int32_t>(effect.type_));
310             return Matrix4::CreateIdentity();
311     }
312 }
313 
ParseDimension(TransformOperation & effect)314 void RenderTransform::ParseDimension(TransformOperation& effect)
315 {
316     switch (effect.type_) {
317         case TransformOperationType::TRANSLATE: {
318             auto& translate = effect.translateOperation_;
319             translate.dx = Dimension(CovertDimensionToPxBySize(translate.dx, GetLayoutSize().Width()));
320             translate.dy = Dimension(CovertDimensionToPxBySize(translate.dy, GetLayoutSize().Height()));
321             // dz not support percent
322             translate.dz = Dimension(CovertDimensionToPxBySize(translate.dz, 0.0));
323             break;
324         }
325         case TransformOperationType::PERSPECTIVE: {
326             auto& perspective = effect.perspectiveOperation_;
327             perspective.distance = Dimension(CovertDimensionToPxBySize(perspective.distance, 0.0));
328             break;
329         }
330         case TransformOperationType::SCALE:
331         case TransformOperationType::SKEW:
332         case TransformOperationType::ROTATE:
333         case TransformOperationType::MATRIX:
334         case TransformOperationType::UNDEFINED:
335             break;
336         default:
337             break;
338     }
339 }
340 
UpdateTransform()341 void RenderTransform::UpdateTransform()
342 {
343     if (!needUpdateTransform_) {
344         return;
345     }
346     needUpdateTransform_ = false;
347     ParseTransformEffects(transformEffects_.GetOperations(), transform_);
348     auto context = context_.Upgrade();
349     if (!context) {
350         LOGE("Update transform failed. context is null.");
351         return;
352     }
353     if (pendingAppearing_ && hasAppearTransition_) {
354         if (transformAnimation_.GetAnimationStatus() != Animator::Status::RUNNING) {
355             for (auto& effect : transformEffectsAppearing_) {
356                 ParseDimension(effect);
357             }
358             transformAnimation_.SetTransformOperations(transformEffectsAppearing_);
359         }
360         transformAnimation_.SetAnimationStopCallback(nullptr);
361         // transform from appearing Transform to identity matrix
362         transformAnimation_.PlayTransformAnimation(transitionOption_, std::vector<TransformOperation>());
363         pendingAppearing_ = false;
364     }
365 }
366 
SetTouchable(bool enable)367 void RenderTransform::SetTouchable(bool enable)
368 {
369     enableTouchTest_ = enable;
370 }
371 
Update(const RefPtr<Component> & component)372 void RenderTransform::Update(const RefPtr<Component>& component)
373 {
374     auto transform = AceType::DynamicCast<TransformComponent>(component);
375     if (transform == nullptr) {
376         LOGE("transform component is nullptr.");
377         return;
378     }
379     ResetTransform();
380     needUpdateTransform_ = true;
381     transform_ = transform->GetTransform();
382     transformEffects_ = transform->GetTransformEffects();
383     hasAppearTransition_ = transform->HasAppearTransition();
384     if (hasAppearTransition_) {
385         transformEffectsAppearing_ = transform->GetTransformEffectsAppearing();
386     } else {
387         transformEffectsAppearing_.clear();
388     }
389     hasDisappearTransition_ = transform->HasDisappearTransition();
390     if (hasDisappearTransition_) {
391         transformEffectsDisappearing_ = transform->GetTransformEffectsDisappearing();
392     } else {
393         transformEffectsDisappearing_.clear();
394     }
395     transitionOption_ = context_.Upgrade()->GetExplicitAnimationOption();
396     originX_ = transform->GetOriginDimension().GetX();
397     originY_ = transform->GetOriginDimension().GetY();
398     SetTouchHandle(transform->GetClickSpringEffectType());
399     SetShadow(transform->GetShadow());
400     MarkNeedLayout();
401 }
402 
PerformLayout()403 void RenderTransform::PerformLayout()
404 {
405     auto child = GetFirstChild();
406     if (child == nullptr) {
407         LOGE("child component is nullptr.");
408         return;
409     }
410 
411     Size layoutSize;
412     LayoutParam innerLayout;
413     Size maxLayoutSize = GetLayoutParam().GetMaxSize();
414     if (!(maxLayoutSize < Size())) {
415         innerLayout.SetMaxSize(maxLayoutSize);
416         child->Layout(innerLayout);
417         layoutSize = child->GetLayoutSize();
418     }
419     SetLayoutSize(layoutSize);
420     needUpdateOrigin_ = true;
421     MarkNeedSyncGeometryProperties();
422 }
423 
UpdateTransformOrigin()424 void RenderTransform::UpdateTransformOrigin()
425 {
426     auto child = GetFirstChild();
427     if (child == nullptr) {
428         LOGE("child component is nullptr.");
429         return;
430     }
431 
432     if (AceType::InstanceOf<RenderBoxBase>(child) && AceType::InstanceOf<RenderBoxBase>(child->GetFirstChild())) {
433         child = child->GetFirstChild();
434     }
435 
436     Size layoutSize = GetLayoutSize();
437     const auto& renderBoxBase = AceType::DynamicCast<RenderBoxBase>(child);
438     if (renderBoxBase) {
439         auto margin = renderBoxBase->GetMargin();
440         double marginTop = margin.TopPx();
441         double marginLeft = margin.LeftPx();
442         double marginBottom = margin.BottomPx();
443         double marginRight = margin.RightPx();
444         double paintWidthSize = layoutSize.Width() - marginLeft - marginRight;
445         double paintHeightSize = layoutSize.Height() - marginTop - marginBottom;
446         origin_.SetX(CovertDimensionToPxBySize(originX_, paintWidthSize) + marginLeft);
447         origin_.SetY(CovertDimensionToPxBySize(originY_, paintHeightSize) + marginTop);
448     } else {
449         origin_.SetX(CovertDimensionToPxBySize(originX_, layoutSize.Width()));
450         origin_.SetY(CovertDimensionToPxBySize(originY_, layoutSize.Height()));
451     }
452 }
453 
CovertDimensionToPxBySize(const Dimension & dimension,double size)454 double RenderTransform::CovertDimensionToPxBySize(const Dimension& dimension, double size)
455 {
456     double result = 0.0;
457     if (dimension.Unit() == DimensionUnit::PERCENT) {
458         result = dimension.Value() * size;
459     } else if (dimension.Unit() == DimensionUnit::VP) {
460         result = NormalizeToPx(dimension);
461     } else {
462         result = dimension.Value();
463     }
464     return result;
465 };
466 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)467 void RenderTransform::OnTouchTestHit(
468     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
469 {
470     if (rawRecognizer_) {
471         rawRecognizer_->SetCoordinateOffset(coordinateOffset);
472         result.emplace_back(rawRecognizer_);
473     }
474 }
475 
SetTouchHandle(ClickSpringEffectType type)476 void RenderTransform::SetTouchHandle(ClickSpringEffectType type)
477 {
478     if (type == ClickSpringEffectType::NONE) {
479         if (rawRecognizer_) {
480             rawRecognizer_->SetOnTouchUp(nullptr);
481             rawRecognizer_->SetOnTouchDown(nullptr);
482             rawRecognizer_->SetOnTouchCancel(nullptr);
483         }
484     } else {
485         if (!rawRecognizer_) {
486             rawRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
487         }
488         if (!clickSpringEffect_) {
489             clickSpringEffect_ = AceType::MakeRefPtr<ClickSpringEffect>(GetContext());
490             clickSpringEffect_->SetRenderNode(WeakClaim(this));
491         }
492         auto touchHandle = [weak = AceType::WeakClaim(this)](
493                                const TouchEventInfo&, TouchType touchType, ClickSpringEffectType effectType) {
494             auto transform = weak.Upgrade();
495             if (transform && transform->clickSpringEffect_) {
496                 transform->clickSpringEffect_->ShowAnimation(touchType, effectType);
497             }
498         };
499         rawRecognizer_->SetOnTouchDown(std::bind(touchHandle, std::placeholders::_1, TouchType::DOWN, type));
500         rawRecognizer_->SetOnTouchUp(std::bind(touchHandle, std::placeholders::_1, TouchType::UP, type));
501         rawRecognizer_->SetOnTouchCancel(std::bind(touchHandle, std::placeholders::_1, TouchType::CANCEL, type));
502     }
503 }
504 
UpdateWithEffectMatrix(Matrix4 matrix)505 Matrix4 RenderTransform::UpdateWithEffectMatrix(Matrix4 matrix)
506 {
507     auto animationMatrix = transformAnimation_.ComputerBlendedMatrix4();
508     if (!animationMatrix.IsIdentityMatrix()) {
509         matrix = matrix * animationMatrix;
510     }
511     if (clickSpringEffect_ && !disableClickEffect_) {
512         double scale = clickSpringEffect_->GetScale();
513         if (!NearEqual(scale, 1.0)) {
514             return matrix * Matrix4::CreateScale(scale, scale, 1.0);
515         }
516     }
517 
518     return matrix;
519 }
520 
GetGlobalOffsetExternal() const521 Offset RenderTransform::GetGlobalOffsetExternal() const
522 {
523     auto renderNode = GetParent().Upgrade();
524     auto offset = renderNode ? GetPosition() + renderNode->GetGlobalOffsetExternal() : GetPosition();
525 
526     // transformPaint_[12] is translation of x,transformPaint_[13] is translation of y.
527     offset += Offset(transform_[12], transform_[13]);
528     return offset;
529 }
530 
HasDisappearingTransition(int32_t nodeId)531 bool RenderTransform::HasDisappearingTransition(int32_t nodeId)
532 {
533     return hasDisappearTransition_ || RenderNode::HasDisappearingTransition(nodeId);
534 }
535 
OnTransition(TransitionType type,int32_t id)536 void RenderTransform::OnTransition(TransitionType type, int32_t id)
537 {
538     auto context = context_.Upgrade();
539     if (!context) {
540         LOGE("OnTransition failed, context_ is null.");
541         return;
542     }
543     const auto& option = context->GetExplicitAnimationOption();
544     if (!option.IsValid()) {
545         LOGE("transition option is not valid.");
546         return;
547     }
548     if (type == TransitionType::APPEARING) {
549         pendingAppearing_ = true;
550         needUpdateTransform_ = true;
551     } else if (type == TransitionType::DISAPPEARING && hasDisappearTransition_) {
552         transformAnimation_.SetAnimationStopCallback([weak = AceType::WeakClaim(this)]() {
553             auto renderNode = weak.Upgrade();
554             if (renderNode) {
555                 renderNode->OnTransformDisappearingCallback();
556             }
557         });
558         for (auto& effect : transformEffectsDisappearing_) {
559             ParseDimension(effect);
560         }
561         transformAnimation_.PlayTransformAnimation(option, transformEffectsDisappearing_);
562     }
563 }
564 
ClearRenderObject()565 void RenderTransform::ClearRenderObject()
566 {
567     RenderNode::ClearRenderObject();
568     transform_ = Matrix4::CreateIdentity();
569     transformAnimation_.SetTransformOperations(std::vector<TransformOperation>());
570     transformEffects_.Clear();
571 
572     needUpdateTransform_ = false;
573     isFirstAnimation_ = true;
574     origin_ = Offset();
575     originX_ = Dimension();
576     originY_ = Dimension();
577     needUpdateOrigin_ = false;
578 
579     enableTouchTest_ = true;
580     transformPaint_ = Matrix4::CreateIdentity();
581     transitionOption_ = AnimationOption();
582     transformEffectsAppearing_.clear();
583     transformEffectsDisappearing_.clear();
584     hasDisappearTransition_ = false;
585     hasAppearTransition_ = false;
586     pendingAppearing_ = false;
587 }
588 
OnTransformDisappearingCallback()589 void RenderTransform::OnTransformDisappearingCallback()
590 {
591     RefPtr<RenderNode> child = AceType::Claim(this);
592     while (child && !child->IsDisappearing()) {
593         child = child->GetParent().Upgrade();
594     }
595     if (!child) {
596         return;
597     }
598     auto parent = child->GetParent().Upgrade();
599     if (parent) {
600         parent->ClearDisappearingNode(child);
601     }
602 }
603 
GetTransformByOffset(Matrix4 matrix,const Offset & offset)604 Matrix4 RenderTransform::GetTransformByOffset(Matrix4 matrix, const Offset& offset)
605 {
606     if (offset.IsZero()) {
607         return matrix;
608     }
609 
610     Matrix4 transform =
611         Matrix4::CreateTranslate(static_cast<float>(-offset.GetX()), static_cast<float>(-offset.GetY()), 0.0f);
612     transform = matrix * transform;
613     transform = Matrix4::CreateTranslate(static_cast<float>(offset.GetX()), static_cast<float>(offset.GetY()), 0.0f) *
614                 transform;
615     return transform;
616 }
617 
618 } // namespace OHOS::Ace
619