1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 5 import static android.os.Build.VERSION_CODES.KITKAT; 6 import static android.os.Build.VERSION_CODES.KITKAT_WATCH; 7 import static android.os.Build.VERSION_CODES.L; 8 import static android.os.Build.VERSION_CODES.LOLLIPOP; 9 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 10 import static android.os.Build.VERSION_CODES.M; 11 import static android.os.Build.VERSION_CODES.N; 12 import static android.os.Build.VERSION_CODES.N_MR1; 13 import static android.os.Build.VERSION_CODES.O; 14 import static android.os.Build.VERSION_CODES.O_MR1; 15 import static android.os.Build.VERSION_CODES.P; 16 import static android.os.Build.VERSION_CODES.Q; 17 import static org.robolectric.annotation.TextLayoutMode.Mode.REALISTIC; 18 19 import android.graphics.ColorFilter; 20 import android.graphics.Paint; 21 import android.graphics.Paint.FontMetricsInt; 22 import android.graphics.PathEffect; 23 import android.graphics.Shader; 24 import android.graphics.Typeface; 25 import org.robolectric.annotation.Implementation; 26 import org.robolectric.annotation.Implements; 27 import org.robolectric.annotation.RealObject; 28 import org.robolectric.annotation.TextLayoutMode; 29 import org.robolectric.config.ConfigurationRegistry; 30 import org.robolectric.shadow.api.Shadow; 31 import org.robolectric.util.ReflectionHelpers.ClassParameter; 32 33 @SuppressWarnings({"UnusedDeclaration"}) 34 @Implements(value = Paint.class, looseSignatures = true) 35 public class ShadowPaint { 36 37 private int color; 38 private Paint.Style style; 39 private Paint.Cap cap; 40 private Paint.Join join; 41 private float width; 42 private float shadowRadius; 43 private float shadowDx; 44 private float shadowDy; 45 private int shadowColor; 46 private Shader shader; 47 private int alpha; 48 private ColorFilter filter; 49 private boolean antiAlias; 50 private boolean dither; 51 private int flags; 52 private PathEffect pathEffect; 53 private float letterSpacing; 54 private float textScaleX = 1f; 55 private float textSkewX; 56 private float wordSpacing; 57 58 @RealObject Paint paint; 59 private Typeface typeface; 60 private float textSize; 61 private Paint.Align textAlign = Paint.Align.LEFT; 62 63 @Implementation __constructor__(Paint otherPaint)64 protected void __constructor__(Paint otherPaint) { 65 ShadowPaint otherShadowPaint = Shadow.extract(otherPaint); 66 this.color = otherShadowPaint.color; 67 this.style = otherShadowPaint.style; 68 this.cap = otherShadowPaint.cap; 69 this.join = otherShadowPaint.join; 70 this.width = otherShadowPaint.width; 71 this.shadowRadius = otherShadowPaint.shadowRadius; 72 this.shadowDx = otherShadowPaint.shadowDx; 73 this.shadowDy = otherShadowPaint.shadowDy; 74 this.shadowColor = otherShadowPaint.shadowColor; 75 this.shader = otherShadowPaint.shader; 76 this.alpha = otherShadowPaint.alpha; 77 this.filter = otherShadowPaint.filter; 78 this.antiAlias = otherShadowPaint.antiAlias; 79 this.dither = otherShadowPaint.dither; 80 this.flags = otherShadowPaint.flags; 81 this.pathEffect = otherShadowPaint.pathEffect; 82 this.letterSpacing = otherShadowPaint.letterSpacing; 83 this.textScaleX = otherShadowPaint.textScaleX; 84 this.textSkewX = otherShadowPaint.textSkewX; 85 this.wordSpacing = otherShadowPaint.wordSpacing; 86 87 Shadow.invokeConstructor(Paint.class, paint, ClassParameter.from(Paint.class, otherPaint)); 88 } 89 90 @Implementation(minSdk = N) nInit()91 protected static long nInit() { 92 return 1; 93 } 94 95 @Implementation getFlags()96 protected int getFlags() { 97 return flags; 98 } 99 100 @Implementation setFlags(int flags)101 protected void setFlags(int flags) { 102 this.flags = flags; 103 } 104 105 @Implementation setUnderlineText(boolean underlineText)106 protected void setUnderlineText(boolean underlineText) { 107 if (underlineText) { 108 setFlags(flags | Paint.UNDERLINE_TEXT_FLAG); 109 } else { 110 setFlags(flags & ~Paint.UNDERLINE_TEXT_FLAG); 111 } 112 } 113 114 @Implementation setStrikeThruText(boolean strikeThruText)115 protected void setStrikeThruText(boolean strikeThruText) { 116 if (strikeThruText) { 117 setFlags(flags | Paint.STRIKE_THRU_TEXT_FLAG); 118 } else { 119 setFlags(flags & ~Paint.STRIKE_THRU_TEXT_FLAG); 120 } 121 } 122 123 @Implementation setShader(Shader shader)124 protected Shader setShader(Shader shader) { 125 this.shader = shader; 126 return shader; 127 } 128 129 @Implementation getAlpha()130 protected int getAlpha() { 131 return alpha; 132 } 133 134 @Implementation setAlpha(int alpha)135 protected void setAlpha(int alpha) { 136 this.alpha = alpha; 137 } 138 139 @Implementation getShader()140 protected Shader getShader() { 141 return shader; 142 } 143 144 @Implementation setColor(int color)145 protected void setColor(int color) { 146 this.color = color; 147 } 148 149 @Implementation getColor()150 protected int getColor() { 151 return color; 152 } 153 154 @Implementation setStyle(Paint.Style style)155 protected void setStyle(Paint.Style style) { 156 this.style = style; 157 } 158 159 @Implementation getStyle()160 protected Paint.Style getStyle() { 161 return style; 162 } 163 164 @Implementation setStrokeCap(Paint.Cap cap)165 protected void setStrokeCap(Paint.Cap cap) { 166 this.cap = cap; 167 } 168 169 @Implementation getStrokeCap()170 protected Paint.Cap getStrokeCap() { 171 return cap; 172 } 173 174 @Implementation setStrokeJoin(Paint.Join join)175 protected void setStrokeJoin(Paint.Join join) { 176 this.join = join; 177 } 178 179 @Implementation getStrokeJoin()180 protected Paint.Join getStrokeJoin() { 181 return join; 182 } 183 184 @Implementation setStrokeWidth(float width)185 protected void setStrokeWidth(float width) { 186 this.width = width; 187 } 188 189 @Implementation getStrokeWidth()190 protected float getStrokeWidth() { 191 return width; 192 } 193 194 @Implementation setShadowLayer(float radius, float dx, float dy, int color)195 protected void setShadowLayer(float radius, float dx, float dy, int color) { 196 shadowRadius = radius; 197 shadowDx = dx; 198 shadowDy = dy; 199 shadowColor = color; 200 } 201 202 @Implementation getTypeface()203 protected Typeface getTypeface() { 204 return typeface; 205 } 206 207 @Implementation setTypeface(Typeface typeface)208 protected Typeface setTypeface(Typeface typeface) { 209 this.typeface = typeface; 210 return typeface; 211 } 212 213 @Implementation getTextSize()214 protected float getTextSize() { 215 return textSize; 216 } 217 218 @Implementation setTextSize(float textSize)219 protected void setTextSize(float textSize) { 220 this.textSize = textSize; 221 } 222 223 @Implementation getTextScaleX()224 protected float getTextScaleX() { 225 return textScaleX; 226 } 227 228 @Implementation setTextScaleX(float scaleX)229 protected void setTextScaleX(float scaleX) { 230 this.textScaleX = scaleX; 231 } 232 233 @Implementation getTextSkewX()234 protected float getTextSkewX() { 235 return textSkewX; 236 } 237 238 @Implementation setTextSkewX(float skewX)239 protected void setTextSkewX(float skewX) { 240 this.textSkewX = skewX; 241 } 242 243 @Implementation(minSdk = L) getLetterSpacing()244 protected float getLetterSpacing() { 245 return letterSpacing; 246 } 247 248 @Implementation(minSdk = L) setLetterSpacing(float letterSpacing)249 protected void setLetterSpacing(float letterSpacing) { 250 this.letterSpacing = letterSpacing; 251 } 252 253 @Implementation(minSdk = Q) getWordSpacing()254 protected float getWordSpacing() { 255 return wordSpacing; 256 } 257 258 @Implementation(minSdk = Q) setWordSpacing(float wordSpacing)259 protected void setWordSpacing(float wordSpacing) { 260 this.wordSpacing = wordSpacing; 261 } 262 263 @Implementation setTextAlign(Paint.Align align)264 protected void setTextAlign(Paint.Align align) { 265 textAlign = align; 266 } 267 268 @Implementation getTextAlign()269 protected Paint.Align getTextAlign() { 270 return textAlign; 271 } 272 273 /** 274 * @return shadow radius (Paint related shadow, not Robolectric Shadow) 275 */ getShadowRadius()276 public float getShadowRadius() { 277 return shadowRadius; 278 } 279 280 /** 281 * @return shadow Dx (Paint related shadow, not Robolectric Shadow) 282 */ getShadowDx()283 public float getShadowDx() { 284 return shadowDx; 285 } 286 287 /** 288 * @return shadow Dx (Paint related shadow, not Robolectric Shadow) 289 */ getShadowDy()290 public float getShadowDy() { 291 return shadowDy; 292 } 293 294 /** 295 * @return shadow color (Paint related shadow, not Robolectric Shadow) 296 */ getShadowColor()297 public int getShadowColor() { 298 return shadowColor; 299 } 300 getCap()301 public Paint.Cap getCap() { 302 return cap; 303 } 304 getJoin()305 public Paint.Join getJoin() { 306 return join; 307 } 308 getWidth()309 public float getWidth() { 310 return width; 311 } 312 313 @Implementation getColorFilter()314 protected ColorFilter getColorFilter() { 315 return filter; 316 } 317 318 @Implementation setColorFilter(ColorFilter filter)319 protected ColorFilter setColorFilter(ColorFilter filter) { 320 this.filter = filter; 321 return filter; 322 } 323 324 @Implementation setAntiAlias(boolean antiAlias)325 protected void setAntiAlias(boolean antiAlias) { 326 this.flags = (flags & ~Paint.ANTI_ALIAS_FLAG) | (antiAlias ? Paint.ANTI_ALIAS_FLAG : 0); 327 } 328 329 @Implementation setDither(boolean dither)330 protected void setDither(boolean dither) { 331 this.dither = dither; 332 } 333 334 @Implementation isDither()335 protected final boolean isDither() { 336 return dither; 337 } 338 339 @Implementation isAntiAlias()340 protected final boolean isAntiAlias() { 341 return (flags & Paint.ANTI_ALIAS_FLAG) == Paint.ANTI_ALIAS_FLAG; 342 } 343 344 @Implementation isFilterBitmap()345 protected final boolean isFilterBitmap() { 346 return (flags & Paint.FILTER_BITMAP_FLAG) == Paint.FILTER_BITMAP_FLAG; 347 } 348 349 @Implementation setFilterBitmap(boolean filterBitmap)350 protected final void setFilterBitmap(boolean filterBitmap) { 351 this.flags = 352 (flags & ~Paint.FILTER_BITMAP_FLAG) | (filterBitmap ? Paint.FILTER_BITMAP_FLAG : 0); 353 } 354 355 @Implementation getPathEffect()356 protected PathEffect getPathEffect() { 357 return pathEffect; 358 } 359 360 @Implementation setPathEffect(PathEffect effect)361 protected PathEffect setPathEffect(PathEffect effect) { 362 this.pathEffect = effect; 363 return effect; 364 } 365 366 @Implementation measureText(String text)367 protected float measureText(String text) { 368 return applyTextScaleX(text.length()); 369 } 370 371 @Implementation measureText(CharSequence text, int start, int end)372 protected float measureText(CharSequence text, int start, int end) { 373 return applyTextScaleX(end - start); 374 } 375 376 @Implementation measureText(String text, int start, int end)377 protected float measureText(String text, int start, int end) { 378 return applyTextScaleX(end - start); 379 } 380 381 @Implementation measureText(char[] text, int index, int count)382 protected float measureText(char[] text, int index, int count) { 383 return applyTextScaleX(count); 384 } 385 applyTextScaleX(float textWidth)386 private float applyTextScaleX(float textWidth) { 387 return Math.max(0f, textScaleX) * textWidth; 388 } 389 390 @Implementation(maxSdk = JELLY_BEAN_MR1) native_breakText( char[] text, int index, int count, float maxWidth, float[] measuredWidth)391 protected int native_breakText( 392 char[] text, int index, int count, float maxWidth, float[] measuredWidth) { 393 return breakText(text, maxWidth, measuredWidth); 394 } 395 396 @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) native_breakText( char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth)397 protected int native_breakText( 398 char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) { 399 return breakText(text, maxWidth, measuredWidth); 400 } 401 402 @Implementation(minSdk = LOLLIPOP, maxSdk = M) native_breakText( long native_object, long native_typeface, char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth)403 protected static int native_breakText( 404 long native_object, 405 long native_typeface, 406 char[] text, 407 int index, 408 int count, 409 float maxWidth, 410 int bidiFlags, 411 float[] measuredWidth) { 412 return breakText(text, maxWidth, measuredWidth); 413 } 414 415 @Implementation(minSdk = N, maxSdk = O_MR1) nBreakText( long nObject, long nTypeface, char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth)416 protected static int nBreakText( 417 long nObject, 418 long nTypeface, 419 char[] text, 420 int index, 421 int count, 422 float maxWidth, 423 int bidiFlags, 424 float[] measuredWidth) { 425 return breakText(text, maxWidth, measuredWidth); 426 } 427 428 @Implementation(minSdk = P) nBreakText( long nObject, char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth)429 protected static int nBreakText( 430 long nObject, 431 char[] text, 432 int index, 433 int count, 434 float maxWidth, 435 int bidiFlags, 436 float[] measuredWidth) { 437 return breakText(text, maxWidth, measuredWidth); 438 } 439 breakText(char[] text, float maxWidth, float[] measuredWidth)440 private static int breakText(char[] text, float maxWidth, float[] measuredWidth) { 441 if (measuredWidth != null) { 442 measuredWidth[0] = maxWidth; 443 } 444 return text.length; 445 } 446 447 @Implementation(maxSdk = JELLY_BEAN_MR1) native_breakText( String text, boolean measureForwards, float maxWidth, float[] measuredWidth)448 protected int native_breakText( 449 String text, boolean measureForwards, float maxWidth, float[] measuredWidth) { 450 return breakText(text, maxWidth, measuredWidth); 451 } 452 453 @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) native_breakText( String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth)454 protected int native_breakText( 455 String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) { 456 return breakText(text, maxWidth, measuredWidth); 457 } 458 459 @Implementation(minSdk = LOLLIPOP, maxSdk = M) native_breakText( long native_object, long native_typeface, String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth)460 protected static int native_breakText( 461 long native_object, 462 long native_typeface, 463 String text, 464 boolean measureForwards, 465 float maxWidth, 466 int bidiFlags, 467 float[] measuredWidth) { 468 return breakText(text, maxWidth, measuredWidth); 469 } 470 471 @Implementation(minSdk = N, maxSdk = O_MR1) nBreakText( long nObject, long nTypeface, String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth)472 protected static int nBreakText( 473 long nObject, 474 long nTypeface, 475 String text, 476 boolean measureForwards, 477 float maxWidth, 478 int bidiFlags, 479 float[] measuredWidth) { 480 return breakText(text, maxWidth, measuredWidth); 481 } 482 483 @Implementation(minSdk = P) nBreakText( long nObject, String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth)484 protected static int nBreakText( 485 long nObject, 486 String text, 487 boolean measureForwards, 488 float maxWidth, 489 int bidiFlags, 490 float[] measuredWidth) { 491 return breakText(text, maxWidth, measuredWidth); 492 } 493 breakText(String text, float maxWidth, float[] measuredWidth)494 private static int breakText(String text, float maxWidth, float[] measuredWidth) { 495 if (measuredWidth != null) { 496 measuredWidth[0] = maxWidth; 497 } 498 return text.length(); 499 } 500 501 @Implementation(minSdk = P) nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi)502 protected static int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi) { 503 if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) { 504 // TODO: hack, just set values to those we see on emulator 505 int descent = 7; 506 int ascent = -28; 507 int leading = 0; 508 509 if (fmi != null) { 510 fmi.top = -32; 511 fmi.ascent = ascent; 512 fmi.descent = descent; 513 fmi.bottom = 9; 514 fmi.leading = leading; 515 } 516 return descent - ascent + leading; 517 } 518 return 0; 519 } 520 521 @Implementation(minSdk = O, maxSdk = O_MR1) nGetFontMetricsInt( long nativePaint, long nativeTypeface, FontMetricsInt fmi)522 protected static int nGetFontMetricsInt( 523 long nativePaint, long nativeTypeface, FontMetricsInt fmi) { 524 return nGetFontMetricsInt(nativePaint, fmi); 525 } 526 527 @Implementation(minSdk = N, maxSdk = N_MR1) nGetFontMetricsInt(Object nativePaint, Object nativeTypeface, Object fmi)528 protected int nGetFontMetricsInt(Object nativePaint, Object nativeTypeface, Object fmi) { 529 return nGetFontMetricsInt((long) nativePaint, (FontMetricsInt) fmi); 530 } 531 532 @Implementation(maxSdk = M) getFontMetricsInt(FontMetricsInt fmi)533 protected int getFontMetricsInt(FontMetricsInt fmi) { 534 return nGetFontMetricsInt(0, fmi); 535 } 536 537 @Implementation(minSdk = P) nGetRunAdvance( long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)538 protected static float nGetRunAdvance( 539 long paintPtr, 540 char[] text, 541 int start, 542 int end, 543 int contextStart, 544 int contextEnd, 545 boolean isRtl, 546 int offset) { 547 if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) { 548 // be consistent with measureText for measurements, and measure 1 pixel per char 549 return end - start; 550 } 551 return 0f; 552 } 553 554 @Implementation(minSdk = N, maxSdk = O_MR1) nGetRunAdvance( long paintPtr, long typefacePtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)555 protected static float nGetRunAdvance( 556 long paintPtr, 557 long typefacePtr, 558 char[] text, 559 int start, 560 int end, 561 int contextStart, 562 int contextEnd, 563 boolean isRtl, 564 int offset) { 565 return nGetRunAdvance(paintPtr, text, start, end, contextStart, contextEnd, isRtl, offset); 566 } 567 568 @Implementation(minSdk = M, maxSdk = M) native_getRunAdvance( long nativeObject, long nativeTypeface, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)569 protected static float native_getRunAdvance( 570 long nativeObject, 571 long nativeTypeface, 572 char[] text, 573 int start, 574 int end, 575 int contextStart, 576 int contextEnd, 577 boolean isRtl, 578 int offset) { 579 return nGetRunAdvance(0, text, start, end, contextStart, contextEnd, isRtl, offset); 580 } 581 582 @Implementation(minSdk = KITKAT_WATCH, maxSdk = LOLLIPOP_MR1) native_getTextRunAdvances( long nativeObject, long nativeTypeface, char[] text, int index, int count, int contextIndex, int contextCount, boolean isRtl, float[] advances, int advancesIndex)583 protected static float native_getTextRunAdvances( 584 long nativeObject, 585 long nativeTypeface, 586 char[] text, 587 int index, 588 int count, 589 int contextIndex, 590 int contextCount, 591 boolean isRtl, 592 float[] advances, 593 int advancesIndex) { 594 return nGetRunAdvance( 595 0, text, index, index + count, contextIndex, contextIndex + contextCount, isRtl, index); 596 } 597 598 @Implementation(minSdk = KITKAT_WATCH, maxSdk = LOLLIPOP_MR1) native_getTextRunAdvances( long nativeObject, long nativeTypeface, String text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex)599 protected static float native_getTextRunAdvances( 600 long nativeObject, 601 long nativeTypeface, 602 String text, 603 int start, 604 int end, 605 int contextStart, 606 int contextEnd, 607 boolean isRtl, 608 float[] advances, 609 int advancesIndex) { 610 return nGetRunAdvance(0, text.toCharArray(), start, end, contextStart, contextEnd, isRtl, 0); 611 } 612 613 @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) native_getTextRunAdvances( int nativeObject, char[] text, int index, int count, int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex)614 protected static float native_getTextRunAdvances( 615 int nativeObject, 616 char[] text, 617 int index, 618 int count, 619 int contextIndex, 620 int contextCount, 621 int flags, 622 float[] advances, 623 int advancesIndex) { 624 return nGetRunAdvance( 625 0, text, index, index + count, contextIndex, contextIndex + contextCount, false, index); 626 } 627 628 @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) native_getTextRunAdvances( int nativeObject, String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex)629 protected static float native_getTextRunAdvances( 630 int nativeObject, 631 String text, 632 int start, 633 int end, 634 int contextStart, 635 int contextEnd, 636 int flags, 637 float[] advances, 638 int advancesIndex) { 639 return nGetRunAdvance(0, text.toCharArray(), start, end, contextStart, contextEnd, false, 0); 640 } 641 642 @Implementation(maxSdk = JELLY_BEAN_MR1) native_getTextRunAdvances( int nativeObject, char[] text, int index, int count, int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex, int reserved)643 protected static float native_getTextRunAdvances( 644 int nativeObject, 645 char[] text, 646 int index, 647 int count, 648 int contextIndex, 649 int contextCount, 650 int flags, 651 float[] advances, 652 int advancesIndex, 653 int reserved) { 654 return nGetRunAdvance( 655 0, text, index, index + count, contextIndex, contextIndex + contextCount, false, index); 656 } 657 658 @Implementation(maxSdk = JELLY_BEAN_MR1) native_getTextRunAdvances( int nativeObject, String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex, int reserved)659 protected static float native_getTextRunAdvances( 660 int nativeObject, 661 String text, 662 int start, 663 int end, 664 int contextStart, 665 int contextEnd, 666 int flags, 667 float[] advances, 668 int advancesIndex, 669 int reserved) { 670 return nGetRunAdvance(0, text.toCharArray(), start, end, contextStart, contextEnd, false, 0); 671 } 672 } 673