/* * $RCSfile: GlyphLayout.java,v $ * * Copyright 1990-2009 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.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; } /** * 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 + "]"; } }