1 /*
2  * Copyright (C) 2023 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 package com.android.internal.widget.remotecompose.core;
17 
18 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
19 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
20 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
21 import com.android.internal.widget.remotecompose.core.operations.ClipRect;
22 import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
23 import com.android.internal.widget.remotecompose.core.operations.DrawArc;
24 import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
25 import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
26 import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
27 import com.android.internal.widget.remotecompose.core.operations.DrawLine;
28 import com.android.internal.widget.remotecompose.core.operations.DrawOval;
29 import com.android.internal.widget.remotecompose.core.operations.DrawPath;
30 import com.android.internal.widget.remotecompose.core.operations.DrawRect;
31 import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
32 import com.android.internal.widget.remotecompose.core.operations.DrawText;
33 import com.android.internal.widget.remotecompose.core.operations.DrawTextAnchored;
34 import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
35 import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
36 import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
37 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
38 import com.android.internal.widget.remotecompose.core.operations.Header;
39 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
40 import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
41 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
42 import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
43 import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
44 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
45 import com.android.internal.widget.remotecompose.core.operations.PaintData;
46 import com.android.internal.widget.remotecompose.core.operations.PathData;
47 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
48 import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
49 import com.android.internal.widget.remotecompose.core.operations.TextData;
50 import com.android.internal.widget.remotecompose.core.operations.TextFromFloat;
51 import com.android.internal.widget.remotecompose.core.operations.TextMerge;
52 import com.android.internal.widget.remotecompose.core.operations.Theme;
53 import com.android.internal.widget.remotecompose.core.operations.Utils;
54 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
55 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
56 
57 import java.io.File;
58 import java.io.FileInputStream;
59 import java.io.FileOutputStream;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 
65 /**
66  * Provides an abstract buffer to encode/decode RemoteCompose operations
67  */
68 public class RemoteComposeBuffer {
69     public static final int EASING_CUBIC_STANDARD = FloatAnimation.CUBIC_STANDARD;
70     public static final int EASING_CUBIC_ACCELERATE = FloatAnimation.CUBIC_ACCELERATE;
71     public static final int EASING_CUBIC_DECELERATE = FloatAnimation.CUBIC_DECELERATE;
72     public static final int EASING_CUBIC_LINEAR = FloatAnimation.CUBIC_LINEAR;
73     public static final int EASING_CUBIC_ANTICIPATE = FloatAnimation.CUBIC_ANTICIPATE;
74     public static final int EASING_CUBIC_OVERSHOOT = FloatAnimation.CUBIC_OVERSHOOT;
75     public static final int EASING_CUBIC_CUSTOM = FloatAnimation.CUBIC_CUSTOM;
76     public static final int EASING_SPLINE_CUSTOM = FloatAnimation.SPLINE_CUSTOM;
77     public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE;
78     public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC;
79     WireBuffer mBuffer = new WireBuffer();
80     Platform mPlatform = null;
81     RemoteComposeState mRemoteComposeState;
82     private static final boolean DEBUG = false;
83 
84     /**
85      * Provides an abstract buffer to encode/decode RemoteCompose operations
86      *
87      * @param remoteComposeState the state used while encoding on the buffer
88      */
RemoteComposeBuffer(RemoteComposeState remoteComposeState)89     public RemoteComposeBuffer(RemoteComposeState remoteComposeState) {
90         this.mRemoteComposeState = remoteComposeState;
91     }
92 
93     /**
94      * Reset the internal buffers
95      *
96      * @param expectedSize provided hint for the main buffer size
97      */
reset(int expectedSize)98     public void reset(int expectedSize) {
99         mBuffer.reset(expectedSize);
100         mRemoteComposeState.reset();
101     }
102 
getPlatform()103     public Platform getPlatform() {
104         return mPlatform;
105     }
106 
setPlatform(Platform platform)107     public void setPlatform(Platform platform) {
108         this.mPlatform = platform;
109     }
110 
getBuffer()111     public WireBuffer getBuffer() {
112         return mBuffer;
113     }
114 
setBuffer(WireBuffer buffer)115     public void setBuffer(WireBuffer buffer) {
116         this.mBuffer = buffer;
117     }
118 
119     ///////////////////////////////////////////////////////////////////////////////////////////////
120     // Supported operations on the buffer
121     ///////////////////////////////////////////////////////////////////////////////////////////////
122 
123     /**
124      * Insert a header
125      *
126      * @param width              the width of the document in pixels
127      * @param height             the height of the document in pixels
128      * @param contentDescription content description of the document
129      * @param capabilities       bitmask indicating needed capabilities (unused for now)
130      */
header(int width, int height, String contentDescription, long capabilities)131     public void header(int width, int height, String contentDescription, long capabilities) {
132         Header.COMPANION.apply(mBuffer, width, height, capabilities);
133         int contentDescriptionId = 0;
134         if (contentDescription != null) {
135             contentDescriptionId = addText(contentDescription);
136             RootContentDescription.COMPANION.apply(mBuffer, contentDescriptionId);
137         }
138     }
139 
140     /**
141      * Insert a header
142      *
143      * @param width              the width of the document in pixels
144      * @param height             the height of the document in pixels
145      * @param contentDescription content description of the document
146      */
header(int width, int height, String contentDescription)147     public void header(int width, int height, String contentDescription) {
148         header(width, height, contentDescription, 0);
149     }
150 
151     /**
152      * Insert a bitmap
153      *
154      * @param image       an opaque image that we'll add to the buffer
155      * @param imageWidth  the width of the image
156      * @param imageHeight the height of the image
157      * @param srcLeft     left coordinate of the source area
158      * @param srcTop      top coordinate of the source area
159      * @param srcRight    right coordinate of the source area
160      * @param srcBottom   bottom coordinate of the source area
161      * @param dstLeft     left coordinate of the destination area
162      * @param dstTop      top coordinate of the destination area
163      * @param dstRight    right coordinate of the destination area
164      * @param dstBottom   bottom coordinate of the destination area
165      */
drawBitmap(Object image, int imageWidth, int imageHeight, int srcLeft, int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight, int dstBottom, String contentDescription)166     public void drawBitmap(Object image,
167                            int imageWidth, int imageHeight,
168                            int srcLeft, int srcTop, int srcRight, int srcBottom,
169                            int dstLeft, int dstTop, int dstRight, int dstBottom,
170                            String contentDescription) {
171         int imageId = mRemoteComposeState.dataGetId(image);
172         if (imageId == -1) {
173             imageId = mRemoteComposeState.cache(image);
174             byte[] data = mPlatform.imageToByteArray(image);
175             BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
176         }
177         int contentDescriptionId = 0;
178         if (contentDescription != null) {
179             contentDescriptionId = addText(contentDescription);
180         }
181         DrawBitmapInt.COMPANION.apply(
182                 mBuffer, imageId, srcLeft, srcTop, srcRight, srcBottom,
183                 dstLeft, dstTop, dstRight, dstBottom, contentDescriptionId
184         );
185     }
186 
187     /**
188      * Adds a text string data to the stream and returns its id
189      * Will be used to insert string with bitmaps etc.
190      *
191      * @param text the string to inject in the buffer
192      */
addText(String text)193     public int addText(String text) {
194         int id = mRemoteComposeState.dataGetId(text);
195         if (id == -1) {
196             id = mRemoteComposeState.cache(text);
197             TextData.COMPANION.apply(mBuffer, id, text);
198         }
199         return id;
200     }
201 
202     /**
203      * Add a click area to the document
204      *
205      * @param id                 the id of the click area, reported in the click listener callback
206      * @param contentDescription the content description of that click area (accessibility)
207      * @param left               left coordinate of the area bounds
208      * @param top                top coordinate of the area bounds
209      * @param right              right coordinate of the area bounds
210      * @param bottom             bottom coordinate of the area bounds
211      * @param metadata           associated metadata, user-provided
212      */
addClickArea( int id, String contentDescription, float left, float top, float right, float bottom, String metadata )213     public void addClickArea(
214             int id,
215             String contentDescription,
216             float left,
217             float top,
218             float right,
219             float bottom,
220             String metadata
221     ) {
222         int contentDescriptionId = 0;
223         if (contentDescription != null) {
224             contentDescriptionId = addText(contentDescription);
225         }
226         int metadataId = 0;
227         if (metadata != null) {
228             metadataId = addText(metadata);
229         }
230         ClickArea.COMPANION.apply(mBuffer, id, contentDescriptionId,
231                 left, top, right, bottom, metadataId);
232     }
233 
234     /**
235      * Sets the way the player handles the content
236      *
237      * @param scroll    set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
238      * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
239      * @param sizing    set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
240      * @param mode      set the mode of sizing, either LAYOUT modes or SCALE modes
241      *                  the LAYOUT modes are:
242      *                  - LAYOUT_MATCH_PARENT
243      *                  - LAYOUT_WRAP_CONTENT
244      *                  or adding an horizontal mode and a vertical mode:
245      *                  - LAYOUT_HORIZONTAL_MATCH_PARENT
246      *                  - LAYOUT_HORIZONTAL_WRAP_CONTENT
247      *                  - LAYOUT_HORIZONTAL_FIXED
248      *                  - LAYOUT_VERTICAL_MATCH_PARENT
249      *                  - LAYOUT_VERTICAL_WRAP_CONTENT
250      *                  - LAYOUT_VERTICAL_FIXED
251      *                  The LAYOUT_*_FIXED modes will use the intrinsic document size
252      */
setRootContentBehavior(int scroll, int alignment, int sizing, int mode)253     public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
254         RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode);
255     }
256 
257     /**
258      * add Drawing the specified arc, which will be scaled to fit inside the specified oval.
259      * <br>
260      * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
261      * 360.
262      * <br>
263      * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
264      * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
265      * negative, the sweep angle is treated as sweep angle modulo 360
266      * <br>
267      * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
268      * degrees (3 o'clock on a watch.)
269      * <br>
270      *
271      * @param left       left coordinate of oval used to define the shape and size of the arc
272      * @param top        top coordinate of oval used to define the shape and size of the arc
273      * @param right      right coordinate of oval used to define the shape and size of the arc
274      * @param bottom     bottom coordinate of oval used to define the shape and size of the arc
275      * @param startAngle Starting angle (in degrees) where the arc begins
276      * @param sweepAngle Sweep angle (in degrees) measured clockwise
277      */
addDrawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)278     public void addDrawArc(float left,
279                            float top,
280                            float right,
281                            float bottom,
282                            float startAngle,
283                            float sweepAngle) {
284         DrawArc.COMPANION.apply(mBuffer, left, top, right, bottom, startAngle, sweepAngle);
285     }
286 
287     /**
288      * @param image              The bitmap to be drawn
289      * @param left               left coordinate of rectangle that the bitmap will be to fit into
290      * @param top                top coordinate of rectangle that the bitmap will be to fit into
291      * @param right              right coordinate of rectangle that the bitmap will be to fit into
292      * @param bottom             bottom coordinate of rectangle that the bitmap will be to fit into
293      * @param contentDescription content description of the image
294      */
addDrawBitmap(Object image, float left, float top, float right, float bottom, String contentDescription)295     public void addDrawBitmap(Object image,
296                               float left,
297                               float top,
298                               float right,
299                               float bottom,
300                               String contentDescription) {
301         int imageId = mRemoteComposeState.dataGetId(image);
302         if (imageId == -1) {
303             imageId = mRemoteComposeState.cache(image);
304             byte[] data = mPlatform.imageToByteArray(image);
305             int imageWidth = mPlatform.getImageWidth(image);
306             int imageHeight = mPlatform.getImageHeight(image);
307 
308             BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
309         }
310         int contentDescriptionId = 0;
311         if (contentDescription != null) {
312             contentDescriptionId = addText(contentDescription);
313         }
314         DrawBitmap.COMPANION.apply(
315                 mBuffer, imageId, left, top, right, bottom, contentDescriptionId
316         );
317     }
318 
319     /**
320      * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be
321      * drawn.
322      *
323      * @param centerX The x-coordinate of the center of the circle to be drawn
324      * @param centerY The y-coordinate of the center of the circle to be drawn
325      * @param radius  The radius of the circle to be drawn
326      */
addDrawCircle(float centerX, float centerY, float radius)327     public void addDrawCircle(float centerX, float centerY, float radius) {
328         DrawCircle.COMPANION.apply(mBuffer, centerX, centerY, radius);
329     }
330 
331     /**
332      * Draw a line segment with the specified start and stop x,y coordinates, using the specified
333      * paint.
334      *
335      * @param x1 The x-coordinate of the start point of the line
336      * @param y1 The y-coordinate of the start point of the line
337      * @param x2 The x-coordinate of the end point of the line
338      * @param y2 The y-coordinate of the end point of the line
339      */
addDrawLine(float x1, float y1, float x2, float y2)340     public void addDrawLine(float x1, float y1, float x2, float y2) {
341         DrawLine.COMPANION.apply(mBuffer, x1, y1, x2, y2);
342     }
343 
344     /**
345      * Draw the specified oval using the specified paint.
346      *
347      * @param left   left coordinate of oval
348      * @param top    top coordinate of oval
349      * @param right  right coordinate of oval
350      * @param bottom bottom coordinate of oval
351      */
addDrawOval(float left, float top, float right, float bottom)352     public void addDrawOval(float left, float top, float right, float bottom) {
353         DrawOval.COMPANION.apply(mBuffer, left, top, right, bottom);
354     }
355 
356     /**
357      * Draw the specified path
358      * <p>
359      * Note: path objects are not immutable
360      * modifying them and calling this will not change the drawing
361      *
362      * @param path The path to be drawn
363      */
addDrawPath(Object path)364     public void addDrawPath(Object path) {
365         int id = mRemoteComposeState.dataGetId(path);
366         if (id == -1) { // never been seen before
367             id = addPathData(path);
368         }
369         addDrawPath(id);
370     }
371 
372     /**
373      * Draw the specified path
374      *
375      * @param pathId
376      */
addDrawPath(int pathId)377     public void addDrawPath(int pathId) {
378         DrawPath.COMPANION.apply(mBuffer, pathId);
379     }
380 
381     /**
382      * Draw the specified Rect
383      *
384      * @param left   left coordinate of rectangle to be drawn
385      * @param top    top coordinate of rectangle to be drawn
386      * @param right  right coordinate of rectangle to be drawn
387      * @param bottom bottom coordinate of rectangle to be drawn
388      */
addDrawRect(float left, float top, float right, float bottom)389     public void addDrawRect(float left, float top, float right, float bottom) {
390         DrawRect.COMPANION.apply(mBuffer, left, top, right, bottom);
391     }
392 
393     /**
394      * Draw the specified round-rect
395      *
396      * @param left    left coordinate of rectangle to be drawn
397      * @param top     left coordinate of rectangle to be drawn
398      * @param right   left coordinate of rectangle to be drawn
399      * @param bottom  left coordinate of rectangle to be drawn
400      * @param radiusX The x-radius of the oval used to round the corners
401      * @param radiusY The y-radius of the oval used to round the corners
402      */
addDrawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY)403     public void addDrawRoundRect(float left, float top, float right, float bottom,
404                                  float radiusX, float radiusY) {
405         DrawRoundRect.COMPANION.apply(mBuffer, left, top, right, bottom, radiusX, radiusY);
406     }
407 
408     /**
409      * Draw the text, with origin at (x,y) along the specified path.
410      *
411      * @param text    The text to be drawn
412      * @param path    The path the text should follow for its baseline
413      * @param hOffset The distance along the path to add to the text's starting position
414      * @param vOffset The distance above(-) or below(+) the path to position the text
415      */
addDrawTextOnPath(String text, Object path, float hOffset, float vOffset)416     public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) {
417         int pathId = mRemoteComposeState.dataGetId(path);
418         if (pathId == -1) { // never been seen before
419             pathId = addPathData(path);
420         }
421         int textId = addText(text);
422         DrawTextOnPath.COMPANION.apply(mBuffer, textId, pathId, hOffset, vOffset);
423     }
424 
425     /**
426      * Draw the text, with origin at (x,y). The origin is interpreted
427      * based on the Align setting in the paint.
428      *
429      * @param text         The text to be drawn
430      * @param start        The index of the first character in text to draw
431      * @param end          (end - 1) is the index of the last character in text to draw
432      * @param contextStart
433      * @param contextEnd
434      * @param x            The x-coordinate of the origin of the text being drawn
435      * @param y            The y-coordinate of the baseline of the text being drawn
436      * @param rtl          Draw RTTL
437      */
addDrawTextRun(String text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean rtl)438     public void addDrawTextRun(String text,
439                                int start,
440                                int end,
441                                int contextStart,
442                                int contextEnd,
443                                float x,
444                                float y,
445                                boolean rtl) {
446         int textId = addText(text);
447         DrawText.COMPANION.apply(
448                 mBuffer, textId, start, end,
449                 contextStart, contextEnd, x, y, rtl);
450     }
451 
452     /**
453      * Draw the text, with origin at (x,y). The origin is interpreted
454      * based on the Align setting in the paint.
455      *
456      * @param textId       The text to be drawn
457      * @param start        The index of the first character in text to draw
458      * @param end          (end - 1) is the index of the last character in text to draw
459      * @param contextStart
460      * @param contextEnd
461      * @param x            The x-coordinate of the origin of the text being drawn
462      * @param y            The y-coordinate of the baseline of the text being drawn
463      * @param rtl          Draw RTTL
464      */
addDrawTextRun(int textId, int start, int end, int contextStart, int contextEnd, float x, float y, boolean rtl)465     public void addDrawTextRun(int textId,
466                                int start,
467                                int end,
468                                int contextStart,
469                                int contextEnd,
470                                float x,
471                                float y,
472                                boolean rtl) {
473         DrawText.COMPANION.apply(
474                 mBuffer, textId, start, end,
475                 contextStart, contextEnd, x, y, rtl);
476     }
477 
478     /**
479      * Draw a text on canvas at relative to position (x, y),
480      * offset panX and panY.
481      * <br>
482      * The panning factors (panX, panY)  mapped to the
483      * resulting bounding box of the text, in such a way that a
484      * panning factor of (0.0, 0.0) would center the text at (x, y)
485      * <ul>
486      * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li>
487      * <li>Panning of  1.0,  1.0 - the text is below and to the left</li>
488      * <li>Panning of  1.0,  0.0 - the test is centered & to the right of x,y</li>
489      * </ul>
490      * Setting panY to NaN results in y being the baseline of the text.
491      *
492      * @param text  text to draw
493      * @param x     Coordinate of the Anchor
494      * @param y     Coordinate of the Anchor
495      * @param panX  justifies text -1.0=right, 0.0=center, 1.0=left
496      * @param panY  position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
497      * @param flags 1 = RTL
498      */
drawTextAnchored(String text, float x, float y, float panX, float panY, int flags)499     public void drawTextAnchored(String text,
500                                  float x,
501                                  float y,
502                                  float panX,
503                                  float panY,
504                                  int flags) {
505         int textId = addText(text);
506         DrawTextAnchored.COMPANION.apply(
507                 mBuffer, textId,
508                 x, y,
509                 panX, panY,
510                 flags);
511     }
512 
513     /**
514      * Add a text and id so that it can be used
515      *
516      * @param text
517      * @return
518      */
createTextId(String text)519     public int createTextId(String text) {
520         return addText(text);
521     }
522 
523     /**
524      * Merge two text (from id's) output one id
525      * @param id1 left id
526      * @param id2 right id
527      * @return new id that merges the two text
528      */
textMerge(int id1, int id2)529     public int textMerge(int id1, int id2) {
530         int textId = addText(id1 + "+" + id2);
531         TextMerge.COMPANION.apply(mBuffer, textId, id1, id2);
532         return textId;
533     }
534 
535     public static final int PAD_AFTER_SPACE = TextFromFloat.PAD_AFTER_SPACE;
536     public static final int PAD_AFTER_NONE = TextFromFloat.PAD_AFTER_NONE;
537     public static final int PAD_AFTER_ZERO = TextFromFloat.PAD_AFTER_ZERO;
538     public static final int PAD_PRE_SPACE = TextFromFloat.PAD_PRE_SPACE;
539     public static final int PAD_PRE_NONE = TextFromFloat.PAD_PRE_NONE;
540     public static final int PAD_PRE_ZERO = TextFromFloat.PAD_PRE_ZERO;
541 
542     /**
543      * Create a TextFromFloat command which creates text from a Float.
544      *
545      * @param value        The value to convert
546      * @param digitsBefore the digits before the decimal point
547      * @param digitsAfter  the digits after the decimal point
548      * @param flags        configure the behaviour using PAD_PRE_* and PAD_AFTER* flags
549      * @return id of the string that can be passed to drawTextAnchored
550      */
createTextFromFloat(float value, short digitsBefore, short digitsAfter, int flags)551     public int createTextFromFloat(float value, short digitsBefore,
552                                    short digitsAfter, int flags) {
553         String placeHolder = Utils.floatToString(value)
554                 + "(" + digitsBefore + "," + digitsAfter + "," + flags + ")";
555         int id = mRemoteComposeState.dataGetId(placeHolder);
556         if (id == -1) {
557             id = mRemoteComposeState.cache(placeHolder);
558             //   TextData.COMPANION.apply(mBuffer, id, text);
559         }
560         TextFromFloat.COMPANION.apply(mBuffer, id, value, digitsBefore,
561                 digitsAfter, flags);
562         return id;
563     }
564 
565     /**
566      * Draw a text on canvas at relative to position (x, y),
567      * offset panX and panY.
568      * <br>
569      * The panning factors (panX, panY)  mapped to the
570      * resulting bounding box of the text, in such a way that a
571      * panning factor of (0.0, 0.0) would center the text at (x, y)
572      * <ul>
573      * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li>
574      * <li>Panning of  1.0,  1.0 - the text is below and to the left</li>
575      * <li>Panning of  1.0,  0.0 - the test is centered & to the right of x,y</li>
576      * </ul>
577      * Setting panY to NaN results in y being the baseline of the text.
578      *
579      * @param textId text to draw
580      * @param x      Coordinate of the Anchor
581      * @param y      Coordinate of the Anchor
582      * @param panX   justifies text -1.0=right, 0.0=center, 1.0=left
583      * @param panY   position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
584      * @param flags  1 = RTL
585      */
drawTextAnchored(int textId, float x, float y, float panX, float panY, int flags)586     public void drawTextAnchored(int textId,
587                                  float x,
588                                  float y,
589                                  float panX,
590                                  float panY,
591                                  int flags) {
592 
593         DrawTextAnchored.COMPANION.apply(
594                 mBuffer, textId,
595                 x, y,
596                 panX, panY,
597                 flags);
598     }
599 
600     /**
601      * draw an interpolation between two paths that have the same pattern
602      * <p>
603      * Warning paths objects are not immutable and this is not taken into consideration
604      *
605      * @param path1 The path1 to be drawn between
606      * @param path2 The path2 to be drawn between
607      * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
608      * @param start The start of the subrange of paths to draw 0 = start form start 0.5 is half way
609      * @param stop  The end of the subrange of paths to draw 1 = end at the end 0.5 is end half way
610      */
addDrawTweenPath(Object path1, Object path2, float tween, float start, float stop)611     public void addDrawTweenPath(Object path1,
612                                  Object path2,
613                                  float tween,
614                                  float start,
615                                  float stop) {
616         int path1Id = mRemoteComposeState.dataGetId(path1);
617         if (path1Id == -1) { // never been seen before
618             path1Id = addPathData(path1);
619         }
620         int path2Id = mRemoteComposeState.dataGetId(path2);
621         if (path2Id == -1) { // never been seen before
622             path2Id = addPathData(path2);
623         }
624         addDrawTweenPath(path1Id, path2Id, tween, start, stop);
625     }
626 
627     /**
628      * draw an interpolation between two paths that have the same pattern
629      *
630      * @param path1Id The path1 to be drawn between
631      * @param path2Id The path2 to be drawn between
632      * @param tween   The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
633      * @param start   The start of the subrange of paths to draw 0 = start form start .5 is 1/2 way
634      * @param stop    The end of the subrange of paths to draw 1 = end at the end .5 is end 1/2 way
635      */
addDrawTweenPath(int path1Id, int path2Id, float tween, float start, float stop)636     public void addDrawTweenPath(int path1Id,
637                                  int path2Id,
638                                  float tween,
639                                  float start,
640                                  float stop) {
641         DrawTweenPath.COMPANION.apply(
642                 mBuffer, path1Id, path2Id,
643                 tween, start, stop);
644     }
645 
646     /**
647      * Add a path object
648      *
649      * @param path
650      * @return the id of the path on the wire
651      */
addPathData(Object path)652     public int addPathData(Object path) {
653         float[] pathData = mPlatform.pathToFloatArray(path);
654         int id = mRemoteComposeState.cache(path);
655         PathData.COMPANION.apply(mBuffer, id, pathData);
656         return id;
657     }
658 
659     /**
660      * Adds a paint Bundle to the doc
661      * @param paint
662      */
addPaint(PaintBundle paint)663     public void addPaint(PaintBundle paint) {
664         PaintData.COMPANION.apply(mBuffer, paint);
665     }
666     ///////////////////////////////////////////////////////////////////////////////////////////////
667 
inflateFromBuffer(ArrayList<Operation> operations)668     public void inflateFromBuffer(ArrayList<Operation> operations) {
669         mBuffer.setIndex(0);
670         while (mBuffer.available()) {
671             int opId = mBuffer.readByte();
672             if (DEBUG) {
673                 Utils.log(">> " + opId);
674             }
675             CompanionOperation operation = Operations.map.get(opId);
676             if (operation == null) {
677                 throw new RuntimeException("Unknown operation encountered " + opId);
678             }
679             operation.read(mBuffer, operations);
680         }
681     }
682 
copy()683     RemoteComposeBuffer copy() {
684         ArrayList<Operation> operations = new ArrayList<>();
685         inflateFromBuffer(operations);
686         RemoteComposeBuffer buffer = new RemoteComposeBuffer(mRemoteComposeState);
687         return copyFromOperations(operations, buffer);
688     }
689 
setTheme(int theme)690     public void setTheme(int theme) {
691         Theme.COMPANION.apply(mBuffer, theme);
692     }
693 
version()694     static String version() {
695         return "v1.0";
696     }
697 
fromFile(String path, RemoteComposeState remoteComposeState)698     public static RemoteComposeBuffer fromFile(String path,
699                                                RemoteComposeState remoteComposeState)
700             throws IOException {
701         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
702         read(new File(path), buffer);
703         return buffer;
704     }
705 
fromFile(File file, RemoteComposeState remoteComposeState)706     public RemoteComposeBuffer fromFile(File file,
707                                         RemoteComposeState remoteComposeState) throws IOException {
708         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
709         read(file, buffer);
710         return buffer;
711     }
712 
fromInputStream(InputStream inputStream, RemoteComposeState remoteComposeState)713     public static RemoteComposeBuffer fromInputStream(InputStream inputStream,
714                                                       RemoteComposeState remoteComposeState) {
715         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
716         read(inputStream, buffer);
717         return buffer;
718     }
719 
copyFromOperations(ArrayList<Operation> operations, RemoteComposeBuffer buffer)720     RemoteComposeBuffer copyFromOperations(ArrayList<Operation> operations,
721                                            RemoteComposeBuffer buffer) {
722 
723         for (Operation operation : operations) {
724             operation.write(buffer.mBuffer);
725         }
726         return buffer;
727     }
728 
write(RemoteComposeBuffer buffer, File file)729     public void write(RemoteComposeBuffer buffer, File file) {
730         try {
731             FileOutputStream fd = new FileOutputStream(file);
732             fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize());
733             fd.flush();
734             fd.close();
735         } catch (Exception ex) {
736             ex.printStackTrace();
737         }
738     }
739 
read(File file, RemoteComposeBuffer buffer)740     static void read(File file, RemoteComposeBuffer buffer) throws IOException {
741         FileInputStream fd = new FileInputStream(file);
742         read(fd, buffer);
743     }
744 
read(InputStream fd, RemoteComposeBuffer buffer)745     public static void read(InputStream fd, RemoteComposeBuffer buffer) {
746         try {
747             byte[] bytes = readAllBytes(fd);
748             buffer.reset(bytes.length);
749             System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
750             buffer.mBuffer.mSize = bytes.length;
751         } catch (Exception e) {
752             e.printStackTrace();
753             // todo decide how to handel this stuff
754         }
755     }
756 
readAllBytes(InputStream is)757     private static byte[] readAllBytes(InputStream is) throws IOException {
758         byte[] buff = new byte[32 * 1024]; // moderate size buff to start
759         int red = 0;
760         while (true) {
761             int ret = is.read(buff, red, buff.length - red);
762             if (ret == -1) {
763                 is.close();
764                 return Arrays.copyOf(buff, red);
765             }
766             red += ret;
767             if (red == buff.length) {
768                 buff = Arrays.copyOf(buff, buff.length * 2);
769             }
770         }
771     }
772 
773     /**
774      * add a Pre-concat the current matrix with the specified skew.
775      *
776      * @param skewX The amount to skew in X
777      * @param skewY The amount to skew in Y
778      */
addMatrixSkew(float skewX, float skewY)779     public void addMatrixSkew(float skewX, float skewY) {
780         MatrixSkew.COMPANION.apply(mBuffer, skewX, skewY);
781     }
782 
783     /**
784      * This call balances a previous call to save(), and is used to remove all
785      * modifications to the matrix/clip state since the last save call.
786      * Do not call restore() more times than save() was called.
787      */
addMatrixRestore()788     public void addMatrixRestore() {
789         MatrixRestore.COMPANION.apply(mBuffer);
790     }
791 
792     /**
793      * Add a saves the current matrix and clip onto a private stack.
794      * <p>
795      * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
796      * clipPath will all operate as usual, but when the balancing call to
797      * restore() is made, those calls will be forgotten, and the settings that
798      * existed before the save() will be reinstated.
799      */
addMatrixSave()800     public void addMatrixSave() {
801         MatrixSave.COMPANION.apply(mBuffer);
802     }
803 
804     /**
805      * add a pre-concat the current matrix with the specified rotation.
806      *
807      * @param angle   The amount to rotate, in degrees
808      * @param centerX The x-coord for the pivot point (unchanged by the rotation)
809      * @param centerY The y-coord for the pivot point (unchanged by the rotation)
810      */
addMatrixRotate(float angle, float centerX, float centerY)811     public void addMatrixRotate(float angle, float centerX, float centerY) {
812         MatrixRotate.COMPANION.apply(mBuffer, angle, centerX, centerY);
813     }
814 
815     /**
816      * add a Pre-concat to the current matrix with the specified translation
817      *
818      * @param dx The distance to translate in X
819      * @param dy The distance to translate in Y
820      */
addMatrixTranslate(float dx, float dy)821     public void addMatrixTranslate(float dx, float dy) {
822         MatrixTranslate.COMPANION.apply(mBuffer, dx, dy);
823     }
824 
825     /**
826      * Add a pre-concat of the current matrix with the specified scale.
827      *
828      * @param scaleX The amount to scale in X
829      * @param scaleY The amount to scale in Y
830      */
addMatrixScale(float scaleX, float scaleY)831     public void addMatrixScale(float scaleX, float scaleY) {
832         MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN);
833     }
834 
835     /**
836      * Add a pre-concat of the current matrix with the specified scale.
837      *
838      * @param scaleX  The amount to scale in X
839      * @param scaleY  The amount to scale in Y
840      * @param centerX The x-coord for the pivot point (unchanged by the scale)
841      * @param centerY The y-coord for the pivot point (unchanged by the scale)
842      */
addMatrixScale(float scaleX, float scaleY, float centerX, float centerY)843     public void addMatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
844         MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY);
845     }
846 
847     /**
848      * sets the clip based on clip id
849      * @param pathId 0 clears the clip
850      */
addClipPath(int pathId)851     public void addClipPath(int pathId) {
852         ClipPath.COMPANION.apply(mBuffer, pathId);
853     }
854 
855     /**
856      * Sets the clip based on clip rec
857      * @param left
858      * @param top
859      * @param right
860      * @param bottom
861      */
addClipRect(float left, float top, float right, float bottom)862     public void addClipRect(float left, float top, float right, float bottom) {
863         ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom);
864     }
865 
866     /**
867      * Add a float return a NaN number pointing to that float
868      * @param value
869      * @return
870      */
addFloat(float value)871     public float addFloat(float value) {
872         int id = mRemoteComposeState.cacheFloat(value);
873         FloatConstant.COMPANION.apply(mBuffer, id, value);
874         return Utils.asNan(id);
875     }
876 
877     /**
878      * Add a float that is a computation based on variables
879      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
880      * @return NaN id of the result of the calculation
881      */
addAnimatedFloat(float... value)882     public float addAnimatedFloat(float... value) {
883         int id = mRemoteComposeState.cache(value);
884         FloatExpression.COMPANION.apply(mBuffer, id, value, null);
885         return Utils.asNan(id);
886     }
887 
888     /**
889      * Add a float that is a computation based on variables.
890      * see packAnimation
891      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
892      * @param animation Array of floats that represents animation
893      * @return NaN id of the result of the calculation
894      */
addAnimatedFloat(float[] value, float[] animation)895     public float addAnimatedFloat(float[] value, float[] animation) {
896         int id = mRemoteComposeState.cache(value);
897         FloatExpression.COMPANION.apply(mBuffer, id, value, animation);
898         return Utils.asNan(id);
899     }
900 
901     /**
902      * Add a color that represents the tween between two colors
903      * @param color1
904      * @param color2
905      * @param tween
906      * @return id of the color (color ids are short)
907      */
addColorExpression(int color1, int color2, float tween)908     public short addColorExpression(int color1, int color2, float tween) {
909         ColorExpression c = new ColorExpression(0, 0, color1, color2, tween);
910         short id = (short) mRemoteComposeState.cache(c);
911         c.mId = id;
912         c.write(mBuffer);
913         return id;
914     }
915 
916     /**
917      * Add a color that represents the tween between two colors where color1
918      * is the id of a color
919      * @param color1
920      * @param color2
921      * @param tween
922      * @return id of the color (color ids are short)
923      */
addColorExpression(short color1, int color2, float tween)924     public short addColorExpression(short color1, int color2, float tween) {
925         ColorExpression c = new ColorExpression(0, 1, color1, color2, tween);
926         short id = (short) mRemoteComposeState.cache(c);
927         c.mId = id;
928         c.write(mBuffer);
929         return id;
930     }
931 
932     /**
933      * Add a color that represents the tween between two colors where color2
934      * is the id of a color
935      * @param color1
936      * @param color2
937      * @param tween
938      * @return id of the color (color ids are short)
939      */
addColorExpression(int color1, short color2, float tween)940     public short addColorExpression(int color1, short color2, float tween) {
941         ColorExpression c = new ColorExpression(0, 2, color1, color2, tween);
942         short id = (short) mRemoteComposeState.cache(c);
943         c.mId = id;
944         c.write(mBuffer);
945         return id;
946     }
947 
948     /**
949      * Add a color that represents the tween between two colors where color1 &
950      * color2 are the ids of colors
951      * @param color1
952      * @param color2
953      * @param tween
954      * @return id of the color (color ids are short)
955      */
addColorExpression(short color1, short color2, float tween)956     public short addColorExpression(short color1, short color2, float tween) {
957         ColorExpression c = new ColorExpression(0, 3, color1, color2, tween);
958         short id = (short) mRemoteComposeState.cache(c);
959         c.mId = id;
960         c.write(mBuffer);
961         return id;
962     }
963 
964     /**
965      *  Color calculated by Hue saturation and value.
966      *  (as floats they can be variables used to create color transitions)
967      * @param hue
968      * @param sat
969      * @param value
970      * @return id of the color (color ids are short)
971      */
addColorExpression(float hue, float sat, float value)972     public short addColorExpression(float hue, float sat, float value) {
973         ColorExpression c = new ColorExpression(0, hue, sat, value);
974         short id = (short) mRemoteComposeState.cache(c);
975         c.mId = id;
976         c.write(mBuffer);
977         return id;
978     }
979 
980     /**
981      * Color calculated by Alpha, Hue saturation and value.
982      * (as floats they can be variables used to create color transitions)
983      * @param alpha
984      * @param hue
985      * @param sat
986      * @param value
987      * @return id of the color (color ids are short)
988      */
addColorExpression(int alpha, float hue, float sat, float value)989     public short addColorExpression(int alpha, float hue, float sat, float value) {
990         ColorExpression c = new ColorExpression(0, alpha, hue, sat, value);
991         short id = (short) mRemoteComposeState.cache(c);
992         c.mId = id;
993         c.write(mBuffer);
994         return id;
995     }
996 
997     /**
998      * create and animation based on description and return as an array of
999      * floats. see addAnimatedFloat
1000      * @param duration
1001      * @param type
1002      * @param spec
1003      * @param initialValue
1004      * @param wrap
1005      * @return
1006      */
packAnimation(float duration, int type, float[] spec, float initialValue, float wrap)1007     public static float[] packAnimation(float duration,
1008                                         int type,
1009                                         float[] spec,
1010                                         float initialValue,
1011                                         float wrap) {
1012 
1013         return FloatAnimation.packToFloatArray(duration, type, spec, initialValue, wrap);
1014     }
1015 
1016 }
1017 
1018