001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.model;
027:
028: import com.sun.perseus.j2d.Box;
029: import com.sun.perseus.j2d.TextRenderingProperties;
030: import com.sun.perseus.j2d.RenderGraphics;
031: import com.sun.perseus.j2d.Tile;
032: import com.sun.perseus.j2d.Transform;
033:
034: /**
035: * <code>GlyphLayout</code> is used to represent successing text 'chunks' in
036: * a <code>Text</code> layout of glyphs. The notion of 'chunk' is that of the
037: * SVG specification.
038: *
039: * @version $Id: GlyphLayout.java,v 1.4 2006/06/29 10:47:31 ln156897 Exp $
040: */
041: class GlyphLayout {
042: /**
043: * The overally layout's advance
044: */
045: protected float advance;
046:
047: /**
048: * The chunk's x-axis position
049: */
050: protected float x;
051:
052: /**
053: * The chunk's y-axis position
054: */
055: protected float y;
056:
057: /**
058: * The first child
059: */
060: protected GlyphProxy firstChild;
061:
062: /**
063: * The last child
064: */
065: protected GlyphProxy lastChild;
066:
067: /**
068: * Previous sibling.
069: */
070: protected GlyphLayout prevSibling;
071:
072: /**
073: * Next sibling
074: */
075: protected GlyphLayout nextSibling;
076:
077: /**
078: * Owner Document, to get cache objects.
079: */
080: protected DocumentNode ownerDocument;
081:
082: /**
083: * Used to scale dash array values.
084: */
085: protected float[] helperDashArray;
086:
087: /**
088: * @param doc the <tt>DocumentNode</tt> scope.
089: */
090: GlyphLayout(final DocumentNode doc) {
091: this .ownerDocument = doc;
092: }
093:
094: /**
095: * @param bbox the bounding box to which this node's bounding box should be
096: * appended. That bounding box is in the target coordinate space. It
097: * may be null, in which case this node should create a new one.
098: * @param t the transform from the node coordinate system to the coordinate
099: * system into which the bounds should be computed.
100: * @return the bounding box of this node, in the target coordinate space,
101: */
102: Box addBBox(Box bbox, final Transform t) {
103: GlyphProxy gp = firstChild;
104: while (gp != null) {
105: ownerDocument.bboxGlyphTxf.setTransform(t);
106: gp.applyTransform(ownerDocument.bboxGlyphTxf);
107: bbox = gp.addNodeBBox(bbox, ownerDocument.bboxGlyphTxf);
108: gp = gp.nextSibling;
109: }
110: return bbox;
111: }
112:
113: /**
114: * Add this GlyphLayout rendering tile to the input Tile.
115: *
116: * @param tile the Tile instance whose bounds should be expanded. May be
117: * null.
118: * @param trp the TextRenderingProperties describing the nodes rendering
119: * characteristics.
120: * @param t the Transform to the requested tile space, from this node's user
121: * space.
122: * @return the expanded tile.
123: */
124: final protected Tile addRenderingTile(Tile tile,
125: final TextRenderingProperties trp, final Transform t) {
126: if (trp.getStroke() != null) {
127: GlyphProxy gp = firstChild;
128: while (gp != null) {
129: ownerDocument.bboxGlyphTxf.setTransform(t);
130: gp.applyTransform(ownerDocument.bboxGlyphTxf);
131: tile = gp.addRenderingTile(tile, trp, t);
132: gp = (GlyphProxy) gp.nextSibling;
133: }
134: } else {
135: GlyphProxy gp = firstChild;
136: float strokeWidth = trp.getStrokeWidth();
137: while (gp != null) {
138: ownerDocument.bboxGlyphTxf.setTransform(t);
139: gp.applyTransform(ownerDocument.bboxGlyphTxf);
140: trp.setStrokeWidth(strokeWidth
141: / gp.proxied.emSquareScale);
142: tile = gp.addRenderingTile(tile, trp, t);
143: gp = (GlyphProxy) gp.nextSibling;
144: }
145: }
146: return tile;
147: }
148:
149: /**
150: * Appends an element at the end of the list
151: *
152: * @param proxy the proxy to add to this <tt>GlyphLayout</tt>
153: * @throws NullPointerException if the input argument is null.
154: */
155: public void add(final GlyphProxy proxy) {
156: if (proxy == null) {
157: throw new NullPointerException();
158: }
159:
160: if (firstChild == null) {
161: firstChild = proxy;
162: lastChild = proxy;
163: proxy.nextSibling = null;
164: proxy.prevSibling = null;
165: } else {
166: lastChild.nextSibling = proxy;
167: proxy.nextSibling = null;
168: proxy.prevSibling = lastChild;
169: lastChild = proxy;
170: }
171: }
172:
173: /**
174: * Applies the chunk's position adjustment due to the text anchor's
175: * value.
176: *
177: * @param trp the <code>TextRenderingProperties</code> on which the
178: * font-size property is defined.
179: * @param tx the <code>Transform</code> to apply additional node
180: * transforms to. This is guaranteed to no be null.
181: */
182: protected void applyTransform(final TextRenderingProperties trp,
183: final Transform tx) {
184: // Position the text chunk on the current
185: // text position.
186: tx.mTranslate(x, y);
187:
188: // Scale according to the fontSize
189: float fontSize = trp.getFontSize();
190: if (fontSize > 0) {
191: tx.mScale(fontSize);
192: }
193:
194: // Account for the text-anchor translation
195: switch (trp.getTextAnchor()) {
196: default:
197: break;
198: case TextNode.TEXT_ANCHOR_MIDDLE:
199: tx.mTranslate(-advance / 2f, 0);
200: break;
201: case TextNode.TEXT_ANCHOR_END:
202: tx.mTranslate(-advance, 0);
203: break;
204: }
205: }
206:
207: /**
208: * Applies the inverse of the chunk's position adjustment due to the
209: * text anchor's value.
210: *
211: * @param trp the <code>TextRenderingProperties</code> on which the
212: * font-size property is defined.
213: * @param tx the <code>Transform</code> to apply the inverse transform
214: * to.
215: */
216: protected void applyInverseTransform(
217: final TextRenderingProperties trp, final Transform tx) {
218: // Account for the text-anchor translation
219: switch (trp.getTextAnchor()) {
220: default:
221: break;
222: case TextNode.TEXT_ANCHOR_MIDDLE:
223: tx.mTranslate(advance / 2f, 0);
224: break;
225: case TextNode.TEXT_ANCHOR_END:
226: tx.mTranslate(advance, 0);
227: break;
228: }
229:
230: // Scale according to the fontSize
231: float fontSize = trp.getFontSize();
232: if (fontSize > 0) {
233: tx.mScale(1 / fontSize);
234: }
235:
236: // Position the text chunk on the current
237: // text position.
238: tx.mTranslate(-x, -y);
239: }
240:
241: /**
242: * Fills text.
243: *
244: * @param rg the <code>RenderGraphics</code> where the node should paint
245: * itself.
246: * @param tx the rendering transform.
247: */
248: void fillText(final RenderGraphics rg, final Transform tx) {
249: GlyphProxy c = firstChild;
250: while (c != null) {
251: ownerDocument.paintGlyphTxf.setTransform(tx);
252: c.applyTransform(ownerDocument.paintGlyphTxf);
253: rg.setTransform(ownerDocument.paintGlyphTxf);
254: if (c.proxied.d != null) {
255: rg.fill(c.proxied.d);
256: }
257: c = c.nextSibling;
258: }
259: }
260:
261: /**
262: * Draws text.
263: *
264: * @param rg the <code>RenderGraphics</code> where the node should paint
265: * itself.
266: * @param tx the rendering transform.
267: */
268: void drawText(final RenderGraphics rg, final Transform tx) {
269: GlyphProxy c = firstChild;
270: float strokeWidth = rg.getStrokeWidth();
271: float dashOffset = rg.getStrokeDashOffset();
272: float[] dashArray = rg.getStrokeDashArray();
273: if ((dashArray != null)
274: && ((helperDashArray == null) || (helperDashArray.length != dashArray.length))) {
275: helperDashArray = new float[dashArray.length];
276: }
277: float lastEMSquareScale = 0;
278: while (c != null) {
279: ownerDocument.paintGlyphTxf.setTransform(tx);
280: c.applyTransform(ownerDocument.paintGlyphTxf);
281: rg.setTransform(ownerDocument.paintGlyphTxf);
282: if (lastEMSquareScale != c.proxied.emSquareScale) {
283: rg
284: .setStrokeWidth(strokeWidth
285: / c.proxied.emSquareScale);
286: if (dashArray != null) {
287: float emSquareScale = c.proxied.emSquareScale;
288: float lengthSum = 0;
289: for (int i = 0; i < dashArray.length; ++i) {
290: helperDashArray[i] = dashArray[i]
291: / emSquareScale;
292: lengthSum += dashArray[i];
293: }
294: float reducedDashOffset = dashOffset;
295: if (lengthSum > 0) {
296: int r = (int) (dashOffset / lengthSum);
297: reducedDashOffset -= r * lengthSum;
298: }
299: rg.setStrokeDashArray(helperDashArray);
300: rg.setStrokeDashOffset(reducedDashOffset
301: / emSquareScale);
302: }
303: lastEMSquareScale = c.proxied.emSquareScale;
304: }
305:
306: if (c.proxied.d != null) {
307: rg.draw(c.proxied.d);
308: }
309: c = c.nextSibling;
310: }
311:
312: // Restore stroke-width property.
313: rg.setStrokeWidth(strokeWidth);
314:
315: if (dashArray != null) {
316: // Restore dash array & offset
317: rg.setStrokeDashArray(dashArray);
318: rg.setStrokeDashOffset(dashOffset);
319: }
320: }
321:
322: /**
323: * Returns true if this node is hit by the input point. The input point
324: * is in viewport space. By default, a node is not hit, not
325: * matter what the input coordinate is.
326: *
327: * @param pt the x/y coordinate. Should never be null and be of size two. If
328: * not, the behavior is unspecified. The x/y coordinate is in the node's
329: * user space.
330: * @param trp the <tt>TextRenderingProperties</tt> containing the properties
331: * applicable to the hit detection operation. This is used because the same
332: * node may be referenced multiple times by different proxies.
333: *
334: * @return true if the node is hit by the input point.
335: * @see #nodeHitAt
336: */
337: protected boolean isHitVP(final float[] pt,
338: final TextRenderingProperties trp, final Transform chunkTxf) {
339: GlyphProxy c = lastChild;
340: while (c != null) {
341: ownerDocument.hitGlyphTxf.setTransform(1, 0, 0, 1, 0, 0);
342: c.applyInverseTransform(ownerDocument.hitGlyphTxf);
343: ownerDocument.hitGlyphTxf.mMultiply(chunkTxf);
344: ownerDocument.hitGlyphTxf.transformPoint(pt,
345: ownerDocument.upt);
346: if (c.isHit(ownerDocument.upt, trp)) {
347: return true;
348: }
349: c = c.prevSibling;
350: }
351:
352: return false;
353: }
354:
355: /**
356: * @return the string description of this layout.
357: */
358: public String toString() {
359: return "GlyphLayout[x=" + x + ", y=" + y + " advance="
360: + advance + "]";
361: }
362: }
|