Canvas_Delegate.java revision cfdc784b6cdcbbb2bf2ba4d53d9a9eb2c37278a3
1/*
2 * Copyright (C) 2010 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
17package android.graphics;
18
19import com.android.layoutlib.bridge.Bridge;
20import com.android.layoutlib.bridge.impl.DelegateManager;
21import com.android.layoutlib.bridge.impl.GcSnapshot;
22
23import android.graphics.Paint_Delegate.FontInfo;
24import android.text.TextUtils;
25
26import java.awt.AlphaComposite;
27import java.awt.BasicStroke;
28import java.awt.Color;
29import java.awt.Graphics2D;
30import java.awt.Rectangle;
31import java.awt.RenderingHints;
32import java.awt.geom.AffineTransform;
33import java.awt.image.BufferedImage;
34import java.util.List;
35
36
37/**
38 * Delegate implementing the native methods of android.graphics.Canvas
39 *
40 * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
41 * by calls to methods of the same name in this delegate class.
42 *
43 * This class behaves like the original native implementation, but in Java, keeping previously
44 * native data into its own objects and mapping them to int that are sent back and forth between
45 * it and the original Canvas class.
46 *
47 * @see DelegateManager
48 *
49 */
50public class Canvas_Delegate {
51
52    // ---- delegate manager ----
53    private static final DelegateManager<Canvas_Delegate> sManager =
54            new DelegateManager<Canvas_Delegate>();
55
56    // ---- delegate helper data ----
57
58    // ---- delegate data ----
59    private BufferedImage mBufferedImage;
60    private GcSnapshot mSnapshot = new GcSnapshot();
61
62    // ---- Public Helper methods ----
63
64    /**
65     * Returns the native delegate associated to a given {@link Canvas} object.
66     */
67    public static Canvas_Delegate getDelegate(Canvas canvas) {
68        return sManager.getDelegate(canvas.mNativeCanvas);
69    }
70
71    /**
72     * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
73     */
74    public static Canvas_Delegate getDelegate(int native_canvas) {
75        return sManager.getDelegate(native_canvas);
76    }
77
78    /**
79     * Returns the current {@link Graphics2D} used to draw.
80     */
81    public GcSnapshot getGcSnapshot() {
82        return mSnapshot;
83    }
84
85    // ---- native methods ----
86
87    /*package*/ static boolean isOpaque(Canvas thisCanvas) {
88        // FIXME
89        throw new UnsupportedOperationException();
90    }
91
92    /*package*/ static int getWidth(Canvas thisCanvas) {
93        // get the delegate from the native int.
94        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
95        if (canvasDelegate == null) {
96            assert false;
97            return 0;
98        }
99
100        return canvasDelegate.mBufferedImage.getWidth();
101    }
102
103    /*package*/ static int getHeight(Canvas thisCanvas) {
104        // get the delegate from the native int.
105        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
106        if (canvasDelegate == null) {
107            assert false;
108            return 0;
109        }
110
111        return canvasDelegate.mBufferedImage.getHeight();
112    }
113
114    /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) {
115        // get the delegate from the native int.
116        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
117        if (canvasDelegate == null) {
118            assert false;
119            return;
120        }
121
122        canvasDelegate.getGcSnapshot().translate(dx, dy);
123    }
124
125    /*package*/ static void rotate(Canvas thisCanvas, float degrees) {
126        // get the delegate from the native int.
127        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
128        if (canvasDelegate == null) {
129            assert false;
130            return;
131        }
132
133        canvasDelegate.getGcSnapshot().rotate(Math.toRadians(degrees));
134    }
135
136    /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
137        // get the delegate from the native int.
138        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
139        if (canvasDelegate == null) {
140            assert false;
141            return;
142        }
143
144        canvasDelegate.getGcSnapshot().scale(sx, sy);
145    }
146
147    /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
148        // get the delegate from the native int.
149        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
150        if (canvasDelegate == null) {
151            assert false;
152            return;
153        }
154
155        // get the current top graphics2D object.
156        GcSnapshot g = canvasDelegate.getGcSnapshot();
157
158        // get its current matrix
159        AffineTransform currentTx = g.getTransform();
160        // get the AffineTransform for the given skew.
161        float[] mtx = Matrix_Delegate.getSkew(kx, ky);
162        AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
163
164        // combine them so that the given matrix is applied after.
165        currentTx.preConcatenate(matrixTx);
166
167        // give it to the graphics2D as a new matrix replacing all previous transform
168        g.setTransform(currentTx);
169    }
170
171    /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) {
172        return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom);
173    }
174
175    /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) {
176        return clipRect(thisCanvas, (float) rect.left, (float) rect.top,
177                (float) rect.right, (float) rect.bottom);
178    }
179
180    /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right,
181            float bottom) {
182        // get the delegate from the native int.
183        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
184        if (canvasDelegate == null) {
185            assert false;
186            return false;
187        }
188
189        return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
190    }
191
192    /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right,
193            int bottom) {
194
195        return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom);
196    }
197
198    /*package*/ static int save(Canvas thisCanvas) {
199        return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
200    }
201
202    /*package*/ static int save(Canvas thisCanvas, int saveFlags) {
203        // get the delegate from the native int.
204        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
205        if (canvasDelegate == null) {
206            assert false;
207            return 0;
208        }
209
210        return canvasDelegate.save(saveFlags);
211    }
212
213    /*package*/ static void restore(Canvas thisCanvas) {
214        // get the delegate from the native int.
215        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
216        if (canvasDelegate == null) {
217            assert false;
218            return;
219        }
220
221        canvasDelegate.restore();
222    }
223
224    /*package*/ static int getSaveCount(Canvas thisCanvas) {
225        // get the delegate from the native int.
226        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
227        if (canvasDelegate == null) {
228            assert false;
229            return 0;
230        }
231
232        return canvasDelegate.getGcSnapshot().size();
233    }
234
235    /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
236        // get the delegate from the native int.
237        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
238        if (canvasDelegate == null) {
239            assert false;
240            return;
241        }
242
243        canvasDelegate.restoreTo(saveCount);
244    }
245
246    /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count,
247            Paint paint) {
248        // FIXME
249        throw new UnsupportedOperationException();
250    }
251
252    /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) {
253        // FIXME
254        throw new UnsupportedOperationException();
255    }
256
257    /*package*/ static void drawLines(Canvas thisCanvas, float[] pts, int offset, int count,
258            Paint paint) {
259        // get the delegate from the native int.
260        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
261        if (canvasDelegate == null) {
262            assert false;
263            return;
264        }
265
266        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint.mNativePaint);
267        if (paintDelegate == null) {
268            assert false;
269            return;
270        }
271
272        // get a Graphics2D object configured with the drawing parameters.
273        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
274
275        try {
276            for (int i = 0 ; i < count ; i += 4) {
277                g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
278                        (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
279            }
280        } finally {
281            // dispose Graphics2D object
282            g.dispose();
283        }
284    }
285
286    /*package*/ static void freeCaches() {
287        // FIXME
288        throw new UnsupportedOperationException();
289    }
290
291    /*package*/ static int initRaster(int nativeBitmapOrZero) {
292        if (nativeBitmapOrZero > 0) {
293            // get the Bitmap from the int
294            Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
295
296            // create a new Canvas_Delegate with the given bitmap and return its new native int.
297            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate.getImage());
298
299            return sManager.addDelegate(newDelegate);
300        } else {
301            // create a new Canvas_Delegate and return its new native int.
302            Canvas_Delegate newDelegate = new Canvas_Delegate();
303
304            return sManager.addDelegate(newDelegate);
305        }
306    }
307
308    /*package*/ static void native_setBitmap(int nativeCanvas, int bitmap) {
309        // get the delegate from the native int.
310        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
311        if (canvasDelegate == null) {
312            assert false;
313            return;
314        }
315
316        // get the delegate from the native int.
317        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
318        if (bitmapDelegate == null) {
319            assert false;
320            return;
321        }
322
323        canvasDelegate.setBitmap(bitmapDelegate.getImage());
324    }
325
326    /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds,
327                                               int paint, int layerFlags) {
328        // FIXME
329        throw new UnsupportedOperationException();
330    }
331
332    /*package*/ static int native_saveLayer(int nativeCanvas, float l,
333                                               float t, float r, float b,
334                                               int paint, int layerFlags) {
335        // FIXME
336        throw new UnsupportedOperationException();
337    }
338
339    /*package*/ static int native_saveLayerAlpha(int nativeCanvas,
340                                                    RectF bounds, int alpha,
341                                                    int layerFlags) {
342        // FIXME
343        throw new UnsupportedOperationException();
344    }
345
346    /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l,
347                                                    float t, float r, float b,
348                                                    int alpha, int layerFlags) {
349        // FIXME
350        throw new UnsupportedOperationException();
351    }
352
353
354    /*package*/ static void native_concat(int nCanvas, int nMatrix) {
355        // get the delegate from the native int.
356        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
357        if (canvasDelegate == null) {
358            assert false;
359            return;
360        }
361
362        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
363        if (matrixDelegate == null) {
364            assert false;
365            return;
366        }
367
368        // get the current top graphics2D object.
369        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
370
371        // get its current matrix
372        AffineTransform currentTx = snapshot.getTransform();
373        // get the AffineTransform of the given matrix
374        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
375
376        // combine them so that the given matrix is applied after.
377        currentTx.preConcatenate(matrixTx);
378
379        // give it to the graphics2D as a new matrix replacing all previous transform
380        snapshot.setTransform(currentTx);
381    }
382
383    /*package*/ static void native_setMatrix(int nCanvas, int nMatrix) {
384        // get the delegate from the native int.
385        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
386        if (canvasDelegate == null) {
387            assert false;
388        }
389
390        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
391        if (matrixDelegate == null) {
392            assert false;
393        }
394
395        // get the current top graphics2D object.
396        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
397
398        // get the AffineTransform of the given matrix
399        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
400
401        // give it to the graphics2D as a new matrix replacing all previous transform
402        snapshot.setTransform(matrixTx);
403
404        if (matrixDelegate.hasPerspective()) {
405            assert false;
406            Bridge.getLog().warning(null,
407                    "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
408                    "supports affine transformations in the Layout Preview.");
409        }
410    }
411
412    /*package*/ static boolean native_clipRect(int nCanvas,
413                                                  float left, float top,
414                                                  float right, float bottom,
415                                                  int regionOp) {
416
417        // get the delegate from the native int.
418        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
419        if (canvasDelegate == null) {
420            assert false;
421        }
422
423        return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
424    }
425
426    /*package*/ static boolean native_clipPath(int nativeCanvas,
427                                                  int nativePath,
428                                                  int regionOp) {
429        // FIXME
430        throw new UnsupportedOperationException();
431    }
432
433    /*package*/ static boolean native_clipRegion(int nativeCanvas,
434                                                    int nativeRegion,
435                                                    int regionOp) {
436        // FIXME
437        throw new UnsupportedOperationException();
438    }
439
440    /*package*/ static void nativeSetDrawFilter(int nativeCanvas,
441                                                   int nativeFilter) {
442        // FIXME
443        throw new UnsupportedOperationException();
444    }
445
446    /*package*/ static boolean native_getClipBounds(int nativeCanvas,
447                                                       Rect bounds) {
448        // get the delegate from the native int.
449        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
450        if (canvasDelegate == null) {
451            assert false;
452            return false;
453        }
454
455        Rectangle rect = canvasDelegate.getGcSnapshot().getClip().getBounds();
456        if (rect != null) {
457            bounds.left = rect.x;
458            bounds.top = rect.y;
459            bounds.right = rect.x + rect.width;
460            bounds.bottom = rect.y + rect.height;
461            return true;
462        }
463        return false;
464    }
465
466    /*package*/ static void native_getCTM(int canvas, int matrix) {
467        // FIXME
468        throw new UnsupportedOperationException();
469    }
470
471    /*package*/ static boolean native_quickReject(int nativeCanvas,
472                                                     RectF rect,
473                                                     int native_edgeType) {
474        // FIXME properly implement quickReject
475        return false;
476    }
477
478    /*package*/ static boolean native_quickReject(int nativeCanvas,
479                                                     int path,
480                                                     int native_edgeType) {
481        // FIXME properly implement quickReject
482        return false;
483    }
484
485    /*package*/ static boolean native_quickReject(int nativeCanvas,
486                                                     float left, float top,
487                                                     float right, float bottom,
488                                                     int native_edgeType) {
489        // FIXME properly implement quickReject
490        return false;
491    }
492
493    /*package*/ static void native_drawRGB(int nativeCanvas, int r, int g, int b) {
494        native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF),
495                PorterDuff.Mode.SRC_OVER.nativeInt);
496
497    }
498
499    /*package*/ static void native_drawARGB(int nativeCanvas, int a, int r, int g, int b) {
500        native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF),
501                PorterDuff.Mode.SRC_OVER.nativeInt);
502    }
503
504    /*package*/ static void native_drawColor(int nativeCanvas, int color) {
505        native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt);
506    }
507
508    /*package*/ static void native_drawColor(int nativeCanvas, int color, int mode) {
509        // get the delegate from the native int.
510        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
511        if (canvasDelegate == null) {
512            assert false;
513            return;
514        }
515
516        // get a new graphics context.
517        Graphics2D graphics = (Graphics2D)canvasDelegate.getGcSnapshot().create();
518        try {
519            // reset its transform just in case
520            graphics.setTransform(new AffineTransform());
521
522            // set the color
523            graphics.setColor(new Color(color, true /*alpha*/));
524
525            setModeInGraphics(graphics, mode);
526
527            graphics.fillRect(0, 0, canvasDelegate.mBufferedImage.getWidth(),
528                    canvasDelegate.mBufferedImage.getHeight());
529        } finally {
530            // dispose Graphics2D object
531            graphics.dispose();
532        }
533    }
534
535    /*package*/ static void native_drawPaint(int nativeCanvas, int paint) {
536        // FIXME
537        throw new UnsupportedOperationException();
538    }
539
540    /*package*/ static void native_drawLine(int nativeCanvas, float startX,
541                                               float startY, float stopX,
542                                               float stopY, int paint) {
543        // get the delegate from the native int.
544        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
545        if (canvasDelegate == null) {
546            assert false;
547            return;
548        }
549
550        // get the delegate from the native int.
551        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
552        if (paintDelegate == null) {
553            assert false;
554            return;
555        }
556
557        // get a Graphics2D object configured with the drawing parameters.
558        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
559
560        try {
561            g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
562        } finally {
563            // dispose Graphics2D object
564            g.dispose();
565        }
566    }
567
568    /*package*/ static void native_drawRect(int nativeCanvas, RectF rect,
569                                               int paint) {
570        native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint);
571    }
572
573    /*package*/ static void native_drawRect(int nativeCanvas, float left,
574                                               float top, float right,
575                                               float bottom, int paint) {
576        // get the delegate from the native int.
577        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
578        if (canvasDelegate == null) {
579            assert false;
580            return;
581        }
582
583        // get the delegate from the native int.
584        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
585        if (paintDelegate == null) {
586            assert false;
587            return;
588        }
589
590        if (right > left && bottom > top) {
591            // get a Graphics2D object configured with the drawing parameters.
592            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
593
594            try {
595                int style = paintDelegate.getStyle();
596
597                // draw
598                if (style == Paint.Style.FILL.nativeInt ||
599                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
600                    g.fillRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
601                }
602
603                if (style == Paint.Style.STROKE.nativeInt ||
604                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
605                    g.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
606                }
607            } finally {
608                // dispose Graphics2D object
609                g.dispose();
610            }
611        }
612    }
613
614    /*package*/ static void native_drawOval(int nativeCanvas, RectF oval,
615                                               int paint) {
616        // get the delegate from the native int.
617        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
618        if (canvasDelegate == null) {
619            assert false;
620            return;
621        }
622
623        // get the delegate from the native int.
624        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
625        if (paintDelegate == null) {
626            assert false;
627            return;
628        }
629
630        if (oval.right > oval.left && oval.bottom > oval.top) {
631            // get a Graphics2D object configured with the drawing parameters.
632            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
633
634            int style = paintDelegate.getStyle();
635
636            // draw
637            if (style == Paint.Style.FILL.nativeInt ||
638                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
639                g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
640            }
641
642            if (style == Paint.Style.STROKE.nativeInt ||
643                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
644                g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
645            }
646
647            // dispose Graphics2D object
648            g.dispose();
649        }
650    }
651
652    /*package*/ static void native_drawCircle(int nativeCanvas, float cx,
653                                                 float cy, float radius,
654                                                 int paint) {
655        native_drawOval(nativeCanvas,
656                new RectF(cx - radius, cy - radius, radius*2, radius*2),
657                paint);
658    }
659
660    /*package*/ static void native_drawArc(int nativeCanvas, RectF oval,
661                                              float startAngle, float sweep,
662                                              boolean useCenter, int paint) {
663        // FIXME
664        throw new UnsupportedOperationException();
665    }
666
667    /*package*/ static void native_drawRoundRect(int nativeCanvas,
668                                                    RectF rect, float rx,
669                                                    float ry, int paint) {
670        // get the delegate from the native int.
671        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
672        if (canvasDelegate == null) {
673            assert false;
674            return;
675        }
676
677        // get the delegate from the native int.
678        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
679        if (paintDelegate == null) {
680            assert false;
681            return;
682        }
683
684        if (rect.right > rect.left && rect.bottom > rect.top) {
685            // get a Graphics2D object configured with the drawing parameters.
686            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
687
688            int style = paintDelegate.getStyle();
689
690            // draw
691            if (style == Paint.Style.FILL.nativeInt ||
692                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
693                g.fillRoundRect(
694                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
695                        (int)rx, (int)ry);
696            }
697
698            if (style == Paint.Style.STROKE.nativeInt ||
699                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
700                g.drawRoundRect(
701                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
702                        (int)rx, (int)ry);
703            }
704
705            // dispose Graphics2D object
706            g.dispose();
707        }
708    }
709
710    /*package*/ static void native_drawPath(int nativeCanvas, int path,
711                                               int paint) {
712        // FIXME
713        throw new UnsupportedOperationException();
714    }
715
716    /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap,
717                                                 float left, float top,
718                                                 int nativePaintOrZero,
719                                                 int canvasDensity,
720                                                 int screenDensity,
721                                                 int bitmapDensity) {
722        // FIXME
723        throw new UnsupportedOperationException();
724    }
725
726    /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap,
727                                                 Rect src, RectF dst,
728                                                 int nativePaintOrZero,
729                                                 int screenDensity,
730                                                 int bitmapDensity) {
731        // get the delegate from the native int.
732        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
733        if (bitmapDelegate == null) {
734            assert false;
735            return;
736        }
737
738        BufferedImage image = bitmapDelegate.getImage();
739
740        if (src == null) {
741            drawBitmap(nativeCanvas, image, nativePaintOrZero,
742                    0, 0, image.getWidth(), image.getHeight(),
743                    (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
744        } else {
745            drawBitmap(nativeCanvas, image, nativePaintOrZero,
746                    src.left, src.top, src.width(), src.height(),
747                    (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
748        }
749    }
750
751    /*package*/ static void native_drawBitmap(int nativeCanvas, int bitmap,
752                                                 Rect src, Rect dst,
753                                                 int nativePaintOrZero,
754                                                 int screenDensity,
755                                                 int bitmapDensity) {
756        // get the delegate from the native int.
757        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
758        if (bitmapDelegate == null) {
759            assert false;
760            return;
761        }
762
763        BufferedImage image = bitmapDelegate.getImage();
764
765        if (src == null) {
766            drawBitmap(nativeCanvas, image, nativePaintOrZero,
767                    0, 0, image.getWidth(), image.getHeight(),
768                    dst.left, dst.top, dst.right, dst.bottom);
769        } else {
770            drawBitmap(nativeCanvas, image, nativePaintOrZero,
771                    src.left, src.top, src.width(), src.height(),
772                    dst.left, dst.top, dst.right, dst.bottom);
773        }
774    }
775
776    /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors,
777                                                int offset, int stride, float x,
778                                                 float y, int width, int height,
779                                                 boolean hasAlpha,
780                                                 int nativePaintOrZero) {
781        // FIXME
782        throw new UnsupportedOperationException();
783    }
784
785    /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap,
786                                                      int nMatrix, int nPaint) {
787        // FIXME
788        throw new UnsupportedOperationException();
789    }
790
791    /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap,
792                                                    int meshWidth, int meshHeight,
793                                                    float[] verts, int vertOffset,
794                                                    int[] colors, int colorOffset, int nPaint) {
795        // FIXME
796        throw new UnsupportedOperationException();
797    }
798
799    /*package*/ static void nativeDrawVertices(int nCanvas, int mode, int n,
800                   float[] verts, int vertOffset, float[] texs, int texOffset,
801                   int[] colors, int colorOffset, short[] indices,
802                   int indexOffset, int indexCount, int nPaint) {
803        // FIXME
804        throw new UnsupportedOperationException();
805    }
806
807    /*package*/ static void native_drawText(int nativeCanvas, char[] text,
808                                               int index, int count, float x,
809                                               float y, int flags, int paint) {
810        // WARNING: the logic in this method is similar to Paint.measureText.
811        // Any change to this method should be reflected in Paint.measureText
812
813        // get the delegate from the native int.
814        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
815        if (canvasDelegate == null) {
816            assert false;
817            return;
818        }
819
820        // get the delegate from the native int.
821        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
822        if (paintDelegate == null) {
823            assert false;
824            return;
825        }
826
827        Graphics2D g = (Graphics2D) canvasDelegate.createCustomGraphics(paintDelegate);
828        try {
829            // Paint.TextAlign indicates how the text is positioned relative to X.
830            // LEFT is the default and there's nothing to do.
831            if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
832                float m = paintDelegate.measureText(text, index, count);
833                if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
834                    x -= m / 2;
835                } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
836                    x -= m;
837                }
838            }
839
840            List<FontInfo> fonts = paintDelegate.getFonts();
841
842            if (fonts.size() > 0) {
843                FontInfo mainFont = fonts.get(0);
844                int i = index;
845                int lastIndex = index + count;
846                while (i < lastIndex) {
847                    // always start with the main font.
848                    int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
849                    if (upTo == -1) {
850                        // draw all the rest and exit.
851                        g.setFont(mainFont.mFont);
852                        g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
853                        return;
854                    } else if (upTo > 0) {
855                        // draw what's possible
856                        g.setFont(mainFont.mFont);
857                        g.drawChars(text, i, upTo - i, (int)x, (int)y);
858
859                        // compute the width that was drawn to increase x
860                        x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
861
862                        // move index to the first non displayed char.
863                        i = upTo;
864
865                        // don't call continue at this point. Since it is certain the main font
866                        // cannot display the font a index upTo (now ==i), we move on to the
867                        // fallback fonts directly.
868                    }
869
870                    // no char supported, attempt to read the next char(s) with the
871                    // fallback font. In this case we only test the first character
872                    // and then go back to test with the main font.
873                    // Special test for 2-char characters.
874                    boolean foundFont = false;
875                    for (int f = 1 ; f < fonts.size() ; f++) {
876                        FontInfo fontInfo = fonts.get(f);
877
878                        // need to check that the font can display the character. We test
879                        // differently if the char is a high surrogate.
880                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
881                        upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
882                        if (upTo == -1) {
883                            // draw that char
884                            g.setFont(fontInfo.mFont);
885                            g.drawChars(text, i, charCount, (int)x, (int)y);
886
887                            // update x
888                            x += fontInfo.mMetrics.charsWidth(text, i, charCount);
889
890                            // update the index in the text, and move on
891                            i += charCount;
892                            foundFont = true;
893                            break;
894
895                        }
896                    }
897
898                    // in case no font can display the char, display it with the main font.
899                    // (it'll put a square probably)
900                    if (foundFont == false) {
901                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
902
903                        g.setFont(mainFont.mFont);
904                        g.drawChars(text, i, charCount, (int)x, (int)y);
905
906                        // measure it to advance x
907                        x += mainFont.mMetrics.charsWidth(text, i, charCount);
908
909                        // and move to the next chars.
910                        i += charCount;
911                    }
912                }
913            }
914        } finally {
915            g.dispose();
916        }
917    }
918
919    /*package*/ static void native_drawText(int nativeCanvas, String text,
920                                               int start, int end, float x,
921                                               float y, int flags, int paint) {
922        int count = end - start;
923        char[] buffer = TemporaryBuffer.obtain(count);
924        TextUtils.getChars(text, start, end, buffer, 0);
925
926        native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
927    }
928
929    /*package*/ static void native_drawTextRun(int nativeCanvas, String text,
930            int start, int end, int contextStart, int contextEnd,
931            float x, float y, int flags, int paint) {
932        int count = end - start;
933        char[] buffer = TemporaryBuffer.obtain(count);
934        TextUtils.getChars(text, start, end, buffer, 0);
935
936        native_drawText(nativeCanvas, buffer, start, end, x, y, flags, paint);
937    }
938
939    /*package*/ static void native_drawTextRun(int nativeCanvas, char[] text,
940            int start, int count, int contextStart, int contextCount,
941            float x, float y, int flags, int paint) {
942        native_drawText(nativeCanvas, text, 0, count, x, y, flags, paint);
943    }
944
945    /*package*/ static void native_drawPosText(int nativeCanvas,
946                                                  char[] text, int index,
947                                                  int count, float[] pos,
948                                                  int paint) {
949        // FIXME
950        throw new UnsupportedOperationException();
951    }
952
953    /*package*/ static void native_drawPosText(int nativeCanvas,
954                                                  String text, float[] pos,
955                                                  int paint) {
956        // FIXME
957        throw new UnsupportedOperationException();
958    }
959
960    /*package*/ static void native_drawTextOnPath(int nativeCanvas,
961                                                     char[] text, int index,
962                                                     int count, int path,
963                                                     float hOffset,
964                                                     float vOffset, int bidiFlags,
965                                                     int paint) {
966        // FIXME
967        throw new UnsupportedOperationException();
968    }
969
970    /*package*/ static void native_drawTextOnPath(int nativeCanvas,
971                                                     String text, int path,
972                                                     float hOffset,
973                                                     float vOffset,
974                                                     int flags, int paint) {
975        // FIXME
976        throw new UnsupportedOperationException();
977    }
978
979    /*package*/ static void native_drawPicture(int nativeCanvas,
980                                                  int nativePicture) {
981        // FIXME
982        throw new UnsupportedOperationException();
983    }
984
985    /*package*/ static void finalizer(int nativeCanvas) {
986        // get the delegate from the native int so that it can be disposed.
987        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
988        if (canvasDelegate == null) {
989            assert false;
990            return;
991        }
992
993        canvasDelegate.dispose();
994
995        // remove it from the manager.
996        sManager.removeDelegate(nativeCanvas);
997    }
998
999    // ---- Private delegate/helper methods ----
1000
1001    private Canvas_Delegate(BufferedImage image) {
1002        setBitmap(image);
1003    }
1004
1005    private Canvas_Delegate() {
1006    }
1007
1008    /**
1009     * Disposes of the {@link Graphics2D} stack.
1010     */
1011    private void dispose() {
1012        mSnapshot.dispose();
1013    }
1014
1015    private int save(int saveFlags) {
1016        // get the current save count
1017        int count = mSnapshot.size();
1018
1019        // create a new snapshot and add it to the stack
1020        mSnapshot = new GcSnapshot(mSnapshot, saveFlags);
1021
1022        // return the old save count
1023        return count;
1024    }
1025
1026    /**
1027     * Restores the {@link GcSnapshot} to <var>saveCount</var>
1028     * @param saveCount the saveCount
1029     */
1030    private void restoreTo(int saveCount) {
1031        mSnapshot = mSnapshot.restoreTo(saveCount);
1032    }
1033
1034    /**
1035     * Restores the {@link GcSnapshot} to <var>saveCount</var>
1036     * @param saveCount the saveCount
1037     */
1038    private void restore() {
1039        mSnapshot = mSnapshot.restore();
1040    }
1041
1042    private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
1043        return mSnapshot.clipRect(left, top, right, bottom, regionOp);
1044    }
1045
1046    private void setBitmap(BufferedImage image) {
1047        mBufferedImage = image;
1048        assert mSnapshot.size() == 1;
1049        mSnapshot.setGraphics2D(mBufferedImage.createGraphics());
1050    }
1051
1052    /**
1053     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
1054     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
1055     */
1056    /*package*/ Graphics2D createCustomGraphics(Paint_Delegate paint) {
1057        // make new one
1058        Graphics2D g = getGcSnapshot().create();
1059
1060        // configure it
1061
1062        if (paint.isAntiAliased()) {
1063            g.setRenderingHint(
1064                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1065            g.setRenderingHint(
1066                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
1067        }
1068
1069        boolean useColorPaint = true;
1070
1071        // get the shader first, as it'll replace the color if it can be used it.
1072        Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(paint.getShader());
1073        if (shaderDelegate != null) {
1074            java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
1075            assert shaderPaint != null;
1076            if (shaderPaint != null) {
1077                g.setPaint(shaderPaint);
1078                useColorPaint = false;
1079            } else {
1080                Bridge.getLog().warning(null,
1081                        String.format(
1082                            "Shader '%1$s' is not supported in the Layout Preview.",
1083                            shaderDelegate.getClass().getCanonicalName()));
1084            }
1085        }
1086
1087        if (useColorPaint) {
1088            g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
1089        }
1090
1091        int style = paint.getStyle();
1092        if (style == Paint.Style.STROKE.nativeInt ||
1093                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
1094
1095            PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(
1096                    paint.getPathEffect());
1097
1098            if (effectDelegate instanceof DashPathEffect_Delegate) {
1099                DashPathEffect_Delegate dpe = (DashPathEffect_Delegate)effectDelegate;
1100                g.setStroke(new BasicStroke(
1101                        paint.getStrokeWidth(),
1102                        paint.getJavaCap(),
1103                        paint.getJavaJoin(),
1104                        paint.getStrokeMiter(),
1105                        dpe.getIntervals(),
1106                        dpe.getPhase()));
1107            } else {
1108                g.setStroke(new BasicStroke(
1109                        paint.getStrokeWidth(),
1110                        paint.getJavaCap(),
1111                        paint.getJavaJoin(),
1112                        paint.getStrokeMiter()));
1113            }
1114        }
1115
1116        Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
1117        if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
1118            int mode = ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
1119
1120            setModeInGraphics(g, mode);
1121        } else {
1122            // default mode is src_over
1123            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
1124
1125            // if xfermode wasn't null, then it's something we don't support. log it.
1126            if (xfermodeDelegate != null) {
1127                assert false;
1128                Bridge.getLog().warning(null,
1129                        String.format(
1130                            "Xfermode '%1$s' is not supported in the Layout Preview.",
1131                            xfermodeDelegate.getClass().getCanonicalName()));
1132            }
1133        }
1134
1135        return g;
1136    }
1137
1138    private static void setModeInGraphics(Graphics2D g, int mode) {
1139        for (PorterDuff.Mode m : PorterDuff.Mode.values()) {
1140            if (m.nativeInt == mode) {
1141                setModeInGraphics(g, m);
1142                return;
1143            }
1144        }
1145    }
1146
1147    private static void setModeInGraphics(Graphics2D g, PorterDuff.Mode mode) {
1148        switch (mode) {
1149            case CLEAR:
1150                g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 1.0f /*alpha*/));
1151                break;
1152            case DARKEN:
1153                break;
1154            case DST:
1155                g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST, 1.0f /*alpha*/));
1156                break;
1157            case DST_ATOP:
1158                g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP, 1.0f /*alpha*/));
1159                break;
1160            case DST_IN:
1161                g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0f /*alpha*/));
1162                break;
1163            case DST_OUT:
1164                g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OUT, 1.0f /*alpha*/));
1165                break;
1166            case DST_OVER:
1167                g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 1.0f /*alpha*/));
1168                break;
1169            case LIGHTEN:
1170                break;
1171            case MULTIPLY:
1172                break;
1173            case SCREEN:
1174                break;
1175            case SRC:
1176                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f /*alpha*/));
1177                break;
1178            case SRC_ATOP:
1179                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f /*alpha*/));
1180                break;
1181            case SRC_IN:
1182                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 1.0f /*alpha*/));
1183                break;
1184            case SRC_OUT:
1185                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT, 1.0f /*alpha*/));
1186                break;
1187            case SRC_OVER:
1188                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f /*alpha*/));
1189                break;
1190            case XOR:
1191                g.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR, 1.0f /*alpha*/));
1192                break;
1193        }
1194    }
1195
1196
1197    private static void drawBitmap(
1198            int nativeCanvas,
1199            BufferedImage image,
1200            int nativePaintOrZero,
1201            int sleft, int stop, int sright, int sbottom,
1202            int dleft, int dtop, int dright, int dbottom) {
1203        // get the delegate from the native int.
1204        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
1205        if (canvasDelegate == null) {
1206            assert false;
1207            return;
1208        }
1209
1210        // get the delegate from the native int.
1211        Paint_Delegate paintDelegate = null;
1212        if (nativePaintOrZero > 0) {
1213            paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
1214            if (paintDelegate == null) {
1215                assert false;
1216                return;
1217            }
1218        }
1219
1220        drawBitmap(canvasDelegate, image, paintDelegate,
1221                sleft, stop, sright, sbottom,
1222                dleft, dtop, dright, dbottom);
1223    }
1224
1225    private static void drawBitmap(
1226            Canvas_Delegate canvasDelegate,
1227            BufferedImage image,
1228            Paint_Delegate paintDelegate,
1229            int sleft, int stop, int sright, int sbottom,
1230            int dleft, int dtop, int dright, int dbottom) {
1231
1232        Graphics2D g = canvasDelegate.getGcSnapshot().create();
1233        try {
1234            if (paintDelegate != null && paintDelegate.isFilterBitmap()) {
1235                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1236                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
1237            }
1238
1239            g.drawImage(image, dleft, dtop, dright, dbottom,
1240                    sleft, stop, sright, sbottom, null);
1241        } finally {
1242            g.dispose();
1243        }
1244    }
1245}
1246
1247