All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.prism.j2d.J2DPrismGraphics Maven / Gradle / Ivy

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.j2d;

import com.sun.glass.ui.Screen;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.font.CompositeGlyphMapper;
import com.sun.javafx.font.CompositeStrike;
import com.sun.javafx.font.FontResource;
import com.sun.javafx.font.FontStrike;
import com.sun.javafx.font.Metrics;
import com.sun.javafx.scene.text.GlyphList;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.Shape;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.GeneralTransform3D;
import com.sun.prism.BasicStroke;
import com.sun.prism.CompositeMode;
import com.sun.prism.MaskTextureGraphics;
import com.sun.prism.RTTexture;
import com.sun.prism.ReadbackGraphics;
import com.sun.prism.RenderTarget;
import com.sun.prism.ResourceFactory;
import com.sun.prism.Texture;
import com.sun.prism.Texture.WrapMode;
import com.sun.prism.camera.PrismCameraImpl;
import com.sun.prism.camera.PrismDefaultCamera;
import com.sun.prism.paint.Color;
import com.sun.prism.paint.Gradient;
import com.sun.prism.paint.ImagePattern;
import com.sun.prism.paint.LinearGradient;
import com.sun.prism.paint.Paint;
import com.sun.prism.paint.RadialGradient;
import com.sun.prism.paint.Stop;

import static java.awt.RenderingHints.*;
import com.sun.prism.j2d.paint.MultipleGradientPaint.ColorSpaceType;
import com.sun.prism.j2d.paint.RadialGradientPaint;
import java.awt.LinearGradientPaint;
import java.awt.font.GlyphVector;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class J2DPrismGraphics
    // Do not subclass BaseGraphics without fixing drawTextureVO below...
    implements ReadbackGraphics, MaskTextureGraphics
    // Do not implement RectShadowGraphics without fixing RT-15016 (note that
    // BaseGraphics implements RectShadowGraphics).
{
    static {
        // Assuming direct translation of BasicStroke enums:
        assert(com.sun.prism.BasicStroke.CAP_BUTT == java.awt.BasicStroke.CAP_BUTT);
        assert(com.sun.prism.BasicStroke.CAP_ROUND == java.awt.BasicStroke.CAP_ROUND);
        assert(com.sun.prism.BasicStroke.CAP_SQUARE == java.awt.BasicStroke.CAP_SQUARE);
        assert(com.sun.prism.BasicStroke.JOIN_BEVEL == java.awt.BasicStroke.JOIN_BEVEL);
        assert(com.sun.prism.BasicStroke.JOIN_MITER == java.awt.BasicStroke.JOIN_MITER);
        assert(com.sun.prism.BasicStroke.JOIN_ROUND == java.awt.BasicStroke.JOIN_ROUND);
        // Assuming direct translation of PathIterator enums:
        assert(com.sun.javafx.geom.PathIterator.WIND_EVEN_ODD == java.awt.geom.PathIterator.WIND_EVEN_ODD);
        assert(com.sun.javafx.geom.PathIterator.WIND_NON_ZERO == java.awt.geom.PathIterator.WIND_NON_ZERO);
        assert(com.sun.javafx.geom.PathIterator.SEG_MOVETO == java.awt.geom.PathIterator.SEG_MOVETO);
        assert(com.sun.javafx.geom.PathIterator.SEG_LINETO == java.awt.geom.PathIterator.SEG_LINETO);
        assert(com.sun.javafx.geom.PathIterator.SEG_QUADTO == java.awt.geom.PathIterator.SEG_QUADTO);
        assert(com.sun.javafx.geom.PathIterator.SEG_CUBICTO == java.awt.geom.PathIterator.SEG_CUBICTO);
        assert(com.sun.javafx.geom.PathIterator.SEG_CLOSE == java.awt.geom.PathIterator.SEG_CLOSE);
    }
    static final LinearGradientPaint.CycleMethod LGP_CYCLE_METHODS[] = {
        LinearGradientPaint.CycleMethod.NO_CYCLE,
        LinearGradientPaint.CycleMethod.REFLECT,
        LinearGradientPaint.CycleMethod.REPEAT,
    };
    static final RadialGradientPaint.CycleMethod RGP_CYCLE_METHODS[] = {
        RadialGradientPaint.CycleMethod.NO_CYCLE,
        RadialGradientPaint.CycleMethod.REFLECT,
        RadialGradientPaint.CycleMethod.REPEAT,
    };

    private static final PrismDefaultCamera DEFAULT_CAMERA = PrismDefaultCamera.getInstance();
    private static final BasicStroke DEFAULT_STROKE =
        new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f);
    private static final Paint DEFAULT_PAINT = Color.WHITE;
    static java.awt.geom.AffineTransform J2D_IDENTITY =
        new java.awt.geom.AffineTransform();
    private GeneralTransform3D pvTx;
    private int clipRectIndex;
    private boolean hasPreCullingBits = false;

    static java.awt.Color toJ2DColor(Color c) {
        return new java.awt.Color(c.getRed(),
                                  c.getGreen(),
                                  c.getBlue(),
                                  c.getAlpha());
    }

    /*
     * Ensure that no fractions are equal
     *
     * Note that the J2D objects reject equal fractions, but the FX versions
     * allow them.
     *
     * The FX version treats values with equal fractions such that as you
     * approach the fractional value from below it interpolates to the
     * first color associated with that fraction and as you interpolate
     * away from it from above it interpolates the last such color.
     *
     * To get the J2D version to exhibit the FX behavior we collapse all
     * adjacent fractional values into a pair of values that are stored
     * with a pair of immediately adjacent floating point values.  This way
     * they have unique fractions, but no fractional value can be generated
     * which fits between them.  Yet, as you approach from below it will
     * interpolate to the first of the pair of colors and as you move away
     * above it, the second value will take precedence for interpolation.
     *
     * Math.ulp() is used to generate an "immediately adjacent fp value".
     */
    static int fixFractions(float fractions[], java.awt.Color colors[]) {
        float fprev = fractions[0];
        int i = 1;  // index of next incoming color/fractions we will examine
        int n = 1;  // index of next outgoing color/fraction we will store
        while (i < fractions.length) {
            float f = fractions[i];
            java.awt.Color c = colors[i++];
            if (f <= fprev) {
                // If we find any duplicates after we reach 1.0 we can
                // just ignore the rest of the array.  Not only is there
                // no more "fraction room" to assign them to, but we will
                // never generate a fraction >1.0 to access them anyway
                if (f >= 1.0f) break;
                // Find all fractions that are either fprev or fprev+ulp
                // and collapse them into two entries, the first at fprev
                // which is already stored, and the last matching entry
                // will be stored with fraction fprev+ulp
                f = fprev + Math.ulp(fprev);
                while (i < fractions.length) {
                    if (fractions[i] > f) break;
                    // We continue to remember the color of the last
                    // "matching" entry so it can be stored below
                    c = colors[i++];
                }
            }
            fractions[n] = fprev = f;
            colors[n++] = c;
        }
        return n;
    }

    java.awt.Paint toJ2DPaint(Paint p, java.awt.geom.Rectangle2D b) {
        if (p instanceof Color) {
            return toJ2DColor((Color) p);
        } else if (p instanceof Gradient) {
            Gradient g = (Gradient) p;
            if (g.isProportional()) {
                if (b == null) {
                    return null;
                }
            }
            List stops = g.getStops();
            int n = stops.size();
            float fractions[] = new float[n];
            java.awt.Color colors[] = new java.awt.Color[n];
            float prevf = -1f;
            boolean needsFix = false;
            for (int i = 0; i < n; i++) {
                Stop stop = stops.get(i);
                float f = stop.getOffset();
                needsFix = (needsFix || f <= prevf);
                fractions[i] = prevf = f;
                colors[i] = toJ2DColor(stop.getColor());
            }
            if (needsFix) {
                n = fixFractions(fractions, colors);
                if (n < fractions.length) {
                    float newf[] = new float[n];
                    System.arraycopy(fractions, 0, newf, 0, n);
                    fractions = newf;
                    java.awt.Color newc[] = new java.awt.Color[n];
                    System.arraycopy(colors, 0, newc, 0, n);
                    colors = newc;
                }
            }
            if (g instanceof LinearGradient) {
                LinearGradient lg = (LinearGradient) p;
                float x1 = lg.getX1();
                float y1 = lg.getY1();
                float x2 = lg.getX2();
                float y2 = lg.getY2();
                if (g.isProportional()) {
                    float x = (float) b.getX();
                    float y = (float) b.getY();
                    float w = (float) b.getWidth();
                    float h = (float) b.getHeight();
                    x1 = x + w * x1;
                    y1 = y + h * y1;
                    x2 = x + w * x2;
                    y2 = y + h * y2;
                }
                if (x1 == x2 && y1 == y1) {
                    x1 -= .0001f;
                    x2 += .0001f;
                }
                java.awt.geom.Point2D p1 =
                    new java.awt.geom.Point2D.Float(x1, y1);
                java.awt.geom.Point2D p2 =
                    new java.awt.geom.Point2D.Float(x2, y2);
                LinearGradientPaint.CycleMethod method =
                    LGP_CYCLE_METHODS[g.getSpreadMethod()];
                return new LinearGradientPaint(p1, p2, fractions, colors, method);
            } else if (g instanceof RadialGradient) {
                RadialGradient rg = (RadialGradient) g;
                float cx = rg.getCenterX();
                float cy = rg.getCenterY();
                float r = rg.getRadius();
                double fa = Math.toRadians(rg.getFocusAngle());
                float fd = rg.getFocusDistance();
                java.awt.geom.AffineTransform at = J2D_IDENTITY;
                if (g.isProportional()) {
                    float x = (float) b.getX();
                    float y = (float) b.getY();
                    float w = (float) b.getWidth();
                    float h = (float) b.getHeight();
                    float dim = Math.min(w, h);
                    float bcx = x + w * 0.5f;
                    float bcy = y + h * 0.5f;
                    cx = bcx + (cx - 0.5f) * dim;
                    cy = bcy + (cy - 0.5f) * dim;
                    r *= dim;
                    if (w != h && w != 0.0 && h != 0.0) {
                        at = java.awt.geom.AffineTransform.getTranslateInstance(bcx, bcy);
                        at.scale(w / dim, h / dim);
                        at.translate(-bcx, -bcy);
                    }
                }
                java.awt.geom.Point2D center =
                    new java.awt.geom.Point2D.Float(cx, cy);
                float fx = (float) (cx + fd * r * Math.cos(fa));
                float fy = (float) (cy + fd * r * Math.sin(fa));
                java.awt.geom.Point2D focus =
                    new java.awt.geom.Point2D.Float(fx, fy);
                RadialGradientPaint.CycleMethod method =
                    RGP_CYCLE_METHODS[g.getSpreadMethod()];
                return new RadialGradientPaint(center, r, focus, fractions, colors,
                                               method, ColorSpaceType.SRGB, at);
            }
        } else if (p instanceof ImagePattern) {
            ImagePattern imgpat = (ImagePattern) p;
            float x = imgpat.getX();
            float y = imgpat.getY();
            float w = imgpat.getWidth();
            float h = imgpat.getHeight();
            if (p.isProportional()) {
                if (b == null) {
                    return null;
                }
                float bx = (float) b.getX();
                float by = (float) b.getY();
                float bw = (float) b.getWidth();
                float bh = (float) b.getHeight();
                w += x;
                h += y;
                x = bx + x * bw;
                y = by + y * bh;
                w = bx + w * bw;
                h = by + h * bh;
                w -= x;
                h -= y;
            }
            Texture tex =
                getResourceFactory().getCachedTexture(imgpat.getImage(), WrapMode.REPEAT);
            java.awt.image.BufferedImage bimg = ((J2DTexture) tex).getBufferedImage();
            tex.unlock();
            return new java.awt.TexturePaint(bimg, tmpRect(x, y, w, h));
        }
        throw new UnsupportedOperationException("Paint "+p+" not supported yet.");
    }

    static java.awt.Stroke toJ2DStroke(BasicStroke stroke) {
        float lineWidth = stroke.getLineWidth();
        int type = stroke.getType();
        if (type != BasicStroke.TYPE_CENTERED) {
            lineWidth *= 2;
        }
        java.awt.BasicStroke bs =
                new java.awt.BasicStroke(lineWidth,
                                         stroke.getEndCap(),
                                         stroke.getLineJoin(),
                                         stroke.getMiterLimit(),
                                         stroke.getDashArray(),
                                         stroke.getDashPhase());
        if (type == BasicStroke.TYPE_INNER) {
            return new InnerStroke(bs);
        } else if (type == BasicStroke.TYPE_OUTER) {
            return new OuterStroke(bs);
        } else {
            return bs;
        }
    }

    private static ConcurrentHashMap>
        fontMap = new ConcurrentHashMap>();
    private static volatile int cleared = 0;

    private static java.awt.Font toJ2DFont(FontStrike strike) {
        FontResource fr = strike.getFontResource();
        java.awt.Font j2dfont;
        Object peer = fr.getPeer();
        if (peer == null && fr.isEmbeddedFont()) {
            J2DFontFactory.registerFont(fr);
            peer = fr.getPeer();
        }
        if (peer != null && peer instanceof java.awt.Font) {
            j2dfont = (java.awt.Font)peer;
        } else {
            if (PlatformUtil.isMac()) {
                // Looking up J2D fonts via full name is not reliable on the
                // Mac, however using the PostScript font name is. The likely
                // cause is Mac platform internals heavy reliance on PostScript
                // names for font identification.
                String psName = fr.getPSName();
                // dummy size
                j2dfont = new java.awt.Font(psName, java.awt.Font.PLAIN, 12);

                // REMIND: Due to bugs in j2d font lookup, these two workarounds
                // are required to ensure the correct font is used. Once fixed
                // in the jdk these workarounds should be removed.
                if (!j2dfont.getPSName().equals(psName)) {
                    // 1. Lookup font via family and style. This covers the
                    // case when the J2D PostScript name does not match psName
                    // in font file. For example "HelveticaCYBold" has the
                    // psName "HelveticaCY-Bold" in j2d.
                    int style = fr.isBold() ? java.awt.Font.BOLD : 0;
                    style = style | (fr.isItalic() ? java.awt.Font.ITALIC : 0);
                    j2dfont = new java.awt.Font(fr.getFamilyName(), style, 12);

                    if(!j2dfont.getPSName().equals(psName)) {
                        // 2. J2D seems to be unable to find a few fonts where
                        // psName == familyName.  Workaround is an exhaustive
                        // search of all fonts.
                        java.awt.Font[] allj2dFonts =
                                java.awt.GraphicsEnvironment.
                                getLocalGraphicsEnvironment().getAllFonts();
                        for (java.awt.Font f : allj2dFonts) {
                            if (f.getPSName().equals(psName)) {
                                j2dfont = f;
                                break;
                            }
                        }
                    }
                }
            } else {
                // dummy size
                j2dfont = new java.awt.Font(fr.getFullName(),
                                            java.awt.Font.PLAIN, 12);
            }

            // Adding j2dfont as peer is OK since fr is a decomposed
            // FontResource. Thus preventing font lookup next time we render.
            fr.setPeer(j2dfont);
        }
        // deriveFont(...) still has a bug and will cause #2 problem to occur
        j2dfont = j2dfont.deriveFont(strike.getSize()); // exact float font size
        java.awt.Font compFont = null;
        WeakReference ref = fontMap.get(j2dfont);
        if (ref != null) {
            compFont = ref.get();
            if (compFont == null) {
                cleared++;
            }
        }
        if (compFont == null) {
            if (fontMap.size() > 100 && cleared > 10) { // purge the map.
                for (java.awt.Font key : fontMap.keySet()) {
                    ref = fontMap.get(key);
                    if (ref == null || ref.get() == null) {
                        fontMap.remove(key);
                    }
                }
                cleared = 0;
            }
            compFont = J2DFontFactory.getCompositeFont(j2dfont);
            ref = new WeakReference(compFont);
            fontMap.put(j2dfont, ref);
        }
        return compFont;
    }

    public static java.awt.geom.AffineTransform
        toJ2DTransform(BaseTransform t)
    {
        return new java.awt.geom.AffineTransform(t.getMxx(), t.getMyx(),
                                                 t.getMxy(), t.getMyy(),
                                                 t.getMxt(), t.getMyt());
    }

    private static java.awt.geom.AffineTransform tmpAT =
        new java.awt.geom.AffineTransform();
    static java.awt.geom.AffineTransform tmpJ2DTransform(BaseTransform t)
    {
        tmpAT.setTransform(t.getMxx(), t.getMyx(),
                           t.getMxy(), t.getMyy(),
                           t.getMxt(), t.getMyt());
        return tmpAT;
    }

    static BaseTransform toPrTransform(java.awt.geom.AffineTransform t)
    {
        return BaseTransform.getInstance(t.getScaleX(), t.getShearY(),
                                         t.getShearX(), t.getScaleY(),
                                         t.getTranslateX(), t.getTranslateY());
    }

    static Rectangle toPrRect(java.awt.Rectangle r)
    {
        return new Rectangle(r.x, r.y, r.width, r.height);
    }

    private static java.awt.geom.Path2D tmpQuadShape =
        new java.awt.geom.Path2D.Float();
    private static java.awt.Shape tmpQuad(float x1, float y1,
                                          float x2, float y2)
    {
        tmpQuadShape.reset();
        tmpQuadShape.moveTo(x1, y1);
        tmpQuadShape.lineTo(x2, y1);
        tmpQuadShape.lineTo(x2, y2);
        tmpQuadShape.lineTo(x1, y2);
        tmpQuadShape.closePath();
        return tmpQuadShape;
    }

    private static java.awt.geom.Rectangle2D.Float tmpRect =
        new java.awt.geom.Rectangle2D.Float();
    private static java.awt.geom.Rectangle2D tmpRect(float x, float y, float w, float h) {
        tmpRect.setRect(x, y, w, h);
        return tmpRect;
    }

    private static java.awt.geom.Ellipse2D tmpEllipse =
        new java.awt.geom.Ellipse2D.Float();
    private static java.awt.Shape tmpEllipse(float x, float y, float w, float h) {
        tmpEllipse.setFrame(x, y, w, h);
        return tmpEllipse;
    }

    private static java.awt.geom.RoundRectangle2D tmpRRect =
        new java.awt.geom.RoundRectangle2D.Float();
    private static java.awt.Shape tmpRRect(float x, float y, float w, float h,
                                           float aw, float ah)
    {
        tmpRRect.setRoundRect(x, y, w, h, aw, ah);
        return tmpRRect;
    }

    private static java.awt.geom.Line2D tmpLine =
        new java.awt.geom.Line2D.Float();
    private static java.awt.Shape tmpLine(float x1, float y1, float x2, float y2) {
        tmpLine.setLine(x1, y1, x2, y2);
        return tmpLine;
    }

    private static AdaptorShape tmpAdaptor = new AdaptorShape();
    private static java.awt.Shape tmpShape(Shape s) {
        tmpAdaptor.setShape(s);
        return tmpAdaptor;
    }

    J2DPresentable target;
    java.awt.Graphics2D g2d;
    Affine2D transform;
    Rectangle clipRect;
    RectBounds devClipRect;
    RectBounds finalClipRect;
    Paint paint;
    boolean paintWasProportional;
    BasicStroke stroke;
    boolean cull;

    J2DPrismGraphics(J2DPresentable target, java.awt.Graphics2D g2d) {
        this(g2d, target.getContentWidth(), target.getContentHeight());
        this.target = target;
    }

    J2DPrismGraphics(java.awt.Graphics2D g2d, int width, int height) {
        this.g2d = g2d;
        captureTransform(g2d);
        this.transform = new Affine2D();
        this.devClipRect = new RectBounds(0, 0, width, height);
        this.finalClipRect = new RectBounds(0, 0, width, height);
        this.cull = true;

        g2d.setRenderingHint(java.awt.RenderingHints.KEY_STROKE_CONTROL,
                             java.awt.RenderingHints.VALUE_STROKE_PURE);
        g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
                              java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION,
                             java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        /* Set the text hints to those most equivalent to FX rendering.
         * Will need to revisit this since its unlikely to be sufficient.
         */
        g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS,
                           java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
                             java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);


        setTransform(BaseTransform.IDENTITY_TRANSFORM);
        setPaint(DEFAULT_PAINT);
        setStroke(DEFAULT_STROKE);
    }

    public RenderTarget getRenderTarget() {
        return target;
    }

    public Screen getAssociatedScreen() {
        return target.getAssociatedScreen();
    }

    public ResourceFactory getResourceFactory() {
        return target.getResourceFactory();
    }

    public void reset() {
    }

    public Rectangle getClipRect() {
        return clipRect == null ? null : new Rectangle(clipRect);
    }

    public Rectangle getClipRectNoClone() {
        return clipRect;
    }

    public RectBounds getFinalClipNoClone() {
        return finalClipRect;
    }

    public void setClipRect(Rectangle clipRect) {
        this.finalClipRect.setBounds(devClipRect);
        if (clipRect == null) {
            this.clipRect = null;
            g2d.setClip(null);
        } else {
            this.clipRect = new Rectangle(clipRect);
            this.finalClipRect.intersectWith(clipRect);
            setTransformG2D(J2D_IDENTITY);
            g2d.setClip(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
            setTransformG2D(tmpJ2DTransform(transform));
        }
    }

    private java.awt.AlphaComposite getAWTComposite() {
        return (java.awt.AlphaComposite) g2d.getComposite();
    }

    public float getExtraAlpha() {
        return getAWTComposite().getAlpha();
    }

    public void setExtraAlpha(float extraAlpha) {
        g2d.setComposite(getAWTComposite().derive(extraAlpha));
    }

    public CompositeMode getCompositeMode() {
        int rule = getAWTComposite().getRule();
        switch (rule) {
            case java.awt.AlphaComposite.CLEAR:
                return CompositeMode.CLEAR;
            case java.awt.AlphaComposite.SRC:
                return CompositeMode.SRC;
            case java.awt.AlphaComposite.SRC_OVER:
                return CompositeMode.SRC_OVER;
            default:
                throw new InternalError("Unrecognized AlphaCompsite rule: "+rule);
        }
    }

    public void setCompositeMode(CompositeMode mode) {
        java.awt.AlphaComposite awtComp = getAWTComposite();
        switch (mode) {
            case CLEAR:
                awtComp = awtComp.derive(java.awt.AlphaComposite.CLEAR);
                break;
            case SRC:
                awtComp = awtComp.derive(java.awt.AlphaComposite.SRC);
                break;
            case SRC_OVER:
                awtComp = awtComp.derive(java.awt.AlphaComposite.SRC_OVER);
                break;
            default:
                throw new InternalError("Unrecognized composite mode: "+mode);
        }
        g2d.setComposite(awtComp);
    }

    public Paint getPaint() {
        return paint;
    }

    public void setPaint(Paint paint) {
        this.paint = paint;
        java.awt.Paint j2dpaint = toJ2DPaint(paint, null);
        if (j2dpaint == null) {
            paintWasProportional = true;
        } else {
            paintWasProportional = false;
            g2d.setPaint(j2dpaint);
        }
    }

    public BasicStroke getStroke() {
        return stroke;
    }

    public void setStroke(BasicStroke stroke) {
        this.stroke = stroke;
        g2d.setStroke(toJ2DStroke(stroke));
    }

    public BaseTransform getTransformNoClone() {
        return transform;
    }

    public void translate(float tx, float ty) {
        transform.translate(tx, ty);
        g2d.translate(tx, ty);
    }

    public void scale(float sx, float sy) {
        transform.scale(sx, sy);
        g2d.scale(sx, sy);
    }

    public void transform(BaseTransform xform) {
        if (!xform.is2D()) {
            // No-op until we support 3D
            return;
        }
        transform.concatenate(xform);
        setTransformG2D(tmpJ2DTransform(transform));
    }

    public void setTransform(BaseTransform xform) {
        // TODO: Modify PrEffectHelper to not pass a null... (RT-27384)
        if (xform == null) xform = BaseTransform.IDENTITY_TRANSFORM;
        transform.setTransform(xform);
        setTransformG2D(tmpJ2DTransform(transform));
    }

    public void setTransform(double m00, double m10,
                             double m01, double m11,
                             double m02, double m12)
    {
        transform.setTransform(m00, m10, m01, m11, m02, m12);
        setTransformG2D(tmpJ2DTransform(transform));
    }

    public void clear() {
        clear(Color.TRANSPARENT);
    }

    public void clear(Color color) {
        this.getRenderTarget().setOpaque(color.isOpaque());
        clear(toJ2DColor(color));
    }

    void clear(java.awt.Color c) {
        java.awt.Graphics2D gtmp = (java.awt.Graphics2D) g2d.create();
        gtmp.setTransform(J2D_IDENTITY);
        gtmp.setComposite(java.awt.AlphaComposite.Src);
        gtmp.setColor(c);
        gtmp.fillRect(0, 0, target.getContentWidth(), target.getContentHeight());
        gtmp.dispose();
    }

    public void clearQuad(float x1, float y1, float x2, float y2) {
        g2d.setComposite(java.awt.AlphaComposite.Clear);
        g2d.fill(tmpQuad(x1, y1, x2, y2));
    }

    void fill(java.awt.Shape shape) {
        if (paintWasProportional) {
            if (nodeBounds != null) {
                g2d.setPaint(toJ2DPaint(paint, nodeBounds));
            } else {
                g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D()));
            }
        }
        g2d.fill(shape);
    }

    public void fill(Shape shape) {
        fill(tmpShape(shape));
    }

    public void fillRect(float x, float y, float width, float height) {
        fill(tmpRect(x, y, width, height));
    }

    public void fillRoundRect(float x, float y, float width, float height,
                              float arcw, float arch)
    {
        fill(tmpRRect(x, y, width, height, arcw, arch));
    }

    public void fillEllipse(float x, float y, float width, float height) {
        fill(tmpEllipse(x, y, width, height));
    }

    public void fillQuad(float x1, float y1, float x2, float y2) {
        fill(tmpQuad(x1, y1, x2, y2));
    }

    void draw(java.awt.Shape shape) {
        if (paintWasProportional) {
            if (nodeBounds != null) {
                g2d.setPaint(toJ2DPaint(paint, nodeBounds));
            } else {
                g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D()));
            }
        }
        try {
            g2d.draw(shape);
        } catch (Throwable t) {
            // Workaround for JDK bug 6670624
            // We may get a Ductus PRError (extends RuntimeException)
            // or we may get an InternalError (extends Error)
            // The only common superclass of the two is Throwable...
        }
    }

    public void draw(Shape shape) {
        draw(tmpShape(shape));
    }

    public void drawLine(float x1, float y1, float x2, float y2) {
        draw(tmpLine(x1, y1, x2, y2));
    }

    public void drawRect(float x, float y, float width, float height) {
        draw(tmpRect(x, y, width, height));
    }

    public void drawRoundRect(float x, float y, float width, float height, float arcw, float arch) {
        draw(tmpRRect(x, y, width, height, arcw, arch));
    }

    public void drawEllipse(float x, float y, float width, float height) {
        draw(tmpEllipse(x, y, width, height));
    }

    Rectangle2D nodeBounds = null;

    public void setNodeBounds(RectBounds bounds) {
        nodeBounds = bounds != null ?
                new Rectangle2D.Float(bounds.getMinX(), bounds.getMinY(),
                                      bounds.getWidth(),bounds.getHeight()) :
                null;
    }

    private void drawString(GlyphList gl, int start, int end,
                            FontStrike strike, float x, float y) {
        if (start == end) return;
        int count = end - start;
        int[] glyphs = new int[count];
        for (int i = 0; i < count; i++) {
            glyphs[i] = gl.getGlyphCode(start + i) & CompositeGlyphMapper.GLYPHMASK;
        }
        java.awt.Font j2dfont = toJ2DFont(strike);
        GlyphVector gv = j2dfont.createGlyphVector(g2d.getFontRenderContext(), glyphs);
        java.awt.geom.Point2D pt = new java.awt.geom.Point2D.Float();
        for (int i = 0; i < count; i++) {
            pt.setLocation(gl.getPosX(start + i), gl.getPosY(start + i));
            gv.setGlyphPosition(i, pt);
        }
        g2d.drawGlyphVector(gv, x, y);
    }

    public void drawString(GlyphList gl, FontStrike strike, float x, float y,
                           Color selectColor, int start, int end) {

        int count = gl.getGlyphCount();
        if (count == 0) return;

        // In JDK6, setting graphics AA disables fast text loops
        g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);

        // If the surface has Alpha, JDK will ignore the LCD loops.
        // So for this to have any effect we need to fix JDK, or
        // ensure an opaque surface type.
        if (strike.getAAMode() == FontResource.AA_LCD) {
            g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        }

        if (paintWasProportional) {
            Rectangle2D rectBounds = nodeBounds;
            if (rectBounds == null) {
                Metrics m = strike.getMetrics();
                rectBounds = new Rectangle2D.Float(0,
                                                   m.getAscent(),
                                                   gl.getWidth(),
                                                   m.getLineHeight());
            }
            g2d.setPaint(toJ2DPaint(paint, rectBounds));
        }

        CompositeStrike cStrike = null;
        int slot = 0;
        if (strike instanceof CompositeStrike) {
            cStrike = (CompositeStrike)strike;
            int glyphCode = gl.getGlyphCode(0);
            slot = cStrike.getStrikeSlotForGlyph(glyphCode);
        }
        java.awt.Color sColor = null;
        java.awt.Color tColor = null;
        boolean selected = false;
        if (selectColor != null) {
            sColor = toJ2DColor(selectColor);
            tColor = g2d.getColor();
            int offset = gl.getCharOffset(0);
            selected = start <= offset && offset < end;
        }
        int index = 0;
        if (sColor != null || cStrike != null) {
            /* Draw a segment every time selection or font changes */
            for (int i = 1; i < count; i++) {
                if (sColor != null) {
                    int offset = gl.getCharOffset(i);
                    boolean glyphSelected = start <= offset && offset < end;
                    if (selected != glyphSelected) {
                        if (cStrike != null) {
                            strike = cStrike.getStrikeSlot(slot);
                        }
                        g2d.setColor(selected ? sColor : tColor);
                        drawString(gl, index, i, strike, x, y);
                        index = i;
                        selected = glyphSelected;
                    }
                }
                if (cStrike != null) {
                    int glyphCode = gl.getGlyphCode(i);
                    int glyphSlot = cStrike.getStrikeSlotForGlyph(glyphCode);
                    if (slot != glyphSlot) {
                        strike = cStrike.getStrikeSlot(slot);
                        if (sColor != null) {
                            g2d.setColor(selected ? sColor : tColor);
                        }
                        drawString(gl, index, i, strike, x, y);
                        index = i;
                        slot = glyphSlot;
                    }
                }
            }

            /* Set strike and color to draw the last segment */
            if (cStrike != null) {
                strike = cStrike.getStrikeSlot(slot);
            }
            if (sColor != null) {
                g2d.setColor(selected ? sColor : tColor);
            }
        }
        drawString(gl, index, count, strike, x, y);

        /* Always restore the graphics to its initial color */
        if (selectColor != null) {
            g2d.setColor(tColor);
        }

        // Set hints back to the default.
        g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
    }

    /**
     * Overridden by printing subclass to preserve the printer graphics
     * transform.
     */
    protected void setTransformG2D(java.awt.geom.AffineTransform tx) {
        g2d.setTransform(tx);
    }

    /**
     * Needed only by printing subclass, which over-rides it.
     */
    protected void captureTransform(java.awt.Graphics2D g2d) {
        return;
    }

    public void drawMappedTextureRaw(Texture tex,
                                     float dx1, float dy1, float dx2, float dy2,
                                     float tx11, float ty11, float tx21, float ty21,
                                     float tx12, float ty12, float tx22, float ty22)
    {
        java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
        float mxx = tx21-tx11;
        float myx = ty21-ty11;
        float mxy = tx12-tx11;
        float myy = ty12-ty11;
//        assert(Math.abs(mxx - (tx22-tx12)) < .000001);
//        assert(Math.abs(myx - (ty22-ty12)) < .000001);
//        assert(Math.abs(mxy - (tx22-tx21)) < .000001);
//        assert(Math.abs(myy - (ty22-ty21)) < .000001);
        setTransformG2D(J2D_IDENTITY);
        tmpAT.setTransform(mxx, myx, mxy, myy, tx11, ty11);
        try {
            tmpAT.invert();
            g2d.translate(dx1, dy1);
            g2d.scale(dx2-dx1, dy2-dy1);
            g2d.transform(tmpAT);
            g2d.drawImage(img, 0, 0, 1, 1, null);
        } catch (NoninvertibleTransformException e) {
        }
        setTransform(transform);
    }

    public void drawTexture(Texture tex, float x, float y, float w, float h) {
        java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
        g2d.drawImage(img, (int) x, (int) y, (int) (x+w), (int) (y+h), 0, 0, (int)w, (int) h, null);
    }

    public void drawTexture(Texture tex,
                            float dx1, float dy1, float dx2, float dy2,
                            float sx1, float sy1, float sx2, float sy2)
    {
        java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
        // Simply casting the subimage coordinates to integers does not
        // produce the same behavior as the Prism hw pipelines (see RT-19270).
        g2d.drawImage(img,
                      (int) dx1, (int) dy1, (int) dx2, (int) dy2,
                      (int) sx1, (int) sy1, (int) sx2, (int) sy2,
                      null);
    }

    public void drawTextureRaw(Texture tex,
                               float dx1, float dy1, float dx2, float dy2,
                               float tx1, float ty1, float tx2, float ty2)
    {
        int w = tex.getContentWidth();
        int h = tex.getContentHeight();
        tx1 *= w;
        ty1 *= h;
        tx2 *= w;
        ty2 *= h;
        drawTexture(tex, dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
    }

    public void drawTextureVO(Texture tex,
                              float topopacity, float botopacity,
                              float dx1, float dy1, float dx2, float dy2,
                              float sx1, float sy1, float sx2, float sy2)
    {
        // assert(caller is PrReflectionPeer and buffer is cleared to transparent)
        // NOTE: the assert conditions are true because that is the only
        // place where this method is used (unless we subclass BaseGraphics),
        // but there is no code here to verify that information.
        // The workarounds to do this for the general case would cost a lot
        // because they would involve creating a temporary intermediate buffer,
        // doing the operations below into the buffer, and then applying the
        // buffer to the destination.  That is not hard, but it costs a lot
        // of buffer allocation (or caching) when it is not really necessary
        // given the way this method is called currently.
        // Note that isoEdgeMask is ignored here, but since this is only ever
        // called by PrReflectionPeer and that code always uses ISOLATE_NONE
        // then we would only need to support ISOLATE_NONE.  The code below
        // does not yet verify if the results will be compatible with
        // ISOLATE_NONE, but given that the source coordinates are rounded to
        // integers in drawTexture() there is not much it can do to get exact
        // edge condition behavior until that deficiency is fixed (see
        // RT-19270 and RT-19271).
        java.awt.Paint savepaint = g2d.getPaint();
        java.awt.Composite savecomp = g2d.getComposite();
        java.awt.Color c1 = new java.awt.Color(1f, 1f, 1f, topopacity);
        java.awt.Color c2 = new java.awt.Color(1f, 1f, 1f, botopacity);
        g2d.setPaint(new java.awt.GradientPaint(0f, dy1, c1, 0f, dy2, c2, true));
        g2d.setComposite(java.awt.AlphaComposite.Src);
        int x = (int) Math.floor(Math.min(dx1, dx2));
        int y = (int) Math.floor(Math.min(dy1, dy2));
        int w = (int) Math.ceil(Math.max(dx1, dx2)) - x;
        int h = (int) Math.ceil(Math.max(dy1, dy2)) - y;
        g2d.fillRect(x, y, w, h);
        g2d.setComposite(java.awt.AlphaComposite.SrcIn);
        drawTexture(tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
        g2d.setComposite(savecomp);
        g2d.setPaint(savepaint);
    }

    public void drawPixelsMasked(RTTexture imgtex, RTTexture masktex,
                                 int dx, int dy, int dw, int dh,
                                 int ix, int iy, int mx, int my)
    {
        doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex,
                          dx, dy, dw, dh,
                          ix, iy, mx, my,
                          true);
    }

    public void maskInterpolatePixels(RTTexture imgtex, RTTexture masktex, int dx,
                                      int dy, int dw, int dh, int ix, int iy,
                                      int mx, int my) {
        doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex,
                          dx, dy, dw, dh,
                          ix, iy, mx, my,
                          false);
    }

    private void doDrawMaskTexture(J2DRTTexture imgtex, J2DRTTexture masktex,
                                   int dx, int dy, int dw, int dh,
                                   int ix, int iy, int mx, int my,
                                   boolean srcover)
    {
        int cx0 = clipRect.x;
        int cy0 = clipRect.y;
        int cx1 = cx0 + clipRect.width;
        int cy1 = cy0 + clipRect.height;

        if (dw <= 0 || dh <= 0) return;
        if (dx < cx0) {
            int bump = cx0 - dx;
            if ((dw -= bump) <= 0) return;
            ix += bump;
            mx += bump;
            dx = cx0;
        }
        if (dy < cy0) {
            int bump = cy0 - dy;
            if ((dh -= bump) <= 0) return;
            iy += bump;
            my += bump;
            dy = cy0;
        }
        if (dx + dw > cx1 && (dw = cx1 - dx) <= 0) return;
        if (dy + dh > cy1 && (dh = cy1 - dy) <= 0) return;

        int iw = imgtex.getContentWidth();
        int ih = imgtex.getContentHeight();
        if (ix < 0) {
            if ((dw += ix) <= 0) return;
            dx -= ix;
            mx -= ix;
            ix = 0;
        }
        if (iy < 0) {
            if ((dh += iy) <= 0) return;
            dy -= iy;
            my -= iy;
            iy = 0;
        }
        if (ix + dw > iw && (dw = iw - ix) <= 0) return;
        if (iy + dh > ih && (dh = ih - iy) <= 0) return;

        int mw = masktex.getContentWidth();
        int mh = masktex.getContentHeight();
        if (mx < 0) {
            if ((dw += mx) <= 0) return;
            dx -= mx;
            ix -= mx;
            mx = 0;
        }
        if (my < 0) {
            if ((dh += my) <= 0) return;
            dy -= my;
            iy -= my;
            my = 0;
        }
        if (mx + dw > mw && (dw = mw - mx) <= 0) return;
        if (my + dh > mh && (dh = mh - my) <= 0) return;

        int imgbuf[] = imgtex.getPixels();
        int maskbuf[] = masktex.getPixels();
        java.awt.image.DataBuffer db = target.getBackBuffer().getRaster().getDataBuffer();
        int dstbuf[] = ((java.awt.image.DataBufferInt) db).getData();
        int iscan = imgtex.getBufferedImage().getWidth();
        int mscan = masktex.getBufferedImage().getWidth();
        int dscan = target.getBackBuffer().getWidth();
        int ioff = iy * iscan + ix;
        int moff = my * mscan + mx;
        int doff = dy * dscan + dx;
        if (srcover) {
            for (int y = 0; y < dh; y++) {
                for (int x = 0; x < dw; x++) {
                    int a, r, g, b;
                    int maskalpha = maskbuf[moff+x] >>> 24;
                    if (maskalpha == 0) continue;
                    int imgpix = imgbuf[ioff+x];
                    a = (imgpix >>> 24);
                    if (a == 0) continue;
                    if (maskalpha < 0xff) {
                        maskalpha += (maskalpha >> 7);
                        a *= maskalpha;
                        r = ((imgpix >>  16) & 0xff) * maskalpha;
                        g = ((imgpix >>   8) & 0xff) * maskalpha;
                        b = ((imgpix       ) & 0xff) * maskalpha;
                    } else if (a < 0xff) {
                        a <<= 8;
                        r = ((imgpix >>  16) & 0xff) << 8;
                        g = ((imgpix >>   8) & 0xff) << 8;
                        b = ((imgpix       ) & 0xff) << 8;
                    } else {
                        dstbuf[doff+x] = imgpix;
                        continue;
                    }
                    maskalpha = ((a + 128) >> 8);
                    maskalpha += (maskalpha >> 7);
                    maskalpha = 256 - maskalpha;
                    imgpix = dstbuf[doff+x];
                    a += ((imgpix >>> 24)       ) * maskalpha + 128;
                    r += ((imgpix >>  16) & 0xff) * maskalpha + 128;
                    g += ((imgpix >>   8) & 0xff) * maskalpha + 128;
                    b += ((imgpix       ) & 0xff) * maskalpha + 128;
                    imgpix = ((a >> 8) << 24) +
                             ((r >> 8) << 16) +
                             ((g >> 8) <<  8) +
                             ((b >> 8)      );
                    dstbuf[doff+x] = imgpix;
                }
                ioff += iscan;
                moff += mscan;
                doff += dscan;
            }
        } else {
            for (int y = 0; y < dh; y++) {
                for (int x = 0; x < dw; x++) {
                    int maskalpha = maskbuf[moff+x] >>> 24;
                    if (maskalpha == 0) continue;
                    int imgpix = imgbuf[ioff+x];
                    if (maskalpha < 0xff) {
                        maskalpha += (maskalpha >> 7);
                        int a = ((imgpix >>> 24)       ) * maskalpha;
                        int r = ((imgpix >>  16) & 0xff) * maskalpha;
                        int g = ((imgpix >>   8) & 0xff) * maskalpha;
                        int b = ((imgpix       ) & 0xff) * maskalpha;
                        maskalpha = 256 - maskalpha;
                        imgpix = dstbuf[doff+x];
                        a += ((imgpix >>> 24)       ) * maskalpha + 128;
                        r += ((imgpix >>  16) & 0xff) * maskalpha + 128;
                        g += ((imgpix >>   8) & 0xff) * maskalpha + 128;
                        b += ((imgpix       ) & 0xff) * maskalpha + 128;
                        imgpix = ((a >> 8) << 24) +
                                 ((r >> 8) << 16) +
                                 ((g >> 8) <<  8) +
                                 ((b >> 8)      );
                    }
                    dstbuf[doff+x] = imgpix;
                }
                ioff += iscan;
                moff += mscan;
                doff += dscan;
            }
        }
    }

    public boolean canReadBack() {
        return true;
    }

    public RTTexture readBack(Rectangle view) {
        J2DRTTexture rtt = target.getReadbackBuffer();
        java.awt.Graphics2D rttg2d = rtt.createAWTGraphics2D();
        rttg2d.setComposite(java.awt.AlphaComposite.Src);
        int x0 = view.x;
        int y0 = view.y;
        int w = view.width;
        int h = view.height;
        int x1 = x0 + w;
        int y1 = y0 + h;
        rttg2d.drawImage(target.getBackBuffer(),
                          0,  0,  w,  h,
                         x0, y0, x1, y1, null);
        rttg2d.dispose();
        return rtt;
    }

    public void releaseReadBackBuffer(RTTexture view) {
        // This will be needed when we track LCD buffer locks and uses.
        // (See RT-29488)
//        target.getReadbackBuffer().unlock();
    }

    public PrismCameraImpl getCameraNoClone() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean hasOrthoCamera() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isDepthBuffer() {
        return false;
    }

    public boolean isDepthTest() {
        return false;
    }

    public void scale(float sx, float sy, float sz) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setTransform3D(double mxx, double mxy, double mxz, double mxt,
                               double myx, double myy, double myz, double myt,
                               double mzx, double mzy, double mzz, double mzt)
    {
        if (mxz != 0.0 || myz != 0.0 ||
            mzx != 0.0 || mzy != 0.0 || mzz != 1.0 || mzt != 0.0)
        {
            throw new UnsupportedOperationException("3D transforms not supported.");
        }
        setTransform(mxx, myx, mxy, myy, mxt, myt);
    }

    public void setCamera(PrismCameraImpl camera) {
        // No-op until we support 3D
        /*
        if (!(camera instanceof PrismParallelCameraImpl)) {

            throw new UnsupportedOperationException(camera+" not supported.");
        }
        */
    }

    public void setDepthBuffer(boolean depthBuffer) {
        // No-op until we support 3D
    }

    public void setDepthTest(boolean depthTest) {
        // No-op until we support 3D
    }

    public void sync() {
    }

    public void translate(float tx, float ty, float tz) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setWindowProjViewTx(GeneralTransform3D pvTx) {
        this.pvTx = pvTx;
    }

    public GeneralTransform3D getWindowProjViewTxNoClone() {
        return this.pvTx;
    }

    public void setCulling(boolean cull) {
        this.cull = cull;
    }

    public boolean isCulling() {
        return this.cull;
    }

 public void setClipRectIndex(int index) {
        this.clipRectIndex = index;
    }
    public int getClipRectIndex() {
        return this.clipRectIndex;
    }

    public void setHasPreCullingBits(boolean hasBits) {
        this.hasPreCullingBits = hasBits;
    }

    public boolean hasPreCullingBits() {
        return hasPreCullingBits;
    }

    private Object renderRoot;
    @Override
    public void setRenderRoot(Object root) {
        this.renderRoot = root;
    }

    @Override
    public Object getRenderRoot() {
        return renderRoot;
    }

    public void setState3D(boolean flag) {
    }

    public boolean isState3D() {
        return false;
    }

    public void setup3DRendering() {
    }

    private static class AdaptorShape implements java.awt.Shape {
        private Shape prshape;

        public void setShape(Shape prshape) {
            this.prshape = prshape;
        }

        public boolean contains(double x, double y) {
            return prshape.contains((float) x, (float) y);
        }

        public boolean contains(java.awt.geom.Point2D p) {
            return contains(p.getX(), p.getY());
        }

        public boolean contains(double x, double y, double w, double h) {
            return prshape.contains((float) x, (float) y, (float) w, (float) h);
        }

        public boolean contains(java.awt.geom.Rectangle2D r) {
            return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

        public boolean intersects(double x, double y, double w, double h) {
            return prshape.intersects((float) x, (float) y, (float) w, (float) h);
        }

        public boolean intersects(java.awt.geom.Rectangle2D r) {
            return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

        public java.awt.Rectangle getBounds() {
            return getBounds2D().getBounds();
        }

        public java.awt.geom.Rectangle2D getBounds2D() {
            RectBounds b = prshape.getBounds();
            java.awt.geom.Rectangle2D r2d =
                new java.awt.geom.Rectangle2D.Float();
            r2d.setFrameFromDiagonal(b.getMinX(), b.getMinY(), b.getMaxX(), b.getMaxY());
            return r2d;
        }

        private static AdaptorPathIterator tmpAdaptor =
                new AdaptorPathIterator();
        private static java.awt.geom.PathIterator tmpAdaptor(PathIterator pi) {
            tmpAdaptor.setIterator(pi);
            return tmpAdaptor;
        }

        public java.awt.geom.PathIterator
            getPathIterator(java.awt.geom.AffineTransform at)
        {
            BaseTransform tx = (at == null) ? null : toPrTransform(at);
            return tmpAdaptor(prshape.getPathIterator(tx));
        }

        public java.awt.geom.PathIterator
            getPathIterator(java.awt.geom.AffineTransform at,
                            double flatness)
        {
            BaseTransform tx = (at == null) ? null : toPrTransform(at);
            return tmpAdaptor(prshape.getPathIterator(tx, (float) flatness));
        }
    }

    private static class AdaptorPathIterator
        implements java.awt.geom.PathIterator
    {
        private static int NUM_COORDS[] = { 2, 2, 4, 6, 0 };
        PathIterator priterator;
        float tmpcoords[];

        public void setIterator(PathIterator priterator) {
            this.priterator = priterator;
        }

        public int currentSegment(float[] coords) {
            return priterator.currentSegment(coords);
        }

        public int currentSegment(double[] coords) {
            if (tmpcoords == null) {
                tmpcoords = new float[6];
            }
            int ret = priterator.currentSegment(tmpcoords);
            for (int i = 0; i < NUM_COORDS[ret]; i++) {
                coords[i] = (double) tmpcoords[i];
            }
            return ret;
        }

        public int getWindingRule() {
            return priterator.getWindingRule();
        }

        public boolean isDone() {
            return priterator.isDone();
        }

        public void next() {
            priterator.next();
        }
    }

    static abstract class FilterStroke implements java.awt.Stroke {
        protected java.awt.BasicStroke stroke;

        FilterStroke(java.awt.BasicStroke stroke) {
            this.stroke = stroke;
        }

        abstract protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r);
        abstract protected java.awt.Shape makeStrokedShape(java.awt.Shape s);

        public java.awt.Shape createStrokedShape(java.awt.Shape p) {
            if (p instanceof java.awt.geom.Rectangle2D) {
                java.awt.Shape s = makeStrokedRect((java.awt.geom.Rectangle2D) p);
                if (s != null) {
                    return s;
                }
            }
            return makeStrokedShape(p);
        }

        // ArcIterator.btan(Math.PI/2)
        static final double CtrlVal = 0.5522847498307933;

        static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp,
                                                      float x0, float y0,
                                                      float xc, float yc,
                                                      float x1, float y1)
        {
            return cornerArc(gp, x0, y0, xc, yc, x1, y1, 0.5f);
        }

        static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp,
                                                      float x0, float y0,
                                                      float xc, float yc,
                                                      float x1, float y1, float t)
        {
            float xc0 = (float) (x0 + CtrlVal * (xc - x0));
            float yc0 = (float) (y0 + CtrlVal * (yc - y0));
            float xc1 = (float) (x1 + CtrlVal * (xc - x1));
            float yc1 = (float) (y1 + CtrlVal * (yc - y1));
            gp.curveTo(xc0, yc0, xc1, yc1, x1, y1);

            return new java.awt.geom.Point2D.Float(eval(x0, xc0, xc1, x1, t),
                                                   eval(y0, yc0, yc1, y1, t));
        }

        static float eval(float c0, float c1, float c2, float c3, float t) {
            c0 = c0 + (c1-c0) * t;
            c1 = c1 + (c2-c1) * t;
            c2 = c2 + (c3-c2) * t;
            c0 = c0 + (c1-c0) * t;
            c1 = c1 + (c2-c1) * t;
            return c0 + (c1-c0) * t;
        }
    }

    static class InnerStroke extends FilterStroke {
        InnerStroke(java.awt.BasicStroke stroke) {
            super(stroke);
        }

        protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) {
            if (stroke.getDashArray() != null) {
                return null;
            }
            float pad = stroke.getLineWidth() / 2f;
            if (pad >= r.getWidth() || pad >= r.getHeight()) {
                return r;
            }
            float rx0 = (float) r.getX();
            float ry0 = (float) r.getY();
            float rx1 = rx0 + (float) r.getWidth();
            float ry1 = ry0 + (float) r.getHeight();
            java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
            gp.moveTo(rx0, ry0);
            gp.lineTo(rx1, ry0);
            gp.lineTo(rx1, ry1);
            gp.lineTo(rx0, ry1);
            gp.closePath();
            rx0 += pad;
            ry0 += pad;
            rx1 -= pad;
            ry1 -= pad;
            gp.moveTo(rx0, ry0);
            gp.lineTo(rx0, ry1);
            gp.lineTo(rx1, ry1);
            gp.lineTo(rx1, ry0);
            gp.closePath();
            return gp;
        }

        // NOTE: This is a work in progress, not used yet
        protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) {
            if (stroke.getDashArray() != null) {
                return null;
            }
            float pad = stroke.getLineWidth() / 2f;
            float w = (float) e.getWidth();
            float h = (float) e.getHeight();
            if (w - 2*pad > h * 2 || h - 2*pad > w * 2) {
                // If the inner ellipse is too "squashed" then we can not
                // approximate it with just a single cubic per quadrant.
                // NOTE: measure so we can relax this restriction and
                // also consider modifying the code below to insert
                // more cubics in those cases.
                return null;
            }
            if (pad >= w || pad >= h) {
                return e;
            }
            float x0 = (float) e.getX();
            float y0 = (float) e.getY();
            float xc = x0 + w / 2f;
            float yc = y0 + h / 2f;
            float x1 = x0 + w;
            float y1 = y0 + h;
            java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
            gp.moveTo(xc, y0);
            cornerArc(gp, xc, y0, x1, y0, x1, yc);
            cornerArc(gp, x1, yc, x1, y1, xc, y1);
            cornerArc(gp, xc, y1, x0, y1, x0, yc);
            cornerArc(gp, x0, yc, x0, y0, xc, y0);
            gp.closePath();
            x0 += pad;
            y0 += pad;
            x1 -= pad;
            y1 -= pad;
            gp.moveTo(xc, y0);
            cornerArc(gp, xc, y0, x0, y0, x0, yc);
            cornerArc(gp, x0, yc, x0, y1, xc, y1);
            cornerArc(gp, xc, y1, x1, y1, x1, yc);
            cornerArc(gp, x1, yc, x1, y0, xc, y0);
            gp.closePath();
            return gp;
        }

        @Override
        protected java.awt.Shape makeStrokedShape(java.awt.Shape s) {
            java.awt.Shape ss = stroke.createStrokedShape(s);
            java.awt.geom.Area b = new java.awt.geom.Area(ss);
            b.intersect(new java.awt.geom.Area(s));
            return b;
        }
    }

    static class OuterStroke extends FilterStroke {
        static double SQRT_2 = Math.sqrt(2);

        OuterStroke(java.awt.BasicStroke stroke) {
            super(stroke);
        }

        protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) {
            if (stroke.getDashArray() != null) {
                return null;
            }
            float pad = stroke.getLineWidth() / 2f;
            float rx0 = (float) r.getX();
            float ry0 = (float) r.getY();
            float rx1 = rx0 + (float) r.getWidth();
            float ry1 = ry0 + (float) r.getHeight();
            java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
            // clockwise
            gp.moveTo(rx0, ry0);
            gp.lineTo(rx1, ry0);
            gp.lineTo(rx1, ry1);
            gp.lineTo(rx0, ry1);
            gp.closePath();
            float ox0 = rx0 - pad;
            float oy0 = ry0 - pad;
            float ox1 = rx1 + pad;
            float oy1 = ry1 + pad;
            switch (stroke.getLineJoin()) {
            case BasicStroke.JOIN_MITER:
                // A miter limit of less than sqrt(2) bevels right angles...
                if (stroke.getMiterLimit() >= SQRT_2) {
                    // counter-clockwise
                    gp.moveTo(ox0, oy0);
                    gp.lineTo(ox0, oy1);
                    gp.lineTo(ox1, oy1);
                    gp.lineTo(ox1, oy0);
                    gp.closePath();
                    break;
                }
                // NO BREAK
            case BasicStroke.JOIN_BEVEL:
                // counter-clockwise
                gp.moveTo(ox0, ry0);
                gp.lineTo(ox0, ry1);  // left edge
                gp.lineTo(rx0, oy1);  // ll corner
                gp.lineTo(rx1, oy1);  // bottom edge
                gp.lineTo(ox1, ry1);  // lr corner
                gp.lineTo(ox1, ry0);  // right edge
                gp.lineTo(rx1, oy0);  // ur corner
                gp.lineTo(rx0, oy0);  // top edge
                gp.closePath();       // ul corner
                break;
            case BasicStroke.JOIN_ROUND:
                // counter-clockwise
                gp.moveTo(ox0, ry0);
                gp.lineTo(ox0, ry1);                          // left edge
                cornerArc(gp, ox0, ry1, ox0, oy1, rx0, oy1);  // ll corner
                gp.lineTo(rx1, oy1);                          // bottom edge
                cornerArc(gp, rx1, oy1, ox1, oy1, ox1, ry1);  // lr corner
                gp.lineTo(ox1, ry0);                          // right edge
                cornerArc(gp, ox1, ry0, ox1, oy0, rx1, oy0);  // ur corner
                gp.lineTo(rx0, oy0);                          // top edge
                cornerArc(gp, rx0, oy0, ox0, oy0, ox0, ry0);  // ul corner
                gp.closePath();
                break;
            default:
                throw new InternalError("Unrecognized line join style");
            }
            return gp;
        }

        // NOTE: This is a work in progress, not used yet
        protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) {
            if (stroke.getDashArray() != null) {
                return null;
            }
            float pad = stroke.getLineWidth() / 2f;
            float w = (float) e.getWidth();
            float h = (float) e.getHeight();
            if (w > h * 2 || h > w * 2) {
                // If the inner ellipse is too "squashed" then we can not
                // approximate it with just a single cubic per quadrant.
                // NOTE: measure so we can relax this restriction and
                // also consider modifying the code below to insert
                // more cubics in those cases.
                return null;
            }
            float x0 = (float) e.getX();
            float y0 = (float) e.getY();
            float xc = x0 + w / 2f;
            float yc = y0 + h / 2f;
            float x1 = x0 + w;
            float y1 = y0 + h;
            java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
            gp.moveTo(xc, y0);
            cornerArc(gp, xc, y0, x1, y0, x1, yc);
            cornerArc(gp, x1, yc, x1, y1, xc, y1);
            cornerArc(gp, xc, y1, x0, y1, x0, yc);
            cornerArc(gp, x0, yc, x0, y0, xc, y0);
            gp.closePath();
            x0 -= pad;
            y0 -= pad;
            x1 += pad;
            y1 += pad;
            gp.moveTo(xc, y0);
            cornerArc(gp, xc, y0, x0, y0, x0, yc);
            cornerArc(gp, x0, yc, x0, y1, xc, y1);
            cornerArc(gp, xc, y1, x1, y1, x1, yc);
            cornerArc(gp, x1, yc, x1, y0, xc, y0);
            gp.closePath();
            return gp;
        }

        @Override
        protected java.awt.Shape makeStrokedShape(java.awt.Shape s) {
            java.awt.Shape ss = stroke.createStrokedShape(s);
            java.awt.geom.Area b = new java.awt.geom.Area(ss);
            b.subtract(new java.awt.geom.Area(s));
            return b;
        }
    }

    @Override
    public void setLights(Object[] lights) {
        // Light are not supported by J2d
    }

    @Override
    public Object[] getLights() {
        // Light are not supported by J2d
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy