FileDocCategorySizeDatePackage
RenderContext.javaAPI DocphoneME MR2 API (J2ME)25419Wed May 02 18:00:34 BST 2007com.sun.perseus.j2d

RenderContext.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program 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.
 * 
 * This program 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 at /legal/license.txt).
 * 
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.perseus.j2d;

/**
 * {@link com.sun.perseus.model.ModelNode ModelNode} are rendered into a 
 * {@link RenderGraphics RenderGraphics} which extends
 * the <tt>RenderContext</tt> class. A <tt>RenderContext</tt> captures
 * the current state of the properties used in painting operations. When a 
 * <tt>ModelNode</tt> tree is rendered, the <tt>RenderGraphics</tt> 
 * is passed down the tree to children nodes as they are painted. This 
 * provides the solution for computing CSS inherited values: if a 
 * <tt>ModelNode</tt> inherits one of its properties, it will simply use
 * the one currently set in the <tt>RenderContext</tt>.
 * <br />
 * Initially (i.e., before any painting operation happens), the 
 * <tt>RenderContext</tt> holds values which correspond to the CSS 2 concept of
 * <a href="http://www.w3.org/TR/REC-CSS2/about.html#q7">initial</a> values. 
 * <br />
 * At any time during a rendering operation, the <tt>RenderContext</tt> holds 
 * values which correspond to the CSS 2 
 * <a href="http://www.w3.org/TR/REC-CSS2/cascade.html#computed-value">
 * computed</a> values.
 *
 * Note that in addition to properties that correspond to CSS2 properties, the 
 * <tt>RenderContext</tt> also keeps an <tt>AffineTransform</tt> which is
 * not a CSS2 property. The transform represents the current user space to 
 * device space transformation.
 *
 * @see com.sun.perseus.model.ModelNode
 * @see com.sun.perseus.model.ElementNode#paint
 *
 * @version $Id: RenderContext.java,v 1.5 2006/04/21 06:35:38 st125089 Exp $
 */
public class RenderContext implements TextRenderingProperties {
    // =======================================================================
    // Initial Property values used internally
    // =======================================================================
    /**
     * Default stroke width
     */
    protected static int INITIAL_STROKE_WIDTH_IMPL = 1 << 16;

    /**
     * Default miter limit
     */
    protected static int INITIAL_STROKE_MITER_LIMIT_IMPL = 4 << 16;

    /**
     * Default dash array
     */
    protected static int[] INITIAL_STROKE_DASH_ARRAY_IMPL = null;

    /**
     * Default dash offset
     */
    protected static int INITIAL_STROKE_DASH_OFFSET_IMPL = 0;

    // =======================================================================
    // Constants used for packing and unpacking data in the pack member
    // =======================================================================
    
    protected static final int FONT_STYLE_MASK =       0x60000000;    /* 30-29 */
    protected static final int FONT_WEIGHT_MASK =      0x1E000000;    /* 28-27-26-25 */
    protected static final int TEXT_ANCHOR_MASK =      0x01800000;    /* 24-23 */
    protected static final int STROKE_OPACITY_MASK =   0x007F8000;    /* 22-21-20-19-18-17-16-15 */
    protected static final int FILL_OPACITY_MASK =     0x00007F80;    /* 14-13-12-11-10-09-08-07 */
    protected static final int FILL_RULE_MASK =        0x00000040;    /* 06 */
    protected static final int STROKE_LINE_CAP_MASK =  0x00000030;    /* 05-04 */
    protected static final int STROKE_LINE_JOIN_MASK = 0x0000000C;    /* 03-02 */
    protected static final int VISIBILITY_MASK =       0x00000002;    /* 01 */
    protected static final int DISPLAY_MASK =          0x00000001;    /* 00 */

    // =======================================================================
    // Constants used for packing and unpacking data in the pack2 member
    // =======================================================================
    protected static final int OPACITY_MASK =   
            0x000000FF;    /* 7-6-5-4-3-2-1-0 */

    protected static final int FONT_STYLE_NORMAL_IMPL =  0x00000000;
    protected static final int FONT_STYLE_ITALIC_IMPL =  0x40000000;
    protected static final int FONT_STYLE_OBLIQUE_IMPL = 0x60000000;

    protected static final int FONT_WEIGHT_100_IMPL = 0x00000000;
    protected static final int FONT_WEIGHT_200_IMPL = 0x02000000;
    protected static final int FONT_WEIGHT_300_IMPL = 0x04000000;
    protected static final int FONT_WEIGHT_400_IMPL = 0x06000000;
    protected static final int FONT_WEIGHT_500_IMPL = 0x08000000;
    protected static final int FONT_WEIGHT_600_IMPL = 0x0A000000;
    protected static final int FONT_WEIGHT_700_IMPL = 0x0C000000;
    protected static final int FONT_WEIGHT_800_IMPL = 0x0E000000;
    protected static final int FONT_WEIGHT_900_IMPL = 0x10000000;

    protected static final int TEXT_ANCHOR_MIDDLE_IMPL = 0x00000000;
    protected static final int TEXT_ANCHOR_START_IMPL  = 0x00800000;
    protected static final int TEXT_ANCHOR_END_IMPL =    0x01000000;

    protected static final int CAP_BUTT_IMPL =   0x00000000;
    protected static final int CAP_ROUND_IMPL =  0x00000010;
    protected static final int CAP_SQUARE_IMPL = 0x00000020;

    protected static final int JOIN_MITER_IMPL = 0x00000000;
    protected static final int JOIN_ROUND_IMPL = 0x00000004;
    protected static final int JOIN_BEVEL_IMPL = 0x00000008;

    // =======================================================================
    // Property values, some are packed.
    // =======================================================================
    /**
     * Color used for fill operations
     */
    protected PaintServer fill = INITIAL_FILL;

    /**
     * Color used for fill operations
     */
    protected PaintServer stroke = INITIAL_STROKE;

    /**
     * Context Color. Corresponds to the concept of 
     * 'current color' in CSS
     */
    protected RGB color = INITIAL_COLOR;

    /**
     * Controls the strokeDash pattern for stroke operations.
     * If the array is null, a solid line is used
     */
    protected int[] strokeDashArray = INITIAL_STROKE_DASH_ARRAY_IMPL;

    /**
     * Controls the width of the stroke. 15.16 format.
     */
    protected int strokeWidth = INITIAL_STROKE_WIDTH_IMPL;

    /**
     * Controls the miter limit for stroke operations. 15.16 format.
     */
    protected int strokeMiterLimit = INITIAL_STROKE_MITER_LIMIT_IMPL;

    /**
     * Controls the offset in the dash array for
     * stroke operations. 15.16 format.
     */
    protected int strokeDashOffset = INITIAL_STROKE_DASH_OFFSET_IMPL;

    /**
     * Controls the current font family. The font family is 
     * a list, which means that a set of fonts may be used
     * to render a given text string. The fonts are queried in
     * order to check which ones can render characters in the
     * drawn text.
     */
    protected String[] fontFamily = INITIAL_FONT_FAMILY;

    /**
     * Controls the current font size
     */
    protected float fontSize = INITIAL_FONT_SIZE;

    // 
    // Property pack
    //
    // fontStyle:     3 styles,  2 bits
    // fontWeight:    9 weights, 4 bits
    // textAnchor     3 values,  2 bits
    // strokeOpacity:            8 bits [0-200]
    // fillOpacity:              8 bits [0-200]
    // fillRule:                 1 bits
    // strokeLineCap: 3 values,  2 bits
    // strokeLineJoin 3 values,  2 bits
    // visibility: 2 values,     1 bit
    // display: 2 values,        1 bit
    // Total:                   31 bits
    // 
    // Encoding:
    //
    // fontStyle: 30-29
    // fontWeight: 28-27-26-25
    // textAnchor: 24-23
    // strokeOpacity: 22-21-20-19-18-17-16-15
    // fillOpacity: 14-13-12-11-10-09-08-07
    // fillRule: 06
    // strokeLineCap: 05-04
    // strokeLineJoin: 03-02
    // visibility: 01
    // display: 00
    //
    protected int pack = INITIAL_PACK;
    
    protected static final int INITIAL_FONT_STYLE_IMPL = FONT_STYLE_NORMAL_IMPL;
    protected static final int INITIAL_FONT_WEIGHT_IMPL = FONT_WEIGHT_400_IMPL;
    protected static final int INITIAL_TEXT_ANCHOR_IMPL = TEXT_ANCHOR_START_IMPL;
    protected static final int INITIAL_STROKE_OPACITY_IMPL = 200 << 15;
    protected static final int INITIAL_FILL_OPACITY_IMPL = 200 << 7;
    protected static final int INITIAL_FILL_RULE_IMPL = 0;
    protected static final int INITIAL_STROKE_LINE_CAP_IMPL = CAP_BUTT_IMPL;
    protected static final int INITIAL_STROKE_LINE_JOIN_IMPL = JOIN_MITER_IMPL;
    protected static final int INITIAL_VISIBILITY_IMPL = 0x2;
    protected static final int INITIAL_DISPLAY_IMPL = 0x1;
    protected static final int INITIAL_PACK = 
        INITIAL_FONT_STYLE_IMPL
        |
        INITIAL_FONT_WEIGHT_IMPL
        |
        INITIAL_TEXT_ANCHOR_IMPL
        |
        INITIAL_STROKE_OPACITY_IMPL
        |
        INITIAL_FILL_OPACITY_IMPL
        |
        INITIAL_FILL_RULE_IMPL
        |
        INITIAL_STROKE_LINE_CAP_IMPL
        |
        INITIAL_STROKE_LINE_JOIN_IMPL
        |
        INITIAL_VISIBILITY_IMPL
        |
        INITIAL_DISPLAY_IMPL;

    // 
    // Property pack2
    //
    // opacity:                  8 bits [0-255]
    // Total:                    8 bits
    // 
    // Encoding:
    //
    // opacity: 07-06-05-04-03-02-01-00
    //
    protected int pack2 = INITIAL_PACK2;    

    protected static final int INITIAL_OPACITY_IMPL = 200;
    protected static final int INITIAL_PACK2 = 
        INITIAL_OPACITY_IMPL;

    /**
     * Default constructor
     */
    public RenderContext() {
    }

    /**
     * Copy constructor
     * @param rcs the <tt>RenderContext</tt> to copy values from. Should
     *        not be null.
     */
    public RenderContext(final RenderContext rcs) {
        restore(rcs);
    }

    /**
     * @param rcs the <tt>RenderContext</tt> state to restore. Should
     *        not be null.
     */
    public void restore(final RenderContext rcs) {
        this.fill = rcs.fill;
        this.stroke = rcs.stroke;
        this.color = rcs.color;
        this.strokeDashArray = rcs.strokeDashArray;
        this.strokeWidth = rcs.strokeWidth;
        this.strokeMiterLimit = rcs.strokeMiterLimit;
        this.strokeDashOffset = rcs.strokeDashOffset;
        this.fontFamily = rcs.fontFamily;
        this.fontSize = rcs.fontSize;
        this.pack = rcs.pack;
        this.pack2 = rcs.pack2;
    }

    /**
     * Resets context to initial values
     */
    public void reset() {
        this.fill = INITIAL_FILL;
        this.stroke = INITIAL_STROKE;
        this.color = INITIAL_COLOR;
        this.strokeDashArray = INITIAL_STROKE_DASH_ARRAY_IMPL;
        this.strokeWidth = INITIAL_STROKE_WIDTH_IMPL;
        this.strokeMiterLimit = INITIAL_STROKE_MITER_LIMIT_IMPL;
        this.strokeDashOffset = INITIAL_STROKE_DASH_OFFSET_IMPL;
        this.fontFamily = INITIAL_FONT_FAMILY;
        this.fontSize = INITIAL_FONT_SIZE;
        this.pack = INITIAL_PACK;
        this.pack2 = INITIAL_PACK2;
    }

    /**
     * @param newFill the new value for the fill property
     */
    public void setFill(final PaintServer newFill) {
        this.fill = newFill;
    }

    /**
     * @return the context's fill property value
     */
    public PaintServer getFill() {
        return fill;
    }

    /**
     * @param newFillOpacity the new value for the fill opacity property. The 
     *        input value is clamped to the [0, 1] range.
     */
    public void setFillOpacity(float newFillOpacity) {
        if (newFillOpacity < 0) {
            newFillOpacity = 0;
        } else if (newFillOpacity > 1) {
            newFillOpacity = 1;
        }
        
        pack &= ~FILL_OPACITY_MASK;
        pack |= ((((int) (newFillOpacity * 200)) << 7) & FILL_OPACITY_MASK);
    }

    /**
     * @return the context's fill opacity property value
     */
    public float getFillOpacity() {
        return ((pack & FILL_OPACITY_MASK) >> 7) / 200.0f;
    }

    /**
     * @return the context's fill opacity property value
     */
    int getFillOpacityImpl() {
        return (int) (((pack & FILL_OPACITY_MASK) >> 7) * 255f / 200f);
    }

    /**
     * @param newStroke the new value for the stroke property
     */
    public void setStroke(final PaintServer newStroke) {
        this.stroke = newStroke;
    }

    /**
     * @return the context's stroke property value
     */
    public PaintServer getStroke() {
        return stroke;
    }

    /**
     * @param newStrokeOpacity the new value for the stroke opacity property.
     *         The input value is clamped to the [0, 1] range.
     */
    public void setStrokeOpacity(float newStrokeOpacity) {
        if (newStrokeOpacity < 0) {
            newStrokeOpacity = 0;
        } else if (newStrokeOpacity > 1) {
            newStrokeOpacity = 1;
        }
        
        pack &= ~STROKE_OPACITY_MASK;
        pack |= ((((int) (newStrokeOpacity * 200)) << 15) & STROKE_OPACITY_MASK);
    }

    /**
     * @return the context's stroke opacity property value
     */
    public float getStrokeOpacity() {
        return ((pack & STROKE_OPACITY_MASK) >> 15) / 200.0f;
    }

    /**
     * @return the context's stroke opacity property value
     */
    public int getStrokeOpacityImpl() {
        return (int) (((pack & STROKE_OPACITY_MASK) >> 15) * 255f / 200f);
    }

    /**
     * @param newColor the new value for the color property
     */
    public void setColor(final RGB newColor) {
        this.color = newColor;
    }

    /**
     * @return the context's color property value
     */
    public RGB getColor() {
        return this.color;
    }

    /**
     * @param newFillRule the new value for the fill rule property
     */
    public void setFillRule(final int newFillRule) {
        if (newFillRule == WIND_EVEN_ODD) {
            pack |= FILL_RULE_MASK;
        } else {
            pack &= ~FILL_RULE_MASK;
        }
    }

    /**
     * @return the context's fill rule property value
     */
    public int getFillRule() {
        if ((pack & FILL_RULE_MASK) == FILL_RULE_MASK) {
            return WIND_EVEN_ODD;
        }
        return WIND_NON_ZERO;
    }

    /**
     * @return the context's stroke dash array property value
     */
    public float[] getStrokeDashArray() {
        if (strokeDashArray == null) {
            return null;
        }

        float[] sda = new float[strokeDashArray.length];
        for (int i = 0; i < sda.length; i++) {
            sda[i] = strokeDashArray[i] / 65536.0f;
        }

        return sda;
    }

    /**
     * @param newStrokeDashArray the new stroke dash array property value
     */
    public void setStrokeDashArray(final float[] newStrokeDashArray) {
        if (newStrokeDashArray == null) {
            strokeDashArray = null;
        } else {
            strokeDashArray = new int[newStrokeDashArray.length];
            for (int i = 0; i < strokeDashArray.length; i++) {
                strokeDashArray[i] = (int) (newStrokeDashArray[i] * 65536);
            }
        }
    }

    /**
     * @return the context's stroke line cap property value
     */
    public int getStrokeLineCap() {
        switch (pack & STROKE_LINE_CAP_MASK) {
        case CAP_BUTT_IMPL:
            return CAP_BUTT;
        case CAP_ROUND_IMPL:
            return CAP_ROUND;
        default:
            return CAP_SQUARE;
        }
    }

    /**
     * @param newStrokeLineCap the new stroke line cap property value
     */
    public void setStrokeLineCap(final int newStrokeLineCap) {
        // Clear stroke-linecap
        pack &= ~STROKE_LINE_CAP_MASK;

        switch (newStrokeLineCap) {
        case CAP_BUTT:
            pack |= CAP_BUTT_IMPL;
            break;
        case CAP_ROUND:
            pack |= CAP_ROUND_IMPL;
            break;
        default:
            pack |= CAP_SQUARE_IMPL;
            break;
        }
    }

    /**
     * @return the context's stroke line join property value
     */
    public int getStrokeLineJoin() {
        switch (pack & STROKE_LINE_JOIN_MASK) {
        case JOIN_MITER_IMPL:
            return JOIN_MITER;
        case JOIN_ROUND_IMPL:
            return JOIN_ROUND;
        default:
            return JOIN_BEVEL;
        }
    }

    /**
     * @param newStrokeLineJoin the new stroke line join property value
     */
    public void setStrokeLineJoin(final int newStrokeLineJoin) {
        // Clear stroke-linejoin
        pack &= ~STROKE_LINE_JOIN_MASK;

        switch (newStrokeLineJoin) {
        case JOIN_MITER:
            pack |= JOIN_MITER_IMPL;
            break;
        case JOIN_ROUND:
            pack |= JOIN_ROUND_IMPL;
            break;
        default:
            pack |= JOIN_BEVEL_IMPL;
            break;
        }
    }

    /**
     * @return the context's stroke width property value
     */
    public float getStrokeWidth() {
        return strokeWidth / 65536.0f;
    }

    /**
     * @param newStrokeWidth the new stroke width property value
     */
    public void setStrokeWidth(final float newStrokeWidth) {
        strokeWidth = (int) (newStrokeWidth * 65536);
    }

    /**
     * @return the context's stroke miter limit property value
     */
    public float getStrokeMiterLimit() {
        return strokeMiterLimit / 65536.0f;
    }

    /**
     * @param newStrokeMiterLimit the new stroke miter limit property value
     */
    public void setStrokeMiterLimit(final float newStrokeMiterLimit) {
        strokeMiterLimit = (int) (newStrokeMiterLimit * 65536);
    }

    /**
     * @return the context's stroke dash offset property
     */
    public float getStrokeDashOffset() {
        return strokeDashOffset / 65536.0f;
    }

    /**
     * @param newStrokeDashOffset the new stroke dash offset property value
     */
    public void setStrokeDashOffset(final float newStrokeDashOffset) {
        strokeDashOffset = (int) (newStrokeDashOffset * 65536);
    }

    /**
     * @param newVisibility the new visibility property value
     */
    public void setVisibility(final boolean newVisibility) {
        if (newVisibility) {
            pack |= VISIBILITY_MASK;
        } else {
            pack &= ~VISIBILITY_MASK;
        }
    }

    /**
     * @return the context's visibility
     */
    public boolean getVisibility() {
        return ((pack & VISIBILITY_MASK) == VISIBILITY_MASK);
    }

    /**
     * @param newDisplay the new value for the display property
     */
    public void setDisplay(final boolean newDisplay) {
        if (newDisplay) {
            pack |= DISPLAY_MASK;
        } else {
            pack &= ~DISPLAY_MASK;
        }
    }

    /**
     * @return the context' display property value
     */
    public boolean getDisplay() {
        return ((pack & DISPLAY_MASK) == DISPLAY_MASK);
    }

    /**
     * @return the context's set of font families
     */
    public String[] getFontFamily() {
        return fontFamily;
    }

    /**
     * @param newFontFamily the new set of font families to use for font
     *        matching.
     */
    public void setFontFamily(final String[] newFontFamily) {
        this.fontFamily = newFontFamily;
    }

    /**
     * @return the context's font size property
     */
    public float getFontSize() {
        return fontSize;
    }

    /**
     * @param newFontSize the new value for the font size property
     */
    public void setFontSize(final float newFontSize) {
        this.fontSize = newFontSize;
    }

    /**
     * @return the context's font style property value
     */
    public int getFontStyle() {
        switch(pack & FONT_STYLE_MASK) {
        case FONT_STYLE_NORMAL_IMPL:
            return FONT_STYLE_NORMAL;
        case FONT_STYLE_ITALIC_IMPL:
            return FONT_STYLE_ITALIC;
        default:
            return FONT_STYLE_OBLIQUE;
        }
    }

    /**
     * @param newFontStyle the new value for the font style property
     */
    public void setFontStyle(final int newFontStyle) {
        // Clear font-style.
        pack &= ~FONT_STYLE_MASK;

        switch(newFontStyle) {
        case FONT_STYLE_NORMAL:
            pack |= FONT_STYLE_NORMAL_IMPL;
            break;
        case FONT_STYLE_ITALIC:
            pack |= FONT_STYLE_ITALIC_IMPL;
            break;
        default:
            pack |= FONT_STYLE_OBLIQUE_IMPL;
            break;
        }
    }

    /**
     * @return the current context's font weight property
     */
    public int getFontWeight() {
        switch(pack & FONT_WEIGHT_MASK) {
        case FONT_WEIGHT_100_IMPL:
            return FONT_WEIGHT_100;
        case FONT_WEIGHT_200_IMPL:
            return FONT_WEIGHT_200;
        case FONT_WEIGHT_300_IMPL:
            return FONT_WEIGHT_300;
        case FONT_WEIGHT_400_IMPL:
            return FONT_WEIGHT_400;
        case FONT_WEIGHT_500_IMPL:
            return FONT_WEIGHT_500;
        case FONT_WEIGHT_600_IMPL:
            return FONT_WEIGHT_600;
        case FONT_WEIGHT_700_IMPL:
            return FONT_WEIGHT_700;
        case FONT_WEIGHT_800_IMPL:
            return FONT_WEIGHT_800;
        default:
            return FONT_WEIGHT_900;
        }
    }

    /**
     * @param newFontWeight new font weight property value
     */
    public void setFontWeight(final int newFontWeight) {
        // Clear font-weight
        pack &= ~FONT_WEIGHT_MASK;

        switch(newFontWeight) {
        case FONT_WEIGHT_100:
            pack |= FONT_WEIGHT_100_IMPL;
            break;
        case FONT_WEIGHT_200:
            pack |= FONT_WEIGHT_200_IMPL;
            break;
        case FONT_WEIGHT_300:
            pack |= FONT_WEIGHT_300_IMPL;
            break;
        case FONT_WEIGHT_400:
            pack |= FONT_WEIGHT_400_IMPL;
            break;
        case FONT_WEIGHT_500:
            pack |= FONT_WEIGHT_500_IMPL;
            break;
        case FONT_WEIGHT_600:
            pack |= FONT_WEIGHT_600_IMPL;
            break;
        case FONT_WEIGHT_700:
            pack |= FONT_WEIGHT_700_IMPL;
            break;
        case FONT_WEIGHT_800:
            pack |= FONT_WEIGHT_800_IMPL;
            break;
        default:
            pack |= FONT_WEIGHT_900_IMPL;
            break;
        }
    }

    /**
     * @return the text anchor property
     */
    public int getTextAnchor() {
        switch(pack & TEXT_ANCHOR_MASK) {
        case TEXT_ANCHOR_START_IMPL:
            return TEXT_ANCHOR_START;
        case TEXT_ANCHOR_MIDDLE_IMPL:
            return TEXT_ANCHOR_MIDDLE;
        default:
            return TEXT_ANCHOR_END;
        }
    }

    /**
     * @param newTextAnchor the new text anchor property value
     */
    public void setTextAnchor(final int newTextAnchor) {
        // Clear text-anchor.
        pack &= ~TEXT_ANCHOR_MASK;

        switch(newTextAnchor) {
        case TEXT_ANCHOR_START:
            pack |= TEXT_ANCHOR_START_IMPL;
            break;
        case TEXT_ANCHOR_MIDDLE:
            pack |= TEXT_ANCHOR_MIDDLE_IMPL;
            break;
        default:
            pack |= TEXT_ANCHOR_END_IMPL;
            break;
        }
    }

    /**
     * @param newOpacity the new value for the opacity property. The 
     *        input value is clamped to the [0, 1] range.
     *
     * @param newOpacity the new opacity value to set
     */
    public void setOpacity(float newOpacity) {
        if (newOpacity < 0) {
            newOpacity = 0;
        } else if (newOpacity > 1) {
            newOpacity = 1;
        }
        
        pack2 &= ~OPACITY_MASK;
        pack2 |= (((int) (newOpacity * 200)) & OPACITY_MASK);
    }

    /**
     * @return the context's opacity property value
     */
    public float getOpacity() {
        return (pack2 & OPACITY_MASK) / 200.0f;
    }

    /**
     * @return the context's opacity property value
     */
    int getOpacityImpl() {
        return (int) ((pack2 & OPACITY_MASK) * 255f / 200f);
    }

    /**
     * @return a copy of this <tt>RenderContext</tt>
     */
    public RenderContext save() {
        return new RenderContext(this);
    }

    /**
     * Implemnetation: Handles negative strokeDashOffset
     *
     * @return a positive strokeDashOffset.
     */
    int computeStrokeDashOffset() {
        if (strokeDashArray == null) {
            // The stroke dash offset does not matter, simply return 0
            return 0;
        }

        if (strokeDashOffset >= 0) {
            return strokeDashOffset;
        }

        int length = 0;
        for (int i = 0; i < strokeDashArray.length; i++) {
            length += strokeDashArray[i];
        }

        if (length <= 0) {
            return 0;
        }

        int sdo = strokeDashOffset;
        while (sdo < 0) {
            sdo += length;
        }

        return sdo;
    }
}