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