1/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {Timestamp} from 'common/time';
18import {TimeDuration} from 'common/time_duration';
19import {RawDataUtils} from 'parsers/raw_data_utils';
20import {TransformUtils} from 'parsers/surface_flinger/transform_utils';
21import {PropertyTreeNode} from './property_tree_node';
22
23const EMPTY_OBJ_STRING = '{empty}';
24const EMPTY_ARRAY_STRING = '[empty]';
25
26function formatNumber(value: number): string {
27  if (!Number.isInteger(value)) {
28    return value.toFixed(3).toString();
29  }
30  return value.toString();
31}
32
33interface PropertyFormatter {
34  format(node: PropertyTreeNode): string;
35}
36
37class DefaultPropertyFormatter implements PropertyFormatter {
38  format(node: PropertyTreeNode): string {
39    const value = node.getValue();
40    if (Array.isArray(value) && value.length === 0) {
41      return EMPTY_ARRAY_STRING;
42    }
43
44    if (typeof value === 'number') {
45      return formatNumber(value);
46    }
47
48    if (value?.toString) return value.toString();
49
50    return `${value}`;
51  }
52}
53const DEFAULT_PROPERTY_FORMATTER = new DefaultPropertyFormatter();
54
55class ColorFormatter implements PropertyFormatter {
56  format(node: PropertyTreeNode): string {
57    const rNode = node.getChildByName('r');
58    const gNode = node.getChildByName('g');
59    const bNode = node.getChildByName('b');
60    const alphaNode = node.getChildByName('a');
61
62    const r = formatNumber(rNode?.getValue() ?? 0);
63    const g = formatNumber(gNode?.getValue() ?? 0);
64    const b = formatNumber(bNode?.getValue() ?? 0);
65    if (rNode && gNode && bNode && !alphaNode) {
66      return `(${r}, ${g}, ${b})`;
67    }
68
69    const alpha = formatNumber(alphaNode?.getValue() ?? 0);
70    if (RawDataUtils.isEmptyObj(node)) {
71      return `${EMPTY_OBJ_STRING}, alpha: ${alpha}`;
72    }
73    return `(${r}, ${g}, ${b}, ${alpha})`;
74  }
75}
76const COLOR_FORMATTER = new ColorFormatter();
77
78class RectFormatter implements PropertyFormatter {
79  format(node: PropertyTreeNode): string {
80    if (!RawDataUtils.isRect(node) || RawDataUtils.isEmptyObj(node)) {
81      return EMPTY_OBJ_STRING;
82    }
83    const left = formatNumber(node.getChildByName('left')?.getValue() ?? 0);
84    const top = formatNumber(node.getChildByName('top')?.getValue() ?? 0);
85    const right = formatNumber(node.getChildByName('right')?.getValue() ?? 0);
86    const bottom = formatNumber(node.getChildByName('bottom')?.getValue() ?? 0);
87
88    return `(${left}, ${top}) - (${right}, ${bottom})`;
89  }
90}
91const RECT_FORMATTER = new RectFormatter();
92
93class BufferFormatter implements PropertyFormatter {
94  format(node: PropertyTreeNode): string {
95    return `w: ${node.getChildByName('width')?.getValue() ?? 0}, h: ${
96      node.getChildByName('height')?.getValue() ?? 0
97    }, stride: ${node.getChildByName('stride')?.getValue()}, format: ${node
98      .getChildByName('format')
99      ?.getValue()}`;
100  }
101}
102const BUFFER_FORMATTER = new BufferFormatter();
103
104class LayerIdFormatter implements PropertyFormatter {
105  format(node: PropertyTreeNode): string {
106    const value = node.getValue();
107    return value === -1 || value === 0 ? 'none' : `${value}`;
108  }
109}
110const LAYER_ID_FORMATTER = new LayerIdFormatter();
111
112class MatrixFormatter implements PropertyFormatter {
113  format(node: PropertyTreeNode): string {
114    const dsdx = formatNumber(node.getChildByName('dsdx')?.getValue() ?? 0);
115    const dtdx = formatNumber(node.getChildByName('dtdx')?.getValue() ?? 0);
116    const dsdy = formatNumber(node.getChildByName('dsdy')?.getValue() ?? 0);
117    const dtdy = formatNumber(node.getChildByName('dtdy')?.getValue() ?? 0);
118    const tx = node.getChildByName('tx');
119    const ty = node.getChildByName('ty');
120    if (
121      dsdx === '0' &&
122      dtdx === '0' &&
123      dsdy === '0' &&
124      dtdy === '0' &&
125      !tx &&
126      !ty
127    ) {
128      return 'null';
129    }
130    const matrix22 = `dsdx: ${dsdx}, dtdx: ${dtdx}, dsdy: ${dsdy}, dtdy: ${dtdy}`;
131    if (!tx && !ty) {
132      return matrix22;
133    }
134    return (
135      matrix22 +
136      `, tx: ${formatNumber(tx?.getValue() ?? 0)}, ty: ${formatNumber(
137        ty?.getValue() ?? 0,
138      )}`
139    );
140  }
141}
142const MATRIX_FORMATTER = new MatrixFormatter();
143
144class TransformFormatter implements PropertyFormatter {
145  format(node: PropertyTreeNode): string {
146    const type = node.getChildByName('type');
147    return type !== undefined
148      ? TransformUtils.getTypeFlags(type.getValue() ?? 0)
149      : 'null';
150  }
151}
152const TRANSFORM_FORMATTER = new TransformFormatter();
153
154class SizeFormatter implements PropertyFormatter {
155  format(node: PropertyTreeNode): string {
156    return `${node.getChildByName('w')?.getValue() ?? 0} x ${
157      node.getChildByName('h')?.getValue() ?? 0
158    }`;
159  }
160}
161const SIZE_FORMATTER = new SizeFormatter();
162
163class PositionFormatter implements PropertyFormatter {
164  format(node: PropertyTreeNode): string {
165    const x = formatNumber(node.getChildByName('x')?.getValue() ?? 0);
166    const y = formatNumber(node.getChildByName('y')?.getValue() ?? 0);
167    return `x: ${x}, y: ${y}`;
168  }
169}
170const POSITION_FORMATTER = new PositionFormatter();
171
172class RegionFormatter implements PropertyFormatter {
173  format(node: PropertyTreeNode): string {
174    let res = 'SkRegion(';
175    node
176      .getChildByName('rect')
177      ?.getAllChildren()
178      .forEach((rectNode: PropertyTreeNode) => {
179        res += `(${rectNode.getChildByName('left')?.getValue() ?? 0}, ${
180          rectNode.getChildByName('top')?.getValue() ?? 0
181        }, ${rectNode.getChildByName('right')?.getValue() ?? 0}, ${
182          rectNode.getChildByName('bottom')?.getValue() ?? 0
183        })`;
184      });
185    return res + ')';
186  }
187}
188const REGION_FORMATTER = new RegionFormatter();
189
190class EnumFormatter implements PropertyFormatter {
191  constructor(private readonly valuesById: {[key: number]: string}) {}
192
193  format(node: PropertyTreeNode): string {
194    const value = node.getValue();
195    if (typeof value === 'number' && this.valuesById[value]) {
196      return this.valuesById[value];
197    }
198    if (typeof value === 'bigint' && this.valuesById[Number(value)]) {
199      return this.valuesById[Number(value)];
200    }
201    return `${value}`;
202  }
203}
204
205class FixedStringFormatter implements PropertyFormatter {
206  constructor(private readonly fixedStringValue: string) {}
207
208  format(node: PropertyTreeNode): string {
209    return this.fixedStringValue;
210  }
211}
212
213class TimestampNodeFormatter implements PropertyFormatter {
214  format(node: PropertyTreeNode): string {
215    const timestamp = node.getValue();
216    if (timestamp instanceof Timestamp || timestamp instanceof TimeDuration) {
217      return timestamp.format();
218    }
219    return 'null';
220  }
221}
222const TIMESTAMP_NODE_FORMATTER = new TimestampNodeFormatter();
223
224export {
225  EMPTY_OBJ_STRING,
226  EMPTY_ARRAY_STRING,
227  PropertyFormatter,
228  DEFAULT_PROPERTY_FORMATTER,
229  COLOR_FORMATTER,
230  RECT_FORMATTER,
231  BUFFER_FORMATTER,
232  LAYER_ID_FORMATTER,
233  TRANSFORM_FORMATTER,
234  SIZE_FORMATTER,
235  POSITION_FORMATTER,
236  REGION_FORMATTER,
237  EnumFormatter,
238  FixedStringFormatter,
239  TIMESTAMP_NODE_FORMATTER,
240  MATRIX_FORMATTER,
241};
242