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