• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 自定义渲染节点 (RenderNode)
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @xiang-shouxing-->
5<!--Designer: @xiang-shouxing-->
6<!--Tester: @sally__-->
7<!--Adviser: @HelloCrease-->
8
9## 概述
10
11对于不具备自己的渲染环境的三方框架,尽管已实现前端解析、布局及事件处理等功能,但仍需依赖系统的基础渲染和动画能力。[FrameNode](./arkts-user-defined-arktsNode-frameNode.md)上的通用属性与通用事件对这类框架而言是冗余的,会导致多次不必要的操作,涵盖布局、事件处理等逻辑。
12
13自定义渲染节点 ([RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md))是更加轻量的渲染节点,仅具备与渲染相关的功能。它提供了设置基础渲染属性的能力,以及节点的动态添加、删除和自定义绘制的能力。RenderNode能够为第三方框架提供基础的渲染和动画支持。
14
15## 创建和删除节点
16
17RenderNode提供了节点创建和删除的能力。可以通过RenderNode的构造函数创建自定义的RenderNode节点。通过构造函数创建的节点对应一个实体的节点。同时,可以通过RenderNode中的[dispose](../reference/apis-arkui/js-apis-arkui-renderNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。
18
19## 操作节点树
20
21RenderNode提供了节点的增、删、查、改的能力,能够修改节点的子树结构;可以对所有RenderNode的节点的父子节点做出查询操作,并返回查询结果。
22
23> **说明:**
24>
25> - RenderNode中查询获取得到的子树结构按照开发通过RenderNode的接口传递的参数构建。
26>
27> - RenderNode如果要与系统直接结合显示,使用需要依赖FrameNode中获取的RenderNode进行挂载上树。
28
29```ts
30import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI';
31
32const TEST_TAG: string = "RenderNode";
33const renderNode = new RenderNode();
34renderNode.frame = {
35  x: 0,
36  y: 0,
37  width: 200,
38  height: 350
39};
40renderNode.backgroundColor = 0xffff0000;
41for (let i = 0; i < 5; i++) {
42  const node = new RenderNode();
43  // 设置node节点的Frame大小
44  node.frame = {
45    x: 10,
46    y: 10 + 60 * i,
47    width: 50,
48    height: 50
49  };
50  // 设置node节点的背景颜色
51  node.backgroundColor = 0xff00ff00;
52  // 将新增节点挂载在renderNode上
53  renderNode.appendChild(node);
54}
55
56class MyNodeController extends NodeController {
57  private rootNode: FrameNode | null = null;
58
59  makeNode(uiContext: UIContext): FrameNode | null {
60    this.rootNode = new FrameNode(uiContext);
61
62    const rootRenderNode = this.rootNode?.getRenderNode();
63    if (rootRenderNode) {
64      rootRenderNode.appendChild(renderNode);
65    }
66    return this.rootNode;
67  }
68}
69
70@Entry
71@Component
72struct Index {
73  private myNodeController: MyNodeController = new MyNodeController();
74
75  build() {
76    Row() {
77      NodeContainer(this.myNodeController)
78        .width(200)
79        .height(350)
80      Button('getNextSibling')
81        .onClick(() => {
82          const child = renderNode.getChild(1);
83          const nextSibling = child!.getNextSibling()
84          if (child === null || nextSibling === null) {
85            console.info(TEST_TAG + ' the child or nextChild is null');
86          } else {
87            // 获取子节点的位置信息
88            console.info(`${TEST_TAG} the position of child is x: ${child.position.x}, y: ${child.position.y}, ` +
89            `the position of nextSibling is x: ${nextSibling.position.x}, y: ${nextSibling.position.y}`);
90          }
91        })
92    }
93  }
94}
95```
96
97## 设置和获取渲染相关属性
98
99RenderNode中可以设置渲染相关的属性,包括:[backgroundColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#backgroundcolor),[clipToFrame](../reference/apis-arkui/js-apis-arkui-renderNode.md#cliptoframe),[opacity](../reference/apis-arkui/js-apis-arkui-renderNode.md#opacity),[size](../reference/apis-arkui/js-apis-arkui-renderNode.md#size),[position](../reference/apis-arkui/js-apis-arkui-renderNode.md#position),[frame](../reference/apis-arkui/js-apis-arkui-renderNode.md#frame),[pivot](../reference/apis-arkui/js-apis-arkui-renderNode.md#pivot),[scale](../reference/apis-arkui/js-apis-arkui-renderNode.md#scale),[translation](../reference/apis-arkui/js-apis-arkui-renderNode.md#translation),[rotation](../reference/apis-arkui/js-apis-arkui-renderNode.md#rotation),[transform](../reference/apis-arkui/js-apis-arkui-renderNode.md#transform),[shadowColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowcolor),[shadowOffset](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowoffset),[shadowAlpha](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowalpha),[shadowElevation](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowelevation),[shadowRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowradius),[borderStyle](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderstyle12),[borderWidth](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderwidth12),[borderColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#bordercolor12),[borderRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderradius12),[shapeMask](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapemask12),[shapeClip](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapeclip12),[markNodeGroup](../reference/apis-arkui/js-apis-arkui-renderNode.md#marknodegroup12)等。具体属性支持范围参考[RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md)接口说明。
100
101> **说明:**
102>
103> - RenderNode中查询获取得到的属性为设置的属性值。
104>
105> - 若未传入参数或者传入参数为非法值则查询获得的为默认值。
106>
107> - 不建议对BuilderNode中的RenderNode进行修改操作。BuilderNode中具体属性设置是由状态管理实现的,属性更新的时序开发者不可控,BuilderNode和FrameNode中同时设置RenderNode属性可能会导致RenderNode属性设置与预期不相符。
108
109```ts
110import { RenderNode, FrameNode, NodeController, ShapeMask, ShapeClip } from '@kit.ArkUI';
111
112const TEST_TAG: string = "RenderNode";
113const mask = new ShapeMask();
114mask.setRectShape({
115  left: 0,
116  right: 150,
117  top: 0,
118  bottom: 150
119});
120mask.fillColor = 0X55FF0000;
121mask.strokeColor = 0XFFFF0000;
122mask.strokeWidth = 24;
123
124const clip = new ShapeClip();
125clip.setCommandPath({ commands: "M100 0 L0 100 L50 200 L150 200 L200 100 Z" });
126
127const renderNode = new RenderNode();
128renderNode.backgroundColor = 0xffff0000;
129renderNode.size = { width: 100, height: 100 };
130
131class MyNodeController extends NodeController {
132  private rootNode: FrameNode | null = null;
133
134  makeNode(uiContext: UIContext): FrameNode | null {
135    this.rootNode = new FrameNode(uiContext);
136
137    const rootRenderNode = this.rootNode.getRenderNode();
138    if (rootRenderNode !== null) {
139      rootRenderNode.appendChild(renderNode);
140    }
141
142    return this.rootNode;
143  }
144}
145
146@Entry
147@Component
148struct Index {
149  private myNodeController: MyNodeController = new MyNodeController();
150
151  build() {
152    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
153      Column() {
154        NodeContainer(this.myNodeController)
155      }
156
157      Button("position")
158        .width(300)
159        .onClick(() => {
160          renderNode.position = { x: 10, y: 10 };
161          console.info(TEST_TAG + " position:" + JSON.stringify(renderNode.position));
162        })
163      Button("pivot")
164        .width(300)
165        .onClick(() => {
166          renderNode.pivot = { x: 0.5, y: 0.6 };
167          console.info(TEST_TAG + " pivot:" + JSON.stringify(renderNode.pivot));
168        })
169      Button("scale")
170        .width(300)
171        .onClick(() => {
172          renderNode.scale = { x: 0.5, y: 1 };
173          console.info(TEST_TAG + " scale:" + JSON.stringify(renderNode.scale));
174        })
175      Button("translation")
176        .width(300)
177        .onClick(() => {
178          renderNode.translation = { x: 100, y: 0 };
179          console.info(TEST_TAG + " translation:" + JSON.stringify(renderNode.translation));
180        })
181      Button("rotation")
182        .width(300)
183        .onClick(() => {
184          renderNode.rotation = { x: 45, y: 0, z: 0 };
185          console.info(TEST_TAG + " rotation:" + JSON.stringify(renderNode.rotation));
186        })
187      Button("transform")
188        .width(300)
189        .onClick(() => {
190          renderNode.transform = [
191            1, 0, 0, 0,
192            0, 2, 0, 0,
193            0, 0, 1, 0,
194            0, 0, 0, 1
195          ];
196          console.info(TEST_TAG + " transform:" + JSON.stringify(renderNode.transform));
197        })
198      Button("shadow")
199        .width(300)
200        .onClick(() => {
201          renderNode.shadowElevation = 10;
202          renderNode.shadowColor = 0XFF00FF00;
203          renderNode.shadowOffset = { x: 10, y: 10 };
204          renderNode.shadowAlpha = 0.1;
205          console.info(TEST_TAG + " shadowElevation:" + JSON.stringify(renderNode.shadowElevation));
206          console.info(TEST_TAG + " shadowColor:" + JSON.stringify(renderNode.shadowColor));
207          console.info(TEST_TAG + " shadowOffset:" + JSON.stringify(renderNode.shadowOffset));
208          console.info(TEST_TAG + " shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha));
209        })
210      Button("shadowRadius")
211        .width(300)
212        .onClick(() => {
213          renderNode.shadowOffset = { x: 10, y: 10 };
214          renderNode.shadowAlpha = 0.7
215          renderNode.shadowRadius = 30;
216          console.info(TEST_TAG + " shadowOffset:" + JSON.stringify(renderNode.shadowOffset));
217          console.info(TEST_TAG + " shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha));
218          console.info(TEST_TAG + " shadowRadius:" + JSON.stringify(renderNode.shadowRadius));
219        })
220      Button("border")
221        .width(300)
222        .onClick(() => {
223          renderNode.borderWidth = {
224            left: 8,
225            top: 8,
226            right: 8,
227            bottom: 8
228          };
229          renderNode.borderStyle = {
230            left: BorderStyle.Solid,
231            top: BorderStyle.Dotted,
232            right: BorderStyle.Dashed,
233            bottom: BorderStyle.Solid
234          }
235          renderNode.borderColor = {
236            left: 0xFF0000FF,
237            top: 0xFF0000FF,
238            right: 0xFF0000FF,
239            bottom: 0xFF0000FF
240          };
241          renderNode.borderRadius = {
242            topLeft: 32,
243            topRight: 32,
244            bottomLeft: 32,
245            bottomRight: 32
246          };
247          console.info(TEST_TAG + " borderWidth:" + JSON.stringify(renderNode.borderWidth));
248          console.info(TEST_TAG + " borderStyle:" + JSON.stringify(renderNode.borderStyle));
249          console.info(TEST_TAG + " borderColor:" + JSON.stringify(renderNode.borderColor));
250          console.info(TEST_TAG + " borderRadius:" + JSON.stringify(renderNode.borderRadius));
251        })
252      Button("shapeMask")
253        .width(300)
254        .onClick(() => {
255          renderNode.shapeMask = mask;
256          console.info(TEST_TAG + " shapeMask:" + JSON.stringify(renderNode.shapeMask));
257        })
258      Button("shapeClip")
259        .width(300)
260        .onClick(() => {
261          renderNode.shapeClip = clip;
262          console.info(TEST_TAG + " shapeClip:" + JSON.stringify(renderNode.shapeClip));
263        })
264    }
265    .padding({
266      left: 35,
267      right: 35,
268      top: 35,
269      bottom: 35
270    })
271    .width("100%")
272    .height("100%")
273  }
274}
275```
276
277## 自定义绘制
278
279通过重写RenderNode中的[draw](../reference/apis-arkui/js-apis-arkui-renderNode.md#draw)方法,可以自定义RenderNode的绘制内容,通过[invalidate](../reference/apis-arkui/js-apis-arkui-renderNode.md#invalidate)接口可以主动触发节点的重新绘制。
280
281> **说明:**
282>
283> - 同时同步触发多个invalidate仅会触发一次重新绘制。
284>
285> - 自定义绘制有两种绘制方式:通过ArkTS接口进行调用和通过Node-API进行调用。
286
287**ArkTS接口调用示例:**
288
289```ts
290import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI';
291import { drawing } from '@kit.ArkGraphics2D';
292
293class MyRenderNode extends RenderNode {
294  width: number = 200;
295
296  draw(context: DrawContext) {
297    // 获取canvas对象
298    const canvas = context.canvas;
299    // 创建笔刷
300    const brush = new drawing.Brush();
301    // 设置笔刷颜色
302    brush.setColor({
303      alpha: 255,
304      red: 255,
305      green: 0,
306      blue: 0
307    });
308    canvas.attachBrush(brush);
309    // 绘制矩阵
310    canvas.drawRect({
311      left: 0,
312      right: this.width,
313      top: 0,
314      bottom: 200
315    });
316    canvas.detachBrush();
317    console.info(`RenderNode draw width = ${this.width}`);
318  }
319}
320
321const renderNode = new MyRenderNode();
322renderNode.frame = {
323  x: 0,
324  y: 0,
325  width: 300,
326  height: 300
327};
328renderNode.backgroundColor = 0xff0000ff;
329renderNode.opacity = 0.5;
330
331class MyNodeController extends NodeController {
332  private rootNode: FrameNode | null = null;
333
334  makeNode(uiContext: UIContext): FrameNode | null {
335    this.rootNode = new FrameNode(uiContext);
336
337    const rootRenderNode = this.rootNode?.getRenderNode();
338    if (rootRenderNode !== null) {
339      rootRenderNode.frame = {
340        x: 0,
341        y: 0,
342        width: 500,
343        height: 500
344      }
345      rootRenderNode.appendChild(renderNode);
346    }
347
348    return this.rootNode;
349  }
350}
351
352@Entry
353@Component
354struct Index {
355  private myNodeController: MyNodeController = new MyNodeController();
356
357  build() {
358    Column() {
359      NodeContainer(this.myNodeController)
360        .width('100%')
361      Button('Invalidate')
362        .onClick(() => {
363          // 同步调用多次,仅触发一次重绘,draw回调中的日志仅打印一次
364          renderNode.width += 10;
365          renderNode.invalidate();
366          renderNode.invalidate();
367        })
368    }
369  }
370}
371```
372
373**Node-API调用示例:**
374
375C++侧可通过Node-API来获取Canvas,并进行后续的自定义绘制操作。
376
377```c++
378// native_bridge.cpp
379#include "napi/native_api.h"
380#include <native_drawing/drawing_canvas.h>
381#include <native_drawing/drawing_color.h>
382#include <native_drawing/drawing_path.h>
383#include <native_drawing/drawing_pen.h>
384
385static napi_value OnDraw(napi_env env, napi_callback_info info)
386{
387    size_t argc = 4;
388    napi_value args[4] = { nullptr };
389    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
390
391    int32_t id;
392    napi_get_value_int32(env, args[0], &id);
393
394    // 获取 Canvas 指针
395    void* temp = nullptr;
396    napi_unwrap(env, args[1], &temp);
397    OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas*>(temp);
398
399    // 获取 Canvas 宽度
400    int32_t width;
401    napi_get_value_int32(env, args[2], &width);
402
403    // 获取 Canvas 高度
404    int32_t height;
405    napi_get_value_int32(env, args[3], &height);
406
407    // 传入canvas、height、width等信息至绘制函数中进行自定义绘制
408    auto path = OH_Drawing_PathCreate();
409    OH_Drawing_PathMoveTo(path, width / 4, height / 4);
410    OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4);
411    OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4);
412    OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4);
413    OH_Drawing_PathLineTo(path, width / 4, height / 4);
414    OH_Drawing_PathClose(path);
415
416    auto pen = OH_Drawing_PenCreate();
417    OH_Drawing_PenSetWidth(pen, 10);
418    OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00));
419    OH_Drawing_CanvasAttachPen(canvas, pen);
420
421    OH_Drawing_CanvasDrawPath(canvas, path);
422
423    return nullptr;
424}
425
426EXTERN_C_START
427static napi_value Init(napi_env env, napi_value exports)
428{
429    napi_property_descriptor desc[] = {
430        { "nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr }
431    };
432    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
433    return exports;
434}
435EXTERN_C_END
436
437static napi_module demoModule = {
438    .nm_version =1,
439    .nm_flags = 0,
440    .nm_filename = nullptr,
441    .nm_register_func = Init,
442    .nm_modname = "entry",
443    .nm_priv = ((void*)0),
444    .reserved = { 0 },
445};
446
447extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
448{
449    napi_module_register(&demoModule);
450}
451```
452
453修改工程中的`src/main/cpp/CMakeLists.txt`文件,添加如下内容:
454```cmake
455# the minimum version of CMake.
456cmake_minimum_required(VERSION 3.4.1)
457project(NapiTest)
458
459set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
460
461include_directories(${NATIVERENDER_ROOT_PATH}
462                    ${NATIVERENDER_ROOT_PATH}/include)
463
464add_library(entry SHARED native_bridge.cpp)
465target_link_libraries(entry PUBLIC libace_napi.z.so)
466target_link_libraries(entry PUBLIC libace_ndk.z.so)
467target_link_libraries(entry PUBLIC libnative_drawing.so)
468```
469
470同时在工程中的`src/main/cpp/types/libentry/index.d.ts`文件中,添加自定义绘制函数在ArkTS侧的定义,如:
471```ts
472import { DrawContext } from '@kit.ArkUI'
473
474export const nativeOnDraw: (id: number, context: DrawContext, width: number, height: number) => number;
475```
476
477ArkTS侧代码:
478
479```ts
480// Index.ets
481import bridge from "libentry.so" // 该 so 由 Node-API 编写并生成
482import { DrawContext, FrameNode, NodeController, RenderNode } from '@kit.ArkUI'
483
484class MyRenderNode extends RenderNode {
485  uiContext: UIContext;
486
487  constructor(uiContext: UIContext) {
488    super();
489    this.uiContext = uiContext;
490  }
491
492  draw(context: DrawContext) {
493    // 需要将 context 中的宽度和高度从vp转换为px
494    bridge.nativeOnDraw(0, context, this.uiContext.vp2px(context.size.height), this.uiContext.vp2px(context.size.width));
495  }
496}
497
498class MyNodeController extends NodeController {
499  private rootNode: FrameNode | null = null;
500
501  makeNode(uiContext: UIContext): FrameNode | null {
502    this.rootNode = new FrameNode(uiContext);
503
504    const rootRenderNode = this.rootNode.getRenderNode();
505    if (rootRenderNode !== null) {
506      const renderNode = new MyRenderNode(uiContext);
507      renderNode.size = { width: 100, height: 100 }
508      rootRenderNode.appendChild(renderNode);
509    }
510    return this.rootNode;
511  }
512}
513
514@Entry
515@Component
516struct Index {
517  private myNodeController: MyNodeController = new MyNodeController();
518
519  build() {
520    Row() {
521      NodeContainer(this.myNodeController)
522    }
523  }
524}
525```
526
527## 设置标签
528
529开发者可利用[label](../reference/apis-arkui/js-apis-arkui-renderNode.md#label12)接口向RenderNode设置标签信息,这有助于在节点Inspector中更清晰地区分各节点。
530
531```ts
532import {  RenderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
533
534class MyNodeController extends NodeController {
535  private rootNode: FrameNode | null = null;
536
537  makeNode(uiContext: UIContext): FrameNode | null {
538    this.rootNode = new FrameNode(uiContext);
539    const renderNode: RenderNode | null = this.rootNode.getRenderNode();
540    if (renderNode !== null) {
541      const renderChildNode: RenderNode = new RenderNode();
542      renderChildNode.frame = { x: 0, y: 0, width: 100, height: 100 };
543      renderChildNode.backgroundColor = 0xffff0000;
544      renderChildNode.label = 'customRenderChildNode';
545      console.info('label:', renderChildNode.label);
546      renderNode.appendChild(renderChildNode);
547    }
548
549    return this.rootNode;
550  }
551}
552
553@Entry
554@Component
555struct Index {
556  private myNodeController: MyNodeController = new MyNodeController();
557
558  build() {
559    Column() {
560      NodeContainer(this.myNodeController)
561        .width(300)
562        .height(700)
563        .backgroundColor(Color.Gray)
564    }
565  }
566}
567```
568
569## 查询当前RenderNode是否解除引用
570
571前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。
572
573从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-renderNode.md#isdisposed20)接口查询当前RenderNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。
574
575```ts
576import { NodeController, FrameNode, RenderNode } from '@kit.ArkUI';
577
578class MyNodeController extends NodeController {
579  private rootNode: FrameNode | null = null;
580  private renderNode: RenderNode | null = null;
581
582  makeNode(uiContext: UIContext): FrameNode | null {
583    this.rootNode = new FrameNode(uiContext);
584    this.renderNode = new RenderNode();
585    this.renderNode.size = { width: 300, height: 300 };
586    this.renderNode.backgroundColor = 0xffd5d5d5;
587
588    // 挂载RenderNode
589    this.rootNode.getRenderNode()?.appendChild(this.renderNode);
590    return this.rootNode;
591  }
592
593  disposeRenderNode() {
594    // 解除RenderNode与后端实体节点的引用关系
595    this.renderNode?.dispose();
596  }
597
598  isDisposed() : string {
599    if (this.renderNode !== null) {
600      // 查询RenderNode是否解除引用
601      if (this.renderNode.isDisposed()) {
602        return 'renderNode isDisposed is true';
603      }
604      else {
605        return 'renderNode isDisposed is false';
606      }
607    }
608    return 'renderNode is null';
609  }
610}
611
612@Entry
613@Component
614struct Index {
615  @State text: string = ''
616  private myNodeController: MyNodeController = new MyNodeController();
617
618  build() {
619    Column({ space: 4 }) {
620      NodeContainer(this.myNodeController)
621      Button('RenderNode dispose')
622        .onClick(() => {
623          this.myNodeController.disposeRenderNode();
624          this.text = '';
625        })
626        .width(200)
627        .height(50)
628      Button('RenderNode isDisposed')
629        .onClick(() => {
630          this.text = this.myNodeController.isDisposed();
631        })
632        .width(200)
633        .height(50)
634      Text(this.text)
635        .fontSize(25)
636    }
637    .width('100%')
638    .height('100%')
639  }
640}
641```