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:
031: import com.sun.perseus.util.SVGConstants;
032:
033: import java.util.Vector;
034:
035: import org.w3c.dom.DOMException;
036:
037: /**
038: * A <code>Font</code> node models an SVG <code><code></code>
039: * element.
040: *
041: * @version $Id: Font.java,v 1.7 2006/06/29 10:47:31 ln156897 Exp $
042: */
043: public class Font extends ElementNode {
044: /**
045: * Only the horiz-adv-x trait is required on <font>
046: */
047: static final String[] REQUIRED_TRAITS = { SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE };
048:
049: /**
050: * The X-coordinate in the font coordinate system of the origin
051: * of a glyph to be used when drawing horizontally oriented text.
052: * (Note that the origin applies to all glyphs in the font.)
053: */
054: protected float horizontalOriginX;
055:
056: /**
057: * The default horizontal advance after rendering a glyph in
058: * horizontal orientation. Glyph widths are required to be non-negative,
059: * even if the glyph is typically rendered right-to-left, as in
060: * Hebrew and Arabic scripts.
061: */
062: protected float horizontalAdvanceX;
063:
064: /**
065: * The current fontFace describing this font, if any
066: */
067: protected FontFace fontFace;
068:
069: /**
070: * The Font's missing glyph, used to render unknown characters
071: */
072: protected Glyph missingGlyph;
073:
074: /**
075: * The Font's first horizontal kerning pair. May be null.
076: */
077: protected HKern firstHKern;
078:
079: /**
080: * The Font's last horizontal kerning pair. May be null.
081: */
082: protected HKern lastHKern;
083:
084: /**
085: * Constructor.
086: *
087: * @param ownerDocument this element's owner <code>DocumentNode</code>
088: */
089: public Font(final DocumentNode ownerDocument) {
090: super (ownerDocument);
091: }
092:
093: /**
094: * @return the SVGConstants.SVG_FONT_TAG value
095: */
096: public String getLocalName() {
097: return SVGConstants.SVG_FONT_TAG;
098: }
099:
100: /**
101: * Used by <code>DocumentNode</code> to create a new instance from
102: * a prototype <code>Font</code>.
103: *
104: * @param doc the <code>DocumentNode</code> for which a new node is
105: * should be created.
106: * @return a new <code>Font</code> for the requested document.
107: */
108: public ElementNode newInstance(final DocumentNode doc) {
109: return new Font(doc);
110: }
111:
112: /**
113: * @param newHorizontalOriginX the new value for the <tt>Font</tt>'s
114: * origin along the x-axis.
115: */
116: public void setHorizontalOriginX(final float newHorizontalOriginX) {
117: if (newHorizontalOriginX == horizontalOriginX) {
118: return;
119: }
120: modifyingNode();
121: horizontalOriginX = newHorizontalOriginX;
122: modifiedNode();
123: }
124:
125: /**
126: * @return this <tt>Font</tt>'s origin along the x-axis
127: */
128: public float getHorizontalOriginX() {
129: return horizontalOriginX;
130: }
131:
132: /**
133: * @param newHorizontalAdvanceX the new horizontal advance along the
134: * x-axis for this <tt>Font</tt>
135: */
136: public void setHorizontalAdvanceX(final float newHorizontalAdvanceX) {
137: if (newHorizontalAdvanceX == horizontalAdvanceX) {
138: return;
139: }
140: modifyingNode();
141: horizontalAdvanceX = newHorizontalAdvanceX;
142: modifiedNode();
143: }
144:
145: /**
146: * @return this <tt>Font</tt>'s horizontal adavance along the x-axis
147: */
148: public float getHorizontalAdvanceX() {
149: return horizontalAdvanceX;
150: }
151:
152: /**
153: * Returns a Glyph if the FontFace can display the
154: * character at the requested index. Returns null
155: * if no matching glyph can be found.
156: *
157: * @param s the character sequence to display
158: * @param index the index of the first character in <tt>s</tt> to
159: * display.
160: * @return the <tt>Glyph</tt> used to represent the input
161: * character at <tt>index</tt> in <tt>s</tt>
162: */
163: public Glyph canDisplay(final char[] s, final int index) {
164: int max = s.length - index;
165: int glLength = 0;
166: int j = 0;
167:
168: Glyph gl = null;
169: ModelNode c = firstChild;
170: while (c != null) {
171: if (c instanceof Glyph) {
172: gl = (Glyph) c;
173: glLength = gl.getLength();
174: if (gl.getUnicode() != null && glLength > 0
175: && index + glLength <= s.length) {
176: for (j = 0; j < glLength; j++) {
177: if (s[index + j] != gl.getCharAt(j)) {
178: break;
179: }
180: }
181: if (j == glLength) {
182: return gl;
183: }
184: }
185: }
186: c = c.nextSibling;
187: }
188:
189: // No matching glyph
190: return null;
191: }
192:
193: /**
194: * @return the Glyph that represents missing glyphs.
195: * This should *not* be null.
196: */
197: public Glyph getMissingGlyph() {
198: return missingGlyph;
199: }
200:
201: // ====================================================================
202: // List Implementation overrides to track special children
203: // ====================================================================
204:
205: /**
206: * @param node <tt>ElementNode</tt> to add to this <tt>ElementNode</tt>
207: */
208: public void add(final ElementNode node) {
209: super .add(node);
210: addSpecial(node);
211: }
212:
213: /**
214: * Called in case special handling of children is required.
215: *
216: * @param node the <tt>ModelNode</tt> added to this list
217: *
218: * @see #addFontFace
219: * @see #addGlyph
220: */
221: protected void addSpecial(final ModelNode node) {
222: if (!(node instanceof Glyph)) {
223: if (!(node instanceof FontFace)) {
224: if (!(node instanceof HKern)) {
225: return;
226: } else {
227: addHKern((HKern) node);
228: }
229: } else {
230: addFontFace((FontFace) node);
231: }
232: } else {
233: Glyph gl = (Glyph) node;
234: if (gl.getUnicode() == null) {
235: addMissingGlyph(gl);
236: } else {
237: addGlyph(gl);
238: }
239: }
240: }
241:
242: /**
243: * @param newFontFace the <tt>FontFace</tt> child to add
244: */
245: protected void addFontFace(final FontFace newFontFace) {
246: if (fontFace != null) {
247: // If there is already a FontFace child, we ignore the second
248: // one. Only the first child is accounted for.
249: return;
250: }
251:
252: // A Font can only have a single font-face child. If there is alre
253: fontFace = newFontFace;
254: updateGlyphEmSquare();
255: }
256:
257: /**
258: * Simply chain the kerning pairs so that they can be
259: * looked up easily later.
260: *
261: * @param hkern the new <code>HKern</code> entry to add
262: * to this node.
263: */
264: protected void addHKern(final HKern hkern) {
265: if (firstHKern != null) {
266: lastHKern.nextHKern = hkern;
267: lastHKern = hkern;
268: } else {
269: firstHKern = hkern;
270: lastHKern = hkern;
271: hkern.nextHKern = null;
272: }
273: }
274:
275: /**
276: * Used by the Text class: looks up kerning pairs to check if
277: * there is any horizontal kerning for the input pair.
278: *
279: * @param g1 this first glyph is assumed to be part of this
280: * Font.
281: * @param g2 this second glyph may come from a different font, in
282: * which case this method returns 0.
283: * @return the kerning adjustment for the given pair of <code>Glyph</code>s.
284: */
285: float getHKern(final Glyph g1, final Glyph g2) {
286: if (g2.parent != this ) {
287: return 0;
288: }
289:
290: HKern k = firstHKern;
291: while (k != null) {
292: if (k.matchesFirst(g1)) {
293: if (k.matchesSecond(g2)) {
294: break;
295: }
296: }
297: k = k.nextHKern;
298: }
299:
300: if (k != null) {
301: if (fontFace != null) {
302: return k.k * fontFace.getEmSquareScale();
303: } else {
304: return k.k;
305: }
306: } else {
307: return 0;
308: }
309: }
310:
311: /**
312: * Update the em square on all the <tt>Font</tt>'s glyphs
313: */
314: protected void updateGlyphEmSquare() {
315: float emSquareScale = 1;
316: if (fontFace != null) {
317: emSquareScale = fontFace.getEmSquareScale();
318: }
319:
320: ElementNode c = firstChild;
321: while (c != null) {
322: if (c instanceof Glyph) {
323: ((Glyph) c).setEmSquareScale(emSquareScale);
324: }
325: c = (ElementNode) c.nextSibling;
326: }
327: }
328:
329: /**
330: * @param gl set the input <tt>Glyph</tt>'s em square.
331: */
332: protected void updateGlyphEmSquare(final Glyph gl) {
333: if (fontFace != null) {
334: gl.setEmSquareScale(fontFace.getEmSquareScale());
335: } else {
336: gl.setEmSquareScale(1);
337: }
338: }
339:
340: /**
341: * If there is already a missing glyph, any new call
342: * to <tt>addMissingGlyph</tt> has no effect on the
343: * missing glyph.
344: *
345: * @param gl <tt>Glyph</tt> to add as a missing glyph.
346: */
347: protected void addMissingGlyph(final Glyph gl) {
348: // Only use gl as a missing glyph if it is the
349: // first missing glyph
350: if (missingGlyph == null) {
351: missingGlyph = gl;
352: }
353: updateGlyphEmSquare(gl);
354: }
355:
356: /**
357: * @param gl <tt>Glyph</tt> to add to this font
358: */
359: protected void addGlyph(final Glyph gl) {
360: updateGlyphEmSquare(gl);
361: }
362:
363: /**
364: * Font handlers the horiz-adv-x and horiz-origin-x traits.
365: *
366: * @param traitName the name of the trait which the element may support.
367: * @return true if this element supports the given trait in one of the
368: * trait accessor methods.
369: */
370: boolean supportsTrait(final String traitName) {
371: if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == traitName
372: || SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == traitName) {
373: return true;
374: }
375:
376: return super .supportsTrait(traitName);
377: }
378:
379: /**
380: * @return an array of traits that are required by this element.
381: */
382: public String[] getRequiredTraits() {
383: return REQUIRED_TRAITS;
384: }
385:
386: /**
387: * Font handles the horiz-adv-x and horiz-origin-x traits.
388: * Other traits are handled by the super class.
389: *
390: * @param name the requested trait name (e.g., "horiz-adv-x")
391: * @return the trait's value, as a string.
392: *
393: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
394: * trait is not supported on this element or null.
395: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
396: * trait's computed value cannot be converted to a String (SVG Tiny only).
397: */
398: public String getTraitImpl(final String name) throws DOMException {
399: if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
400: return Float.toString(getHorizontalAdvanceX());
401: } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) {
402: return Float.toString(getHorizontalOriginX());
403: } else {
404: return super .getTraitImpl(name);
405: }
406: }
407:
408: /**
409: * Font handles the horiz-adv-x and horiz-origin-x traits.
410: * Other traits are handled by the super class.
411: *
412: * @param name the requested trait name (e.g., "horiz-adv-x")
413: * @return the trait's value, as a float.
414: *
415: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
416: * trait is not supported on this element or null.
417: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
418: * trait's computed value cannot be converted to a String (SVG Tiny only).
419: */
420: float getFloatTraitImpl(final String name) throws DOMException {
421: if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
422: return getHorizontalAdvanceX();
423: } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) {
424: return getHorizontalOriginX();
425: } else {
426: return super .getFloatTraitImpl(name);
427: }
428: }
429:
430: /**
431: * Font handles the horiz-adv-x and horiz-origin-x traits.
432: * Other traits are handled by the super class.
433: *
434: * @param name the trait's name (e.g., "horiz-adv-x")
435: * @param value the new trait string value (e.g., "10")
436: *
437: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
438: * trait is not supported on this element or null.
439: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
440: * trait's value cannot be specified as a String
441: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
442: * value is an invalid value for the given trait or null.
443: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
444: * attempt is made to change readonly trait.
445: */
446: public void setTraitImpl(final String name, final String value)
447: throws DOMException {
448: if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
449: checkWriteLoading(name);
450: setHorizontalAdvanceX(parseFloatTrait(name, value));
451: } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) {
452: checkWriteLoading(name);
453: setHorizontalOriginX(parseFloatTrait(name, value));
454: } else {
455: super .setTraitImpl(name, value);
456: }
457: }
458:
459: /**
460: * Font handles the horiz-adv-x and horiz-origin-x traits.
461: * Other traits are handled by the super class.
462: *
463: * @param name the trait's name (e.g., "horiz-adv-x")
464: * @param value the new trait float value (e.g., 10f)
465: *
466: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
467: * trait is not supported on this element or null.
468: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
469: * trait's value cannot be specified as a String
470: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
471: * value is an invalid value for the given trait or null.
472: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
473: * attempt is made to change readonly trait.
474: */
475: public void setFloatTraitImpl(final String name, final float value)
476: throws DOMException {
477: if (SVGConstants.SVG_HORIZ_ADV_X_ATTRIBUTE == name) {
478: setHorizontalAdvanceX(value);
479: } else if (SVGConstants.SVG_HORIZ_ORIGIN_X_ATTRIBUTE == name) {
480: setHorizontalOriginX(value);
481: } else {
482: super.setFloatTraitImpl(name, value);
483: }
484: }
485:
486: }
|