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