FileDocCategorySizeDatePackage
GlyphLayout.javaAPI DocphoneME MR2 API (J2ME)12257Wed May 02 18:00:36 BST 2007com.sun.perseus.model

GlyphLayout.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.model;

import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.TextRenderingProperties;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;

/**
 * <code>GlyphLayout</code> is used to represent successing text 'chunks' in
 * a <code>Text</code> layout of glyphs. The notion of 'chunk' is that of the
 * SVG specification.
 *
 * @version $Id: GlyphLayout.java,v 1.4 2006/06/29 10:47:31 ln156897 Exp $
 */
class GlyphLayout {
    /**
     * The overally layout's advance
     */
    protected float advance;

    /**
     * The chunk's x-axis position
     */
    protected float x;

    /**
     * The chunk's y-axis position
     */
    protected float y;

    /**
     * The first child
     */
    protected GlyphProxy firstChild;
        
    /**
     * The last child
     */
    protected GlyphProxy lastChild;

    /**
     * Previous sibling.
     */
    protected GlyphLayout prevSibling;

    /**
     * Next sibling
     */
    protected GlyphLayout nextSibling;

    /**
     * Owner Document, to get cache objects.
     */
    protected DocumentNode ownerDocument;

    /**
     * Used to scale dash array values.
     */         
    protected float[] helperDashArray;

    /**
     * @param doc the <tt>DocumentNode</tt> scope.
     */
    GlyphLayout(final DocumentNode doc) {
        this.ownerDocument = doc;
    }
        
    /**
     * @param bbox the bounding box to which this node's bounding box should be
     *        appended. That bounding box is in the target coordinate space. It 
     *        may be null, in which case this node should create a new one.
     * @param t the transform from the node coordinate system to the coordinate
     *        system into which the bounds should be computed.
     * @return the bounding box of this node, in the target coordinate space, 
     */
    Box addBBox(Box bbox, final Transform t) {
        GlyphProxy gp = firstChild;
        while (gp != null) {
            ownerDocument.bboxGlyphTxf.setTransform(t);
            gp.applyTransform(ownerDocument.bboxGlyphTxf);
            bbox = gp.addNodeBBox(bbox, ownerDocument.bboxGlyphTxf);
            gp = gp.nextSibling;
        }
        return bbox;
    }
        
    /**
     * Add this GlyphLayout rendering tile to the input Tile.
     *
     * @param tile the Tile instance whose bounds should be expanded. May be
     *        null.
     * @param trp the TextRenderingProperties describing the nodes rendering 
     *        characteristics.
     * @param t the Transform to the requested tile space, from this node's user
     *        space.
     * @return the expanded tile.
     */
    final protected Tile addRenderingTile(Tile tile, 
                                          final TextRenderingProperties trp,
                                          final Transform t) {
        if (trp.getStroke() != null) {
            GlyphProxy gp = firstChild;
            while (gp != null) {
                ownerDocument.bboxGlyphTxf.setTransform(t);
                gp.applyTransform(ownerDocument.bboxGlyphTxf);
                tile = gp.addRenderingTile(tile, trp, t);
                gp = (GlyphProxy) gp.nextSibling;
            }
        } else {
            GlyphProxy gp = firstChild;
            float strokeWidth = trp.getStrokeWidth();
            while (gp != null) {
                ownerDocument.bboxGlyphTxf.setTransform(t);
                gp.applyTransform(ownerDocument.bboxGlyphTxf);
                trp.setStrokeWidth(strokeWidth / gp.proxied.emSquareScale);
                tile = gp.addRenderingTile(tile, trp, t);
                gp = (GlyphProxy) gp.nextSibling;
            }
        }
        return tile;
    }

    /**
     * Appends an element at the end of the list
     *
     * @param proxy the proxy to add to this <tt>GlyphLayout</tt>
     * @throws NullPointerException if the input argument is null.
     */
    public void add(final GlyphProxy proxy) {
        if (proxy == null) {
            throw new NullPointerException();
        }
            
        if (firstChild == null) {
            firstChild = proxy;
            lastChild = proxy;
            proxy.nextSibling = null;
            proxy.prevSibling = null;
        } else {
            lastChild.nextSibling = proxy;
            proxy.nextSibling = null;
            proxy.prevSibling = lastChild;
            lastChild = proxy;                
        }
    }

    /**
     * Applies the chunk's position adjustment due to the text anchor's
     * value.
     *
     * @param trp the <code>TextRenderingProperties</code> on which the 
     *        font-size property is defined.
     * @param tx the <code>Transform</code> to apply additional node 
     *        transforms to. This is guaranteed to no be null.
     */
    protected void applyTransform(final TextRenderingProperties trp, 
                                  final Transform tx) {
        // Position the text chunk on the current 
        // text position.
        tx.mTranslate(x, y);

        // Scale according to the fontSize
        float fontSize = trp.getFontSize();
        if (fontSize > 0) {
            tx.mScale(fontSize);
        }

        // Account for the text-anchor translation
        switch (trp.getTextAnchor()) {
        default:
            break;
        case TextNode.TEXT_ANCHOR_MIDDLE:
            tx.mTranslate(-advance / 2f, 0);
            break;
        case TextNode.TEXT_ANCHOR_END:
            tx.mTranslate(-advance, 0);
            break;
        }
    }

    /**
     * Applies the inverse of the chunk's position adjustment due to the
     * text anchor's value.
     *
     * @param trp the <code>TextRenderingProperties</code> on which the 
     *        font-size property is defined.
     * @param tx the <code>Transform</code> to apply the inverse transform
     *        to.
     */
    protected void applyInverseTransform(final TextRenderingProperties trp, 
                                         final Transform tx) {
        // Account for the text-anchor translation
        switch (trp.getTextAnchor()) {
        default:
            break;
        case TextNode.TEXT_ANCHOR_MIDDLE:
            tx.mTranslate(advance / 2f, 0);
            break;
        case TextNode.TEXT_ANCHOR_END:
            tx.mTranslate(advance, 0);
            break;
        }

        // Scale according to the fontSize
        float fontSize = trp.getFontSize();
        if (fontSize > 0) {
            tx.mScale(1 / fontSize);
        }

        // Position the text chunk on the current 
        // text position.
        tx.mTranslate(-x, -y);
    }

    /**
     * Fills text.
     *
     * @param rg the <code>RenderGraphics</code> where the node should paint
     *        itself.
     * @param tx the rendering transform.
     */
    void fillText(final RenderGraphics rg, final Transform tx) {
        GlyphProxy c = firstChild;            
        while (c != null) {
            ownerDocument.paintGlyphTxf.setTransform(tx);
            c.applyTransform(ownerDocument.paintGlyphTxf);
            rg.setTransform(ownerDocument.paintGlyphTxf);
            if (c.proxied.d != null) {
                rg.fill(c.proxied.d);
            }
            c = c.nextSibling;
        }
    }

    /**
     * Draws text.
     *
     * @param rg the <code>RenderGraphics</code> where the node should paint
     *        itself.
     * @param tx the rendering transform.
     */
    void drawText(final RenderGraphics rg, final Transform tx) {
        GlyphProxy c = firstChild;
        float strokeWidth = rg.getStrokeWidth();
        float dashOffset = rg.getStrokeDashOffset();
        float[] dashArray = rg.getStrokeDashArray();
        if ((dashArray != null) && 
                ((helperDashArray == null) || 
                 (helperDashArray.length != dashArray.length))) {
            helperDashArray = new float[dashArray.length];
        }
        float lastEMSquareScale = 0;
        while (c != null) {
            ownerDocument.paintGlyphTxf.setTransform(tx);
            c.applyTransform(ownerDocument.paintGlyphTxf);
            rg.setTransform(ownerDocument.paintGlyphTxf);
            if (lastEMSquareScale != c.proxied.emSquareScale) {
                rg.setStrokeWidth(strokeWidth / c.proxied.emSquareScale);
                if (dashArray != null) {
                    float emSquareScale = c.proxied.emSquareScale;
                    float lengthSum = 0;
                    for (int i = 0; i < dashArray.length; ++i) {
                        helperDashArray[i] = dashArray[i] / emSquareScale;
                        lengthSum += dashArray[i];
                    }
                    float reducedDashOffset = dashOffset;
                    if (lengthSum > 0) {
                        int r = (int)(dashOffset / lengthSum);
                        reducedDashOffset -= r * lengthSum;
                    }
                    rg.setStrokeDashArray(helperDashArray);
                    rg.setStrokeDashOffset(reducedDashOffset / emSquareScale);
                }
                lastEMSquareScale = c.proxied.emSquareScale;
            }
            
            if (c.proxied.d != null) {
                rg.draw(c.proxied.d);
            }
            c = c.nextSibling;
        }
            
        // Restore stroke-width property.
        rg.setStrokeWidth(strokeWidth);
        
        if (dashArray != null) {
            // Restore dash array & offset
            rg.setStrokeDashArray(dashArray);
            rg.setStrokeDashOffset(dashOffset);
        }
    }

    /**
     * Returns true if this node is hit by the input point. The input point
     * is in viewport space. By default, a node is not hit, not
     * matter what the input coordinate is.
     *  
     * @param pt the x/y coordinate. Should never be null and be of size two. If
     * not, the behavior is unspecified.  The x/y coordinate is in the node's
     * user space.
     * @param trp the <tt>TextRenderingProperties</tt> containing the properties
     * applicable to the hit detection operation. This is used because the same
     * node may be referenced multiple times by different proxies.
     *
     * @return true if the node is hit by the input point. 
     * @see #nodeHitAt
     */
    protected boolean isHitVP(final float[] pt, 
                              final TextRenderingProperties trp,
                              final Transform chunkTxf) {
        GlyphProxy c = lastChild;            
        while (c != null) {
            ownerDocument.hitGlyphTxf.setTransform(1, 0, 0, 1, 0, 0);
            c.applyInverseTransform(ownerDocument.hitGlyphTxf);
            ownerDocument.hitGlyphTxf.mMultiply(chunkTxf);
            ownerDocument.hitGlyphTxf.transformPoint(pt, ownerDocument.upt);
            if (c.isHit(ownerDocument.upt, trp)) {
                return true;
            }
            c = c.prevSibling;
        }

        return false;
    }

    /**
     * @return the string description of this layout.
     */
    public String toString() {
        return "GlyphLayout[x=" + x + ", y=" + y 
            + " advance=" + advance + "]";
    }
}