Home | History | Annotate | Download | only in graphics
      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 
     17 package android.graphics;
     18 
     19 import com.android.layoutlib.api.ILayoutLog;
     20 
     21 import android.graphics.DrawFilter;
     22 import android.graphics.Picture;
     23 import android.graphics.PorterDuff;
     24 import android.graphics.Rect;
     25 import android.graphics.RectF;
     26 import android.graphics.Region;
     27 import android.graphics.Xfermode;
     28 import android.graphics.Paint.Align;
     29 import android.graphics.Paint.FontInfo;
     30 import android.graphics.Paint.Style;
     31 import android.graphics.Region.Op;
     32 
     33 import java.awt.AlphaComposite;
     34 import java.awt.BasicStroke;
     35 import java.awt.Color;
     36 import java.awt.Composite;
     37 import java.awt.Graphics2D;
     38 import java.awt.Rectangle;
     39 import java.awt.RenderingHints;
     40 import java.awt.geom.AffineTransform;
     41 import java.awt.image.BufferedImage;
     42 import java.util.List;
     43 import java.util.Stack;
     44 
     45 import javax.microedition.khronos.opengles.GL;
     46 
     47 /**
     48  * Re-implementation of the Canvas, 100% in java on top of a BufferedImage.
     49  */
     50 public 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