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