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:
027: package com.sun.perseus.model;
028:
029: import com.sun.perseus.j2d.RenderGraphics;
030: import com.sun.perseus.j2d.TextRenderingProperties;
031:
032: import com.sun.perseus.util.SVGConstants;
033:
034: import org.w3c.dom.DOMException;
035:
036: import com.sun.perseus.j2d.Box;
037: import com.sun.perseus.j2d.Path;
038: import com.sun.perseus.j2d.PathSupport;
039: import com.sun.perseus.j2d.Tile;
040: import com.sun.perseus.j2d.Transform;
041:
042: /**
043: * A <tt>Glyph</tt> node corresponds to an SVG <tt><glyph></tt>
044: * and <tt><missing-glyph></tt> elements. If the unicode value is null,
045: * then a <tt>Glyph</tt> represents a <tt><missing-glyph></tt>
046: * and its length is assumed to be 1.
047: * <br />
048: *
049: * @version $Id: Glyph.java,v 1.11 2006/06/29 10:47:31 ln156897 Exp $
050: */
051: public class Glyph extends ElementNode {
052: /**
053: * By default, use the parent Font's advance
054: */
055: public static final float DEFAULT_HORIZONTAL_ADVANCE_X = -1;
056:
057: /**
058: * The Path representing the outline of this glyph
059: */
060: protected Path d;
061:
062: /**
063: * This glyph's horizontal advance.
064: * A negative value means that the parent Font value should be used
065: */
066: protected float horizontalAdvanceX = DEFAULT_HORIZONTAL_ADVANCE_X;
067:
068: /**
069: * The computed glyph's horizontal advance. If this glyphs
070: * advance is set, then this is equal to the glyph's advance.
071: * Otherwise, this is the grand-parent font's advance if there
072: * is one.
073: */
074: protected float computedHorizontalAdvanceX = -1;
075:
076: /**
077: * The computed glyph origin.
078: */
079: protected float origin = 0;
080:
081: /**
082: * The em square scale factor.
083: */
084: protected float emSquareScale = 1f;
085:
086: /**
087: * The unicode character, or sequence of unicode character, that
088: * this glyph represents. If null, then this Glyph can be used to
089: * represent missing glyphs for arbitrary unicode values.
090: */
091: protected String unicode;
092:
093: /**
094: * Set of glyph names. May be null.
095: */
096: protected String[] glyphName;
097:
098: /**
099: * This glyph's local name.
100: */
101: protected String localName = SVGConstants.SVG_GLYPH_TAG;
102:
103: /**
104: * Constructor.
105: *
106: * @param ownerDocument this element's owner <code>DocumentNode</code>
107: */
108: public Glyph(final DocumentNode ownerDocument) {
109: this (ownerDocument, SVGConstants.SVG_GLYPH_TAG);
110: }
111:
112: /**
113: * Constructor.
114: *
115: * @param ownerDocument this element's owner <code>DocumentNode</code>
116: * @param localName this element's local name. One of
117: * 'glyph' or 'missing-glyph'.
118: */
119: public Glyph(final DocumentNode ownerDocument,
120: final String localName) {
121: super (ownerDocument);
122:
123: if (SVGConstants.SVG_GLYPH_TAG == localName
124: || SVGConstants.SVG_MISSING_GLYPH_TAG == localName) {
125: this .localName = localName;
126: } else {
127: throw new IllegalArgumentException("Illegal Glyph name : "
128: + localName);
129: }
130: }
131:
132: /**
133: * @return the SVGConstants.SVG_GLYPH_TAG value
134: */
135:
136: public String getLocalName() {
137: return localName;
138: }
139:
140: /**
141: * Used by <code>DocumentNode</code> to create a new instance from
142: * a prototype <code>Glyph</code>.
143: *
144: * @param doc the <code>DocumentNode</code> for which a new node is
145: * should be created.
146: * @return a new <code>Glyph</code> for the requested document.
147: */
148: public ElementNode newInstance(final DocumentNode doc) {
149: return new Glyph(doc, localName);
150: }
151:
152: /**
153: * Sets the emSquareScale. See the Font's list method adding
154: * children: when the FontFace is set, the Font sets the em square
155: * transform on all its children. When a Glyph is
156: * set, and if there is a FontFace already, the em square transform
157: * is set as well.
158: *
159: * @param newEmSquareScale the new value for this glyph's em square
160: * scale, i.e., the scale between the glyph's em square and
161: * the text's coordinate system.
162: */
163: public void setEmSquareScale(final float newEmSquareScale) {
164: if (newEmSquareScale == emSquareScale) {
165: return;
166: }
167: emSquareScale = newEmSquareScale;
168: }
169:
170: /**
171: * Sets the horizontal advance along the x axis. This is
172: * in the em square coordinate space.
173: *
174: * @param newHorizontalAdvanceX the new advance along the x axis
175: */
176: public void setHorizontalAdvanceX(final float newHorizontalAdvanceX) {
177: if (newHorizontalAdvanceX == horizontalAdvanceX) {
178: return;
179: }
180:
181: this .horizontalAdvanceX = newHorizontalAdvanceX;
182: if (newHorizontalAdvanceX == -1 && parent != null
183: && parent instanceof Font) {
184: computedHorizontalAdvanceX = ((Font) parent)
185: .getHorizontalAdvanceX();
186: origin = ((Font) parent).getHorizontalOriginX();
187: } else {
188: computedHorizontalAdvanceX = newHorizontalAdvanceX;
189: }
190: }
191:
192: /**
193: * Override set parent to capture the default advance
194: *
195: * @param parent this node's new parent
196: */
197: public void setParent(final ModelNode parent) {
198: super .setParent(parent);
199:
200: if (parent != null && parent instanceof Font) {
201: if (horizontalAdvanceX == -1) {
202: computedHorizontalAdvanceX = ((Font) parent)
203: .getHorizontalAdvanceX();
204: }
205: origin = ((Font) parent).getHorizontalOriginX();
206: } else {
207: origin = 0;
208: }
209: }
210:
211: /**
212: * @param tx the <code>Transform</code> to add node transform to.
213: */
214: protected void applyTransform(final Transform tx) {
215: tx.mScale(emSquareScale, -emSquareScale);
216: tx.mTranslate(-origin, 0);
217: }
218:
219: /**
220: * @param tx the inverse <code>Transform</code> between this node and its
221: * parent space.
222: */
223: protected void applyInverseTransform(final Transform tx) {
224: tx.mTranslate(origin, 0);
225: tx.mScale(1 / emSquareScale, -1 / emSquareScale);
226: }
227:
228: /**
229: * @return the horizontal advance along the x axis for this
230: * glyph. This advance is in the em square coordinate space.
231: * To get the advance in text coordinate space, use the
232: * getTextHorizontalAdvanceX method.
233: * Note that if the horizontalAdvanceX of this glyph is -1,
234: * this method returns the horizontal advance of the parent
235: * FontFace element if one is set.
236: *
237: * @see #getTextHorizontalAdvanceX
238: */
239: public float getHorizontalAdvanceX() {
240: return computedHorizontalAdvanceX;
241: }
242:
243: /**
244: * @return the advance along the x axis in the text
245: * coordinate space, i.e., after applying the
246: * emSquare transform.
247: */
248: public float getTextHorizontalAdvanceX() {
249: return computedHorizontalAdvanceX * emSquareScale;
250: }
251:
252: /**
253: * @return this glyph's geometry, as a <tt>Path</tt>.
254: * The returned path is in the Font's coordinate system.
255: */
256: public Path getD() {
257: return d;
258: }
259:
260: /**
261: * @param newD the new <tt>Path</tt> for this <tt>Glyph</tt>
262: */
263: public void setD(final Path newD) {
264: if (equal(newD, d)) {
265: return;
266: }
267: this .d = newD;
268: }
269:
270: /**
271: * Returns this glyph's name(s)
272: *
273: * @return thsi glyph's name set. Null if none.
274: */
275: public String[] getGlyphName() {
276: return glyphName;
277: }
278:
279: /**
280: * Sets this glyph's name(s). May be null.
281: *
282: * @param glyphName the new name for the glyph
283: */
284: public void setGlyphName(final String[] glyphName) {
285: if (equal(glyphName, this .glyphName)) {
286: return;
287: }
288: this .glyphName = glyphName;
289: }
290:
291: /**
292: * @param tile the Tile instance whose bounds should be appended.
293: * @param trp the TextRenderingProperties describing the nodes rendering
294: * characteristics.
295: * @param t the Transform to the requested tile space, from this node's user
296: * space.
297: * @return the expanded tile.
298: */
299: protected Tile addRenderingTile(Tile tile,
300: final TextRenderingProperties trp, final Transform t) {
301: if (d != null) {
302: if (trp.getStroke() == null) {
303: // No stroking on the shape, we can use the geometrical bounding
304: // box.
305: Box renderingBox = addNodeBBox(null, t);
306: if (renderingBox != null) {
307: if (tile == null) {
308: tile = new Tile();
309: tile.snapBox(renderingBox);
310: } else {
311: tile.addSnapBox(renderingBox);
312: }
313: }
314: } else {
315: // Need to account for stroking, with a more costly operation
316: // to compute the stroked bounds.
317: Object strokedPath = PathSupport.getStrokedPath(d, trp);
318: if (tile == null) {
319: tile = new Tile();
320: PathSupport.computeStrokedPathTile(tile,
321: strokedPath, t);
322: } else {
323: Tile st = new Tile();
324: PathSupport.computeStrokedPathTile(st, strokedPath,
325: t);
326: tile.addTile(st);
327: }
328: }
329: }
330: return tile;
331: }
332:
333: /**
334: * @return this <tt>Glyph</tt>'s unicode value
335: */
336: public String getUnicode() {
337: return unicode;
338: }
339:
340: /**
341: * @param newUnicode this <tt>Glyph</tt>'s new unicode value
342: */
343: public void setUnicode(final String newUnicode) {
344: if (equal(newUnicode, unicode)) {
345: return;
346: }
347: this .unicode = newUnicode;
348: }
349:
350: /**
351: * @return the number of characters represented by this glyph
352: */
353: public int getLength() {
354: if (unicode == null) {
355: return 1;
356: }
357:
358: return unicode.length();
359: }
360:
361: /**
362: * It is the responsibility of the caller to check that the
363: * requested index is between the unicode's value index range
364: * (i.e., between 0 and unicode.length())
365: *
366: * @param i the index of the unicode value for the given index.
367: * @return the character at the requested index
368: */
369: public int getCharAt(final int i) {
370: return unicode.charAt(i);
371: }
372:
373: /**
374: * A <tt>Glyph</tt> is hit if the <tt>Path</tt> it paints is
375: * hit by the given point.
376: *
377: * @param pt the x/y coordinate. Should never be null and be
378: * of size two. If not, the behavior is unspecified. The
379: * point is in the node's user space.
380: * @param trp the <tt>TextRenderingProperties</tt> containing the properties
381: * applicable to the hit detection operation. This is used
382: * because the same node may be referenced multiple times
383: * by different proxies.
384: * @return true if this node is hit at the given point. false otherwise.
385: */
386: public boolean isHit(final float[] pt,
387: final TextRenderingProperties trp) {
388: if (d == null) {
389: return false;
390: }
391:
392: // If the node is filled, see if the shape is hit
393: if (trp.getFill() != null) {
394: if (PathSupport.isHit(d, trp.getFillRule(), pt[0], pt[1])) {
395: return true;
396: }
397: }
398:
399: // Test detection on the edge if the stroke color
400: // is set.
401: if (trp.getStroke() != null) {
402: Object strokedPath = PathSupport.getStrokedPath(d, trp);
403: if (PathSupport.isStrokedPathHit(strokedPath, trp
404: .getFillRule(), pt[0], pt[1])) {
405: return true;
406: }
407: }
408:
409: return false;
410: }
411:
412: /**
413: * @param bbox the bounding box to which this node's bounding box should be
414: * appended. That bounding box is in the target coordinate space. It
415: * may be null, in which case this node should create a new one.
416: * @param t the transform from the node coordinate system to the coordinate
417: * system into which the bounds should be computed.
418: * @return the bounding box of this node, in the target coordinate space,
419: */
420: Box addNodeBBox(final Box bbox, final Transform t) {
421: return addShapeBBox(bbox, d, t);
422: }
423:
424: /**
425: * Supported traits: d, horiz-adv-x
426: *
427: * @param traitName the name of the trait which the element may support.
428: * @return true if this element supports the given trait in one of the
429: * trait accessor methods.
430: */
431: boolean supportsTrait(final String traitName) {
432: if (SVGConstants.SVG_D_ATTRIBUTE == traitName
433: || SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == traitName
434: || SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == traitName
435: || SVGConstants.SVG_UNICODE_ATTRIBUTE == traitName) {
436: return true;
437: }
438: return super .supportsTrait(traitName);
439: }
440:
441: /**
442: * Supported traits: d, horiz-adv-x
443: *
444: * @param name the requested trait name.
445: * @return the requested trait value, as a string.
446: *
447: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
448: * trait is not supported on this element or null.
449: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
450: * trait's computed value cannot be converted to a String (SVG Tiny only).
451: */
452: public String getTraitImpl(String name) throws DOMException {
453: if (SVGConstants.SVG_D_ATTRIBUTE == name) {
454: if (d == null) {
455: return "";
456: } else {
457: return d.toString();
458: }
459: } else if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
460: return Float.toString(getHorizontalAdvanceX());
461: } else if (SVGConstants.SVG_UNICODE_ATTRIBUTE == name) {
462: if (unicode == null) {
463: return "";
464: }
465: return unicode;
466: } else if (SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == name) {
467: return toStringTrait(getGlyphName());
468: }
469:
470: return super .getTraitImpl(name);
471: }
472:
473: /**
474: * Supported traits: d, horiz-adv-x
475: *
476: * @param name the trait name.
477: * @param value the trait's string value.
478: *
479: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
480: * trait is not supported on this element or null.
481: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
482: * trait's value cannot be specified as a String
483: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
484: * value is an invalid value for the given trait or null.
485: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
486: * attempt is made to change readonly trait.
487: */
488: public void setTraitImpl(final String name, final String value)
489: throws DOMException {
490: if (SVGConstants.SVG_D_ATTRIBUTE == name) {
491: checkWriteLoading(name);
492: setD(parsePathTrait(name, value));
493: } else if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
494: checkWriteLoading(name);
495: setHorizontalAdvanceX(parsePositiveFloatTrait(name, value));
496: } else if (SVGConstants.SVG_UNICODE_ATTRIBUTE == name) {
497: checkWriteLoading(name);
498: if (value == null) {
499: throw illegalTraitValue(name, value);
500: }
501: setUnicode(value);
502: } else if (SVGConstants.SVG_GLYPH_NAME_ATTRIBUTE == name) {
503: checkWriteLoading(name);
504: setGlyphName(parseStringArrayTrait(name, value,
505: SVGConstants.COMMA_STR));
506: } else {
507: super .setTraitImpl(name, value);
508: }
509: }
510:
511: /**
512: * @return a text description of the glyph including it's unicode value
513: */
514: public String toString() {
515: return "com.sun.perseus.model.Glyph[" + unicode + "]";
516: }
517: }
|