0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026: package com.sun.perseus.model;
0027:
0028: import com.sun.perseus.platform.MathSupport;
0029:
0030: import com.sun.perseus.j2d.Box;
0031: import com.sun.perseus.j2d.PaintServer;
0032: import com.sun.perseus.j2d.PaintTarget;
0033: import com.sun.perseus.j2d.RenderContext;
0034: import com.sun.perseus.j2d.RenderGraphics;
0035: import com.sun.perseus.j2d.TextRenderingProperties;
0036: import com.sun.perseus.j2d.TextProperties;
0037: import com.sun.perseus.j2d.Tile;
0038: import com.sun.perseus.j2d.Transform;
0039:
0040: import com.sun.perseus.util.SVGConstants;
0041:
0042: import org.w3c.dom.DOMException;
0043:
0044: import org.w3c.dom.svg.SVGRect;
0045:
0046: /**
0047: * Models text content. In Perseus, text nodes are described by their content
0048: * (a character string), a number of font properties which guide which
0049: * Font is selected to render the text and a text property (text-anchor) which
0050: * defines how the text is positioned about its anchor point.
0051: * <br />
0052: * Text nodes lazily (i.e., on paint request) evaluate which font they use to
0053: * render their content. At that time, the characters in the Text content is
0054: * matched with the glyphs that available Fonts can display. This generates a
0055: * set of children <code>GlyphProxy</code> nodes which are then laid-out
0056: * using the text property and the font metrics.
0057: *
0058: * @version $Id: Text.java,v 1.18 2006/06/29 10:47:35 ln156897 Exp $
0059: */
0060: public class Text extends Group {
0061: /**
0062: * The text to be displayed
0063: */
0064: protected String content;
0065:
0066: /**
0067: * The origin of the text string on the x axis.
0068: */
0069: protected float[] x = { 0 };
0070:
0071: /**
0072: * The origin of the text string on the y axis.
0073: */
0074: protected float[] y = { 0 };
0075:
0076: /**
0077: * Per-glyph rotation
0078: */
0079: protected float[] rotate;
0080:
0081: /**
0082: * Points to the first text chunk.
0083: */
0084: protected GlyphLayout firstChunk;
0085:
0086: /**
0087: * Points to the last text chunk.
0088: */
0089: protected GlyphLayout lastChunk;
0090:
0091: /**
0092: * Used to track the node's rendering area and the rendered areas.
0093: */
0094: protected RenderingManager renderingManager = null;
0095:
0096: /**
0097: * Used to scale dash array values.
0098: */
0099: protected float[] helperDashArray;
0100:
0101: /**
0102: * Constructor.
0103: *
0104: * @param ownerDocument this element's owner <code>DocumentNode</code>
0105: */
0106: public Text(final DocumentNode ownerDocument) {
0107: super (ownerDocument);
0108:
0109: if (DirtyAreaManager.ON) {
0110: renderingManager = new RenderingManager(this );
0111: }
0112:
0113: }
0114:
0115: /**
0116: * @return the SVGConstants.SVG_TEXT_TAG value
0117: */
0118: public String getLocalName() {
0119: return SVGConstants.SVG_TEXT_TAG;
0120: }
0121:
0122: /**
0123: * Used by <code>DocumentNode</code> to create a new instance from
0124: * a prototype <code>Text</code>.
0125: *
0126: * @param doc the <code>DocumentNode</code> for which a new node is
0127: * should be created.
0128: * @return a new <code>Text</code> for the requested document.
0129: */
0130: public ElementNode newInstance(final DocumentNode doc) {
0131: return new Text(doc);
0132: }
0133:
0134: /**
0135: * @return an adequate <code>ElementNodeProxy</code> for this node.
0136: */
0137: ElementNodeProxy buildProxy() {
0138: return new TextProxy(this );
0139: }
0140:
0141: /**
0142: * @param newX this text's new x-axis position
0143: */
0144: public void setX(final float[] newX) {
0145: if (equal(newX, x)) {
0146: return;
0147: }
0148:
0149: modifyingNode();
0150: if (newX == null || newX.length == 0) {
0151: this .x = new float[1];
0152: this .x[0] = 0;
0153: } else {
0154: this .x = newX;
0155: }
0156: clearLayoutsQuiet();
0157: renderingDirty();
0158: modifiedNode();
0159: }
0160:
0161: /**
0162: * @param newY this text's new y-axis position
0163: */
0164: public void setY(final float[] newY) {
0165: if (equal(newY, y)) {
0166: return;
0167: }
0168:
0169: modifyingNode();
0170: if (newY == null || newY.length == 0) {
0171: this .y = new float[1];
0172: this .y[0] = 0;
0173: } else {
0174: this .y = newY;
0175: }
0176: clearLayoutsQuiet();
0177: renderingDirty();
0178: modifiedNode();
0179: }
0180:
0181: /**
0182: * @param newRotate this text's new per-glyph rotation
0183: */
0184: public void setRotate(final float[] newRotate) {
0185: if (equal(newRotate, rotate)) {
0186: return;
0187: }
0188: modifyingNode();
0189: this .rotate = newRotate;
0190: clearLayoutsQuiet();
0191: modifiedNode();
0192: }
0193:
0194: /**
0195: * @return this text's x-axis author position
0196: */
0197: public float[] getX() {
0198: return x;
0199: }
0200:
0201: /**
0202: * @return this text's y-axis anchor position
0203: */
0204: public float[] getY() {
0205: return y;
0206: }
0207:
0208: /**
0209: * @return this text's set of rotation values for
0210: * its glyphs.
0211: */
0212: public float[] getRotate() {
0213: return rotate;
0214: }
0215:
0216: /**
0217: * @return true always, as a text node needs its textual content which
0218: * is not fully loaded until the element has been loaded.
0219: */
0220: public boolean getPaintNeedsLoad() {
0221: return true;
0222: }
0223:
0224: /**
0225: * @return the bounding box, in screen coordinate, which encompasses the
0226: * node's rendering.
0227: */
0228: protected Tile getRenderingTile() {
0229: return renderingManager.getRenderingTile();
0230: }
0231:
0232: /**
0233: * @return the tile which encompasses the node's last actual rendering. If
0234: * this node's hasRendering method returns false, then this method should
0235: * return null. By default, this method returns null because
0236: * hasNodeRendering returns false by default.
0237: */
0238: protected Tile getLastRenderedTile() {
0239: return renderingManager.getLastRenderedTile();
0240: }
0241:
0242: /**
0243: * After calling this method, getLastRenderedTile should always return null.
0244: */
0245: protected void clearLastRenderedTile() {
0246: renderingManager.clearLastRenderedTile();
0247: }
0248:
0249: /**
0250: * To be overriddent by derived classes, such as TimedElementNode,
0251: * if they need to perform special operations when hooked into the
0252: * document tree.
0253: */
0254: void nodeHookedInDocumentTree() {
0255: super .nodeHookedInDocumentTree();
0256: renderingDirty();
0257: }
0258:
0259: /**
0260: * To be overriddent by derived classes, such as TimedElementNode,
0261: * if they need to perform special operations when unhooked from the
0262: * document tree.
0263: */
0264: void nodeUnhookedFromDocumentTree() {
0265: super .nodeUnhookedFromDocumentTree();
0266: renderingDirty();
0267: }
0268:
0269: /**
0270: * @return the tight bounding box in current user coordinate
0271: * space.
0272: */
0273: public SVGRect getBBox() {
0274: return addNodeBBox(null, null);
0275: }
0276:
0277: /**
0278: * @return the tight bounding box in screen coordinate space.
0279: */
0280: public SVGRect getScreenBBox() {
0281: // There is no screen bounding box if the element is not hooked
0282: // into the main tree.
0283: if (!inDocumentTree()) {
0284: return null;
0285: }
0286:
0287: return addNodeBBox(null, txf);
0288: }
0289:
0290: /**
0291: * @param bbox the bounding box to which this node's bounding box should be
0292: * appended. That bounding box is in the target coordinate space. It
0293: * may be null, in which case this node should create a new one.
0294: * @param t the transform from the node coordinate system to the coordinate
0295: * system into which the bounds should be computed.
0296: * @return the bounding box of this node, in the target coordinate space,
0297: */
0298: Box addNodeBBox(final Box bbox, final Transform t) {
0299: checkLayout();
0300: return addNodeBBox(bbox, t, firstChunk);
0301: }
0302:
0303: /**
0304: * @param bbox the bounding box to which this node's bounding box should be
0305: * appended. That bounding box is in the target coordinate space. It
0306: * may be null, in which case this node should create a new one.
0307: * @param t the transform from the node coordinate system to the coordinate
0308: * system into which the bounds should be computed.
0309: * @param fc the <code>GlyphLayout</code> first chunk, so that the method
0310: * can be used by <code>TextProxy</code>
0311: * @return the bounding box of this node, in the target coordinate space,
0312: */
0313: Box addNodeBBox(Box bbox, final Transform t, final GlyphLayout fc) {
0314: GlyphLayout c = fc;
0315: while (c != null) {
0316: ownerDocument.bboxChunkTxf.setTransform(t);
0317: c.applyTransform(this , ownerDocument.bboxChunkTxf);
0318: bbox = c.addBBox(bbox, ownerDocument.bboxChunkTxf);
0319: c = c.nextSibling;
0320: }
0321: return bbox;
0322: }
0323:
0324: /**
0325: * An <code>Text</code> has something to render
0326: *
0327: * @return true
0328: */
0329: public boolean hasNodeRendering() {
0330: return true;
0331: }
0332:
0333: /**
0334: * This method is overridden for elements which has special renderings,
0335: * such as the ShapeNodes.
0336: *
0337: * @param tile the Tile instance whose bounds should be set.
0338: */
0339: protected void computeRenderingTile(final Tile tile) {
0340: checkLayout();
0341: computeRenderingTile(tile, txf, this , firstChunk);
0342: }
0343:
0344: /**
0345: * The rendering tile of a Text node is the union of the rendering tile of
0346: * its layouts.
0347: *
0348: * @param tile the Tile instance whose bounds should be set.
0349: * @param t the Transform to the requested tile space, from this node's user
0350: * @param trp the TextRenderinProperties describing the nodes rendering
0351: * characteristics. space.
0352: * @param fc the first GlyphLayout chunk.
0353: */
0354: final protected void computeRenderingTile(final Tile tile,
0355: final Transform t, final TextRenderingProperties trp,
0356: final GlyphLayout fc) {
0357: GlyphLayout gl = fc;
0358: Tile glt = null;
0359: while (gl != null) {
0360: ownerDocument.bboxChunkTxf.setTransform(t);
0361: gl.applyTransform(trp, ownerDocument.bboxChunkTxf);
0362: glt = gl.addRenderingTile(glt, trp,
0363: ownerDocument.bboxChunkTxf);
0364: gl = (GlyphLayout) gl.nextSibling;
0365: }
0366:
0367: if (glt != null) {
0368: tile.setTile(glt);
0369: } else {
0370: tile.setEmptyTile();
0371: }
0372: }
0373:
0374: /**
0375: * @param text the text to append to this node's content.
0376: * If text is null or empty, this does nothing.
0377: */
0378: public void appendTextChild(final String text) {
0379: if (text == null || text.length() == 0) {
0380: return;
0381: }
0382:
0383: if (content == null) {
0384: setContent(text);
0385: } else {
0386: setContent(content + text);
0387: }
0388: }
0389:
0390: /**
0391: * @param newContent this node's new content string
0392: */
0393: public void setContent(final String newContent) {
0394: if (equal(newContent, content)) {
0395: return;
0396: }
0397: modifyingNode();
0398: this .content = newContent;
0399: clearLayouts(false);
0400: modifiedNode();
0401: }
0402:
0403: /**
0404: * @return this node's text content as a string
0405: */
0406: public String getContent() {
0407: return content;
0408: }
0409:
0410: /**
0411: * Returns the <code>ModelNode</code>, if any, hit by the
0412: * point at coordinate x/y.
0413: *
0414: * @param pt the x/y coordinate. Should never be null and be
0415: * of size two. If not, the behavior is unspecified.
0416: * The coordinates are in viewport space.
0417: * @return the <tt>ModelNode</tt> hit at the given point or null
0418: * if none was hit.
0419: */
0420: public ModelNode nodeHitAt(final float[] pt) {
0421: // If a node does not render, it is never hit
0422: if (canRenderState != 0) {
0423: return null;
0424: }
0425:
0426: checkLayout();
0427:
0428: if (isHitVP(pt, this , getInverseTransformState(), lastChunk)) {
0429: return this ;
0430: }
0431:
0432: return null;
0433: }
0434:
0435: /**
0436: * Returns the <code>ModelNode</code>, if any, hit by the
0437: * point at coordinate x/y in the proxy tree starting at
0438: * proxy.
0439: *
0440: * @param pt the x/y coordinate. Should never be null and be
0441: * of size two. If not, the behavior is unspecified.
0442: * The coordinates are in viewport space.
0443: * @param proxy the root of the proxy tree to test.
0444: * @return the <tt>ModelNode</tt> hit at the given point or null
0445: * if none was hit.
0446: */
0447: ModelNode proxyNodeHitAt(final float[] pt,
0448: final ElementNodeProxy proxy) {
0449: // If a node does not render, it is never hit
0450: if (canRenderState != 0) {
0451: return null;
0452: }
0453:
0454: TextProxy tp = (TextProxy) proxy;
0455: tp.checkLayout();
0456:
0457: if (isHitVP(pt, tp, tp.inverseTxf, tp.lastChunk)) {
0458: return proxy;
0459: }
0460:
0461: return null;
0462: }
0463:
0464: /**
0465: * Returns true if this node is hit by the input point. The input point
0466: * is in viewport space. By default, a node is not hit, not
0467: * matter what the input coordinate is.
0468: *
0469: * @param pt the x/y coordinate. Should never be null and be
0470: * of size two. If not, the behavior is unspecified.
0471: * The x/y coordinate is in the node's user space.
0472: * @param trp the text rendering properties that apply to the layout
0473: * rendering.
0474: * @param itx the transform from viewport space to the text's user space.
0475: * @param lc the <tt>GlyphLayout</tt> 'lastChunk' so that the method
0476: * can be used by <tt>Text</tt> and <tt>TextProxy</tt>.
0477: * @return true if the node is hit by the input point.
0478: * @see #nodeHitAt
0479: */
0480: protected boolean isHitVP(final float[] pt,
0481: final TextRenderingProperties trp, final Transform itx,
0482: final GlyphLayout lc) {
0483: // Node has to be visible to be a hit target
0484: if (!trp.getVisibility()) {
0485: return false;
0486: }
0487:
0488: GlyphLayout c = lc;
0489:
0490: while (c != null) {
0491: // Initialize hitChunkTransform to be from viewport space to the
0492: // chunk's user space. This is why we apply the inverse transform
0493: // second and why we invoke applyInverseTransform on the GlyphLayout
0494: // instance.
0495: ownerDocument.hitChunkTxf.setTransform(1, 0, 0, 1, 0, 0);
0496: c.applyInverseTransform(trp, ownerDocument.hitChunkTxf);
0497: ownerDocument.hitChunkTxf.mMultiply(itx);
0498: if (c.isHitVP(pt, this , ownerDocument.hitChunkTxf)) {
0499: return true;
0500: }
0501: c = c.prevSibling;
0502: }
0503:
0504: return false;
0505: }
0506:
0507: /**
0508: * Paints this node into the input <code>RenderGraphics</code>. A
0509: * <code>Text</code> node renders its associated <code>GlyphLayout</code>
0510: * children.
0511: *
0512: * @param rg the <tt>RenderGraphics</tt> where the node should paint itself
0513: */
0514: public void paint(final RenderGraphics rg) {
0515: if (canRenderState != 0) {
0516: return;
0517: }
0518:
0519: checkLayout();
0520:
0521: if (DirtyAreaManager.ON) {
0522: Tile primitiveTile = getRenderingTile();
0523: if (primitiveTile == null
0524: || rg.getRenderingTile().isHit(primitiveTile)) {
0525: // rg.setPrimitiveTile(primitiveTile);
0526: paintRendered(rg, this , txf, firstChunk);
0527:
0528: // nodeRendered is called seperately from paintRendered
0529: // because paintRendered is used in different contexts,
0530: // for example by proxy nodes to render, using their
0531: // proxied node's paintRendered method.
0532: nodeRendered();
0533: }
0534: } else {
0535: paintRendered(rg, this , txf, firstChunk);
0536: }
0537: }
0538:
0539: /**
0540: * Paints this node into the input RenderGraphics, assuming the node
0541: * is rendered.
0542: *
0543: * @param rg the <code>RenderGraphics</code> where the node should paint
0544: * itself.
0545: * @param tc the <code>TextRenderingProperties</code> holding the properties
0546: * tha should be used for rendering into the input
0547: * <code>RenderGraphics</code>
0548: * @param tx the transform to use when rendering this node
0549: * @param fc the first chunk to render.
0550: * @see ElementNode#paint
0551: * @see ElementNode#paintRendered
0552: * @see ModelNode#canRender
0553: */
0554: public void paintRendered(final RenderGraphics rg,
0555: final TextRenderingProperties trc, final Transform tx,
0556: final GlyphLayout fc) {
0557: if (!trc.getVisibility()) {
0558: return;
0559: }
0560: rg.setPaintTarget(this );
0561: rg.setPaintTransform(tx);
0562:
0563: rg.setFontSize(trc.getFontSize());
0564: rg.setTextAnchor(trc.getTextAnchor());
0565:
0566: // Fill text. Only apply the fill properties
0567: if (trc.getFill() != null) {
0568: rg.setFillRule(trc.getFillRule());
0569: rg.setFill(trc.getFill());
0570: rg.setFillOpacity(trc.getFillOpacity());
0571: fillText(rg, tx, fc);
0572: }
0573:
0574: // Stroke text. Only apply the stroke properties
0575: if (trc.getStroke() != null) {
0576: // We divide the strokeWidth by the fontSize to account
0577: // for the additional scale factor (by fontSize). The
0578: // additional scale factor is applied when rendering
0579: // GlyphLayouts.
0580: // See #GlyphLayout.applyTransform
0581: rg.setStrokeWidth(trc.getStrokeWidth() / trc.getFontSize());
0582:
0583: // do the same for dashArray and dashOffset if necessary
0584: float[] dashArray = trc.getStrokeDashArray();
0585: float[] trDashArray = null;
0586: float trDashOffset = 0;
0587: if (dashArray != null) {
0588: float fontSize = trc.getFontSize();
0589:
0590: if ((helperDashArray == null)
0591: || (helperDashArray.length != dashArray.length)) {
0592: helperDashArray = new float[dashArray.length];
0593: }
0594: trDashArray = helperDashArray;
0595:
0596: for (int i = 0; i < dashArray.length; ++i) {
0597: trDashArray[i] = dashArray[i] / fontSize;
0598: }
0599:
0600: trDashOffset = trc.getStrokeDashOffset() / fontSize;
0601: }
0602:
0603: rg.setStrokeDashArray(trDashArray);
0604: rg.setStrokeDashOffset(trDashOffset);
0605:
0606: rg.setStroke(trc.getStroke());
0607: rg.setStrokeOpacity(trc.getStrokeOpacity());
0608: rg.setStrokeLineCap(trc.getStrokeLineCap());
0609: rg.setStrokeLineJoin(trc.getStrokeLineJoin());
0610: rg.setStrokeMiterLimit(trc.getStrokeMiterLimit());
0611: drawText(rg, tx, fc);
0612: }
0613: }
0614:
0615: /**
0616: * Fills text.
0617: *
0618: * @param rg the <code>RenderGraphics</code> where the node should paint
0619: * itself.
0620: * @param tx the rendering transform.
0621: * @param fc the first chunk to render.
0622: */
0623: void fillText(final RenderGraphics rg, final Transform tx,
0624: final GlyphLayout fc) {
0625: GlyphLayout c = fc;
0626: while (c != null) {
0627: ownerDocument.paintChunkTxf.setTransform(tx);
0628: c.applyTransform(rg, ownerDocument.paintChunkTxf);
0629: c.fillText(rg, ownerDocument.paintChunkTxf);
0630: c = c.nextSibling;
0631: }
0632: }
0633:
0634: /**
0635: * Draws text.
0636: *
0637: * @param rg the <code>RenderGraphics</code> where the node should paint
0638: * itself.
0639: * @param tx the rendering transform.
0640: * @param fc the first chunk to render.
0641: */
0642: void drawText(final RenderGraphics rg, final Transform tx,
0643: final GlyphLayout fc) {
0644: GlyphLayout c = fc;
0645: while (c != null) {
0646: ownerDocument.paintChunkTxf.setTransform(tx);
0647: c.applyTransform(rg, ownerDocument.paintChunkTxf);
0648: c.drawText(rg, ownerDocument.paintChunkTxf);
0649: c = c.nextSibling;
0650: }
0651: }
0652:
0653: /**
0654: * Should be called whenever this node's rendering becomes dirty.
0655: */
0656: final void renderingDirty() {
0657: if (DirtyAreaManager.ON) {
0658: renderingManager.dirty();
0659: }
0660: }
0661:
0662: /**
0663: * Simply notifies the RenderingManager.
0664: */
0665: protected void nodeRendered() {
0666: if (DirtyAreaManager.ON) {
0667: renderingManager.rendered();
0668: }
0669: }
0670:
0671: /**
0672: * Clears all cached layout information. Because the computed transform
0673: * property on text depends on the layout, clearLayouts on <code>Text</code>
0674: * also clears the transform and property cache.
0675: */
0676: public void clearLayouts() {
0677: clearLayouts(true);
0678: }
0679:
0680: /**
0681: * Implementations. Clears layouts but does not generate a
0682: * modification notification.
0683: *
0684: * @param notif if true, then notifications are issued
0685: * (i.e., calls to <code>modifyingNode</code> and
0686: * <code>modifiedNode</code>).
0687: */
0688: protected void clearLayouts(final boolean notif) {
0689: if (notif) {
0690: modifyingNode();
0691: }
0692:
0693: clearLayoutsQuiet();
0694:
0695: if (notif) {
0696: modifiedNode();
0697: }
0698: }
0699:
0700: /**
0701: * Clears all cached layout information
0702: * but does not generate modification
0703: * events for this node.
0704: */
0705: protected void clearLayoutsQuiet() {
0706: firstChunk = null;
0707: lastChunk = null;
0708:
0709: //
0710: // Now, clear the proxies layouts
0711: //
0712: ElementNodeProxy p = firstProxy;
0713: while (p != null) {
0714: ((TextProxy) p).clearLayoutsQuiet();
0715: p = p.nextProxy;
0716: }
0717:
0718: }
0719:
0720: /**
0721: * Checks that the text's layout has been computed and computes it in
0722: * case it was not.
0723: */
0724: void checkLayout() {
0725: if (firstChunk == null) {
0726: firstChunk = layoutText(this );
0727: GlyphLayout cur = firstChunk;
0728: while (cur.nextSibling != null) {
0729: cur = cur.nextSibling;
0730: }
0731: lastChunk = (GlyphLayout) cur;
0732: }
0733: }
0734:
0735: /**
0736: * @return true if the content string is not null and not empty or
0737: * if this node has children. false otherwise.
0738: */
0739: public boolean hasDescendants() {
0740: return super .hasDescendants() || content != null
0741: && !("".equals(content));
0742: }
0743:
0744: /**
0745: * Applies the xml:space policy
0746: *
0747: * @param s the array of characters which should be processed
0748: * @return the index of the last (inclusive) relevant character
0749: * after processing.
0750: */
0751: protected int applyXMLSpace(final char[] s) {
0752: switch (getXMLSpace()) {
0753: case XML_SPACE_DEFAULT:
0754: case XML_SPACE_INHERIT:
0755: return applyXMLSpaceDefault(s);
0756: default:
0757: return applyXMLSpacePreserve(s);
0758: }
0759:
0760: }
0761:
0762: /**
0763: * Applies the xml:space="default" policy
0764: *
0765: * ** SPECIFICATION TEXT, SECTION 10.15 **
0766: *
0767: * When xml:space="default", the SVG user agent will do
0768: * the following using a copy of the original character
0769: * data content. First, it will remove all newline characters.
0770: * Then it will convert all tab characters into space characters.
0771: * Then, it will strip off all leading and trailing space
0772: * characters. Then, all contiguous space characters will be
0773: * consolidated
0774: *
0775: * @param s the array of characters which should be processed
0776: * @return the index of the last (exclusive) relevant character
0777: * after processing.
0778: */
0779: protected int applyXMLSpaceDefault(final char[] s) {
0780: int j = 0;
0781:
0782: // Remove newline characters and
0783: // convert tabs to white spaces.
0784: // Output to s.
0785: int i = 0;
0786: for (; i < s.length; i++) {
0787: if (s[i] != '\n') {
0788: if (s[i] == '\t') {
0789: s[i] = ' ';
0790: }
0791: s[j++] = s[i];
0792: }
0793: }
0794:
0795: int length = j;
0796:
0797: // Now, consolidate white spaces
0798:
0799: // Trim leading spaces
0800: for (i = 0; i < length; i++) {
0801: if (s[i] != ' ') {
0802: break;
0803: }
0804: }
0805:
0806: // Consolidate spaces
0807: j = 0;
0808: s[j++] = s[i++];
0809: for (; i < length; i++) {
0810: // Keep all non-space characters
0811: if (s[i] != ' ') {
0812: s[j++] = s[i];
0813: } else {
0814: // Only keep space character if
0815: // previous character is not a ' '
0816: if (s[j - 1] != ' ') {
0817: s[j++] = ' ';
0818: }
0819: }
0820: }
0821:
0822: // Trim trailing space
0823: length = j;
0824: if (s[j - 1] == ' ') {
0825: length = j - 1;
0826: }
0827:
0828: return length;
0829: }
0830:
0831: /**
0832: * Applies the xml:space="preserve" policy
0833: *
0834: * ** SPECIFICATION TEXT, SECTION 10.15 **
0835: *
0836: * When xml:space="preserve", the SVG user agent will do the
0837: * following using a copy of the original character data content.
0838: * It will convert all newline and tab characters into space
0839: * characters. Then, it will draw all space characters, including
0840: * leading, trailing and multiple contiguous space characters.
0841: * Thus, when drawn with xml:space="preserve", the string "a b"
0842: * (three spaces between "a" and "b") will produce a
0843: * larger separation between "a" and "b" than "a b" (one space
0844: * between "a" and "b").
0845: *
0846: * @param s the array of characters which should be processed
0847: * @return the index of the last (exclusive) relevant character
0848: * after processing.
0849: */
0850: protected int applyXMLSpacePreserve(final char[] s) {
0851: for (int i = 0; i < s.length; i++) {
0852: if (s[i] == '\n' || s[i] == '\t') {
0853: s[i] = ' ';
0854: }
0855: }
0856:
0857: return s.length;
0858: }
0859:
0860: /**
0861: * Invoked when text layout should be performed
0862: * or checked.
0863: *
0864: * @param tp text is laid out for the input <tt>TextProperties</tt>
0865: * @return an <code>GlyphLayout</code> instance containing the
0866: * laid out text.
0867: */
0868: public GlyphLayout layoutText(final TextProperties tp) {
0869: // There is always at least one, possibly empty
0870: // text chunk.
0871: GlyphLayout startChunk = new GlyphLayout(ownerDocument);
0872: GlyphLayout chunk = startChunk;
0873: chunk.x = x[0];
0874: chunk.y = y[0];
0875:
0876: // Stop now if there is not actual content
0877: if (content == null || "".equals(content)) {
0878: return startChunk;
0879: }
0880:
0881: // Take care of white space handling
0882: char[] s = content.toCharArray();
0883: int length = applyXMLSpace(s);
0884:
0885: // First, build the list of font faces which match the font properties
0886: // for this text node. FontFace resolution is done through the root
0887: // node element which holds the FontFace data base.
0888: FontFace.Match defaultMatch = ownerDocument
0889: .resolveFontFaces(tp);
0890: FontFace.Match firstMatch = defaultMatch.next;
0891: FontFace.Match curMatch = null;
0892:
0893: Glyph missingGlyph = defaultMatch.fontFace.getMissingGlyph();
0894:
0895: // Now, for each character in the content string, find a matching
0896: // font. The fontFace that matches can match up to n characters.
0897: int cur = 0; // Current index in character array 's'
0898: Glyph glyph = null;
0899: GlyphProxy proxy = null, prevProxy = null;
0900: float curAdv = 0;
0901: float fontSize = tp.getFontSize();
0902:
0903: while (cur < length) {
0904: // =================================================================
0905: // Check if we need to create a new text chunk
0906:
0907: if (cur > 0 && (cur < x.length || cur < y.length)) {
0908: // Create a new chunk. The current chunk becomes the
0909: // previous chunk.
0910: GlyphLayout prevChunk = chunk;
0911: chunk = new GlyphLayout(ownerDocument);
0912: prevProxy = null;
0913:
0914: // We have finished computing the advance of
0915: // the now previous chunk
0916: prevChunk.advance = curAdv;
0917: curAdv = 0;
0918:
0919: if (cur < x.length) {
0920: chunk.x = x[cur];
0921: } else {
0922: chunk.x = prevChunk.x + fontSize
0923: * prevChunk.advance;
0924: }
0925:
0926: if (cur < y.length) {
0927: chunk.y = y[cur];
0928: } else {
0929: chunk.y = prevChunk.y;
0930: }
0931:
0932: // Chain the new chunk with the previous one
0933: prevChunk.nextSibling = chunk;
0934: chunk.prevSibling = prevChunk;
0935:
0936: }
0937:
0938: // =================================================================
0939: // Find a matching glyph or default to the missing glyph.
0940:
0941: glyph = null;
0942: curMatch = firstMatch;
0943: while (curMatch != null) {
0944: if ((glyph = curMatch.fontFace.canDisplay(s, cur)) != null) {
0945: break;
0946: }
0947: curMatch = curMatch.next;
0948: }
0949:
0950: if (glyph == null) {
0951: if ((glyph = defaultMatch.fontFace.canDisplay(s, cur)) == null) {
0952: glyph = missingGlyph;
0953: }
0954: }
0955:
0956: // Create a proxy for the glyph
0957: proxy = new GlyphProxy(glyph);
0958:
0959: // =================================================================
0960: // Add the proxy to the current text chunk
0961: chunk.add(proxy);
0962:
0963: // Account for kerning, only if the previous glyph is part of
0964: // the same text chunk.
0965: if (prevProxy != null) {
0966: float adjust = ((Font) proxy.proxied.parent).getHKern(
0967: prevProxy.proxied, proxy.proxied);
0968: curAdv -= adjust;
0969: }
0970:
0971: proxy.setX(curAdv);
0972:
0973: if (rotate != null && cur < rotate.length) {
0974: // The rotation is in the text coordinate system
0975: proxy.setRotate(rotate[cur]);
0976: }
0977:
0978: cur += glyph.getLength();
0979:
0980: // Increment the advance *in the text coordinate system*.
0981: //
0982: // IMPORTANT NOTE: it is very important to use the
0983: // glyph's advance in the text coordinate space. It
0984: // is not possible to work in the em square coordinate
0985: // space for the advance because the em square may be
0986: // different for each glyph in the text string (when
0987: // different fonts are used).
0988: curAdv += glyph.getTextHorizontalAdvanceX();
0989:
0990: prevProxy = proxy;
0991: }
0992:
0993: chunk.advance = curAdv;
0994:
0995: return startChunk;
0996: }
0997:
0998: /**
0999: * Recomputes the transform cache, if one exists.
1000: * @param parentTransform the Transform applied to this node's parent.
1001: */
1002: protected void recomputeTransformState(
1003: final Transform parentTransform) {
1004: txf = appendTransform(parentTransform, txf);
1005: computeCanRenderTransformBit(txf);
1006: inverseTxf = null;
1007: // inverseTxf = computeInverseTransform(txf, parentTransform,
1008: // inverseTxf);
1009: renderingDirty();
1010: }
1011:
1012: /**
1013: * Called when the computed value of the given property has changed.
1014: * As we do not render any text children, this does not propage changes any
1015: * further.
1016: *
1017: * @param propertyIndex index for the property whose value has changed.
1018: * @param parentPropertyValue the value that children of this node should
1019: * now inherit.
1020: */
1021: protected void propagatePropertyState(final int propertyIndex,
1022: final Object parentPropertyValue) {
1023: // Propagate to proxies.
1024: if (firstProxy != null) {
1025: ElementNodeProxy proxy = firstProxy;
1026: while (proxy != null) {
1027: ((CompositeGraphicsNodeProxy) proxy)
1028: .proxiedPropertyStateChange(propertyIndex,
1029: parentPropertyValue);
1030: proxy = proxy.nextProxy;
1031: }
1032: }
1033: }
1034:
1035: /**
1036: * Called when the computed value of the given float property has changed.
1037: * As we do not render any text children, this does not propage changes any
1038: * further.
1039: *
1040: * @param propertyIndex index for the property whose value has changed.
1041: * @param parentPropertyValue the value that children of this node should
1042: * now inherit.
1043: */
1044: protected void propagateFloatPropertyState(final int propertyIndex,
1045: final float parentPropertyValue) {
1046: // Propagate to proxies.
1047: if (firstProxy != null) {
1048: ElementNodeProxy proxy = firstProxy;
1049: while (proxy != null) {
1050: ((CompositeGraphicsNodeProxy) proxy)
1051: .proxiedFloatPropertyStateChange(propertyIndex,
1052: parentPropertyValue);
1053: proxy = proxy.nextProxy;
1054: }
1055: }
1056: }
1057:
1058: /**
1059: * Called when the computed value of the given packed property has changed.
1060: * As we do not render any text children, this does not propage changes any
1061: * further.
1062: *
1063: * @param propertyIndex index for the property whose value has changed.
1064: * @param parentPropertyValue the value that children of this node should
1065: * now inherit.
1066: */
1067: protected void propagatePackedPropertyState(
1068: final int propertyIndex, final int parentPropertyValue) {
1069: // Propagate to proxies.
1070: if (firstProxy != null) {
1071: ElementNodeProxy proxy = firstProxy;
1072: while (proxy != null) {
1073: ((CompositeGraphicsNodeProxy) proxy)
1074: .proxiedPackedPropertyStateChange(
1075: propertyIndex, parentPropertyValue);
1076: proxy = proxy.nextProxy;
1077: }
1078: }
1079: }
1080:
1081: /**
1082: * Text handles x, y, and #text traits.
1083: *
1084: * @param traitName the name of the trait which the element may support.
1085: * @return true if this element supports the given trait in one of the
1086: * trait accessor methods.
1087: */
1088: boolean supportsTrait(final String traitName) {
1089: if (SVGConstants.SVG_X_ATTRIBUTE == traitName
1090: || SVGConstants.SVG_Y_ATTRIBUTE == traitName
1091: || SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName
1092: || SVGConstants.SVG_TEXT_PSEUDO_ATTRIBUTE == traitName) {
1093: return true;
1094: } else {
1095: return super .supportsTrait(traitName);
1096: }
1097: }
1098:
1099: /**
1100: * Text handles x, y, and #text traits.
1101: *
1102: * @param name the requested trait's name (e.g., "#text")
1103: * @return the requested trait's string value (e.g., "Hello SVG Text")
1104: *
1105: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
1106: * trait is not supported on this element or null.
1107: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
1108: * trait's computed value cannot be converted to a String (SVG Tiny only).
1109: */
1110: public String getTraitImpl(final String name) throws DOMException {
1111: if (SVGConstants.SVG_X_ATTRIBUTE == name) {
1112: return toStringTrait(getX());
1113: } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
1114: return toStringTrait(getY());
1115: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
1116: if (rotate == null) {
1117: return "";
1118: }
1119:
1120: /*
1121: float[] rt = new float[rotate.length];
1122: for (int i = 0; i < rt.length; i++) {
1123: rt[i] = MathSupport.toDegrees(rotate[i]);
1124: }
1125: */
1126:
1127: System.err.println(">>>>>>>>>>>>>>>>>> getTraitImpl("
1128: + name + ") : '" + toStringTrait(rotate) + "'");
1129: return toStringTrait(rotate);
1130: } else if (SVGConstants.SVG_TEXT_PSEUDO_ATTRIBUTE == name) {
1131: if (content == null) {
1132: return "";
1133: }
1134: return getContent();
1135: } else {
1136: return super .getTraitImpl(name);
1137: }
1138: }
1139:
1140: /**
1141: * Text handles x, y float traits.
1142: *
1143: * @param name the trait name (e.g., "y")
1144: * @return the trait's float value (e.g., 10f)
1145: *
1146: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
1147: * trait is not supported on this element or null.
1148: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
1149: * trait's computed value cannot be converted to a float
1150: * @throws SecurityException if the application does not have the necessary
1151: * privilege rights to access this (SVG) content.
1152: */
1153: float getFloatTraitImpl(final String name) throws DOMException {
1154: if (SVGConstants.SVG_X_ATTRIBUTE == name) {
1155: return getX()[0];
1156: } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
1157: return getY()[0];
1158: } else {
1159: return super .getFloatTraitImpl(name);
1160: }
1161: }
1162:
1163: /**
1164: * @param traitName the trait name.
1165: */
1166: TraitAnim createTraitAnimImpl(final String traitName) {
1167: if (SVGConstants.SVG_X_ATTRIBUTE == traitName
1168: || SVGConstants.SVG_Y_ATTRIBUTE == traitName
1169: || SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName) {
1170: return new FloatTraitAnim(this , traitName, TRAIT_TYPE_FLOAT);
1171: } else if (SVGConstants.SVG_TEXT_PSEUDO_ATTRIBUTE == traitName) {
1172: return new StringTraitAnim(this , NULL_NS, traitName);
1173: } else {
1174: return super .createTraitAnimImpl(traitName);
1175: }
1176: }
1177:
1178: /**
1179: * Set the trait value as float.
1180: *
1181: * @param name the trait's name.
1182: * @param value the trait's value.
1183: *
1184: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
1185: * trait is not supported on this element.
1186: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
1187: * trait's value cannot be specified as a float
1188: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
1189: * value is an invalid value for the given trait.
1190: */
1191: void setFloatArrayTrait(final String name, final float[][] value)
1192: throws DOMException {
1193: if (SVGConstants.SVG_X_ATTRIBUTE == name) {
1194: setX(toTraitFloatArray(value));
1195: } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
1196: setY(toTraitFloatArray(value));
1197: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
1198: setRotate(toTraitFloatArray(value));
1199: } else {
1200: super .setFloatArrayTrait(name, value);
1201: }
1202: }
1203:
1204: /**
1205: * Validates the input trait value.
1206: *
1207: * @param traitName the name of the trait to be validated.
1208: * @param value the value to be validated
1209: * @param reqNamespaceURI the namespace of the element requesting
1210: * validation.
1211: * @param reqLocalName the local name of the element requesting validation.
1212: * @param reqTraitNamespace the namespace of the trait which has the values
1213: * value on the requesting element.
1214: * @param reqTraitName the name of the trait which has the values value on
1215: * the requesting element.
1216: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
1217: * value is incompatible with the given trait.
1218: */
1219: public float[][] validateFloatArrayTrait(final String traitName,
1220: final String value, final String reqNamespaceURI,
1221: final String reqLocalName, final String reqTraitNamespace,
1222: final String reqTraitName) throws DOMException {
1223: if (SVGConstants.SVG_X_ATTRIBUTE == traitName
1224: || SVGConstants.SVG_Y_ATTRIBUTE == traitName) {
1225: return toAnimatedFloatArray(parseFloatArrayTrait(traitName,
1226: value));
1227: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName) {
1228: float[][] v = toAnimatedFloatArray(parseFloatArrayTrait(
1229: traitName, value));
1230: // Convert from degrees to radians
1231: /*
1232: for (int i = 0; i < v.length; i++) {
1233: v[i][0] = MathSupport.toRadians(v[i][0]);
1234: }
1235: */
1236: return v;
1237: } else {
1238: return super .validateFloatArrayTrait(traitName, value,
1239: reqNamespaceURI, reqLocalName, reqTraitNamespace,
1240: reqTraitName);
1241: }
1242: }
1243:
1244: /**
1245: * Text handles x, y and #text traits.
1246: *
1247: * @param name the trait's name (e.g., "#text")
1248: * @param value the trait's value (e.g, "Hello SVG Text")
1249: *
1250: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
1251: * trait is not supported on this element or null.
1252: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
1253: * trait's value cannot be specified as a String
1254: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
1255: * value is an invalid value for the given trait or null.
1256: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
1257: * attempt is made to change readonly trait.
1258: */
1259: public void setTraitImpl(final String name, final String value)
1260: throws DOMException {
1261: if (SVGConstants.SVG_X_ATTRIBUTE == name) {
1262: setX(parseFloatArrayTrait(name, value));
1263: } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
1264: setY(parseFloatArrayTrait(name, value));
1265: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
1266: float[] rt = parseFloatArrayTrait(name, value);
1267: /*
1268: if (rt != null) {
1269: for (int i = 0; i < rt.length; i++) {
1270: rt[i] = MathSupport.toRadians(rt[i]);
1271: }
1272: }
1273: */
1274: setRotate(rt);
1275: } else if (SVGConstants.SVG_TEXT_PSEUDO_ATTRIBUTE == name) {
1276: if (value == null) {
1277: throw illegalTraitValue(name, value);
1278: }
1279: setContent(value);
1280: } else {
1281: super .setTraitImpl(name, value);
1282: }
1283: }
1284:
1285: /**
1286: * Text handles x, y float traits.
1287: *
1288: * @param name the trait's name (e.g., "x")
1289: * @param value the trait's value (e.g, 10f)
1290: *
1291: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
1292: * trait is not supported on this element.
1293: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
1294: * trait's value cannot be specified as a float
1295: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
1296: * value is an invalid value for the given trait.
1297: * @throws SecurityException if the application does not have the necessary
1298: * privilege rights to access this (SVG) content.
1299: */
1300: public void setFloatTraitImpl(final String name, final float value)
1301: throws DOMException {
1302: if (SVGConstants.SVG_X_ATTRIBUTE == name) {
1303: setX(new float[] { value });
1304: } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
1305: setY(new float[] { value });
1306: } else {
1307: super .setFloatTraitImpl(name, value);
1308: }
1309: }
1310:
1311: /**
1312: * @param name the name of the trait to convert.
1313: * @param value the float trait value to convert.
1314: */
1315: String toStringTrait(final String name, final float[][] value) {
1316: if (SVGConstants.SVG_X_ATTRIBUTE == name
1317: || SVGConstants.SVG_Y_ATTRIBUTE == name) {
1318: float[] v = new float[value.length];
1319: for (int i = 0; i < value.length; i++) {
1320: v[i] = value[i][0];
1321: }
1322: return toStringTrait(v);
1323: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
1324: float[] v = new float[value.length];
1325: for (int i = 0; i < value.length; i++) {
1326: v[i] = value[i][0];
1327: }
1328: return toStringTrait(v);
1329:
1330: } else {
1331: return super .toStringTrait(name, value);
1332: }
1333: }
1334:
1335: /**
1336: * Debug helper.
1337: * @return a <code>String</code> containing the text content
1338: */
1339: public String toString() {
1340: String xStr = "x[";
1341: for (int i = 0; i < x.length; i++) {
1342: xStr += x[i] + ", ";
1343: }
1344: xStr = xStr.substring(0, xStr.length() - 2);
1345: xStr += "]";
1346:
1347: String yStr = "y[";
1348: for (int i = 0; i < y.length; i++) {
1349: yStr += y[i] + ", ";
1350: }
1351: yStr = yStr.substring(0, yStr.length() - 2);
1352: yStr += "]";
1353:
1354: return super .toString() + "[\"" + content + "\"] " + xStr + " "
1355: + yStr;
1356: }
1357:
1358: /**
1359: * @param newDisplay the new computed display value
1360: */
1361: void setComputedDisplay(final boolean newDisplay) {
1362: super .setComputedDisplay(newDisplay);
1363:
1364: renderingDirty();
1365: }
1366:
1367: /**
1368: * @param newVisibility the new computed visibility property.
1369: */
1370: void setComputedVisibility(final boolean newVisibility) {
1371: super .setComputedVisibility(newVisibility);
1372:
1373: renderingDirty();
1374: }
1375:
1376: /**
1377: * @param newFill the new computed fill property.
1378: */
1379: void setComputedFill(final PaintServer newFill) {
1380: this .fill = newFill;
1381: renderingDirty();
1382: }
1383:
1384: /**
1385: * @param newStroke the new computed stroke property.
1386: */
1387: void setComputedStroke(final PaintServer newStroke) {
1388: this .stroke = newStroke;
1389: renderingDirty();
1390: }
1391:
1392: /**
1393: * @param newStrokeWidth the new computed stroke-width property value.
1394: */
1395: void setComputedStrokeWidth(final float newStrokeWidth) {
1396: strokeWidth = newStrokeWidth;
1397:
1398: // Only dirty rendering if the object is actually stroked.
1399: if (stroke != null) {
1400: renderingDirty();
1401: }
1402: }
1403:
1404: /**
1405: * @param newStrokeLineJoin the new computed value for stroke-line-join
1406: */
1407: void setComputedStrokeLineJoin(final int newStrokeLineJoin) {
1408: super .setComputedStrokeLineJoin(newStrokeLineJoin);
1409:
1410: if (stroke != null) {
1411: renderingDirty();
1412: }
1413: }
1414:
1415: /**
1416: * @param newStrokeLineCap the new value for the stroke-linecap property.
1417: */
1418: void setComputedStrokeLineCap(final int newStrokeLineCap) {
1419: super .setComputedStrokeLineCap(newStrokeLineCap);
1420:
1421: if (stroke != null) {
1422: renderingDirty();
1423: }
1424: }
1425:
1426: /**
1427: * @param newStrokeMiterLimit the new computed stroke-miterlimit property.
1428: */
1429: void setComputedStrokeMiterLimit(final float newStrokeMiterLimit) {
1430: strokeMiterLimit = newStrokeMiterLimit;
1431:
1432: if (stroke != null && getStrokeLineJoin() == JOIN_MITER) {
1433: renderingDirty();
1434: }
1435: }
1436:
1437: /**
1438: * @param newStrokeDashArray the new computed stroke-dasharray property
1439: * value.
1440: */
1441: void setComputedStrokeDashArray(final float[] newStrokeDashArray) {
1442: strokeDashArray = newStrokeDashArray;
1443:
1444: if (stroke != null) {
1445: renderingDirty();
1446: }
1447: }
1448:
1449: /**
1450: * @param newStrokeDashOffset the new stroke-dashoffset computed property
1451: * value.
1452: */
1453: void setComputedStrokeDashOffset(final float newStrokeDashOffset) {
1454: strokeDashOffset = newStrokeDashOffset;
1455:
1456: if (stroke != null && strokeDashArray != null) {
1457: renderingDirty();
1458: }
1459: }
1460:
1461: /**
1462: * @param newFillOpacity the new computed value for the fill opacity
1463: * property.
1464: */
1465: void setComputedFillOpacity(final float newFillOpacity) {
1466: super .setComputedFillOpacity(newFillOpacity);
1467:
1468: if (fill != null) {
1469: renderingDirty();
1470: }
1471: }
1472:
1473: /**
1474: * @param newStrokeOpacity the new computed stroke-opacity property.
1475: */
1476: void setComputedStrokeOpacity(final float newStrokeOpacity) {
1477: super .setComputedStrokeOpacity(newStrokeOpacity);
1478:
1479: if (stroke != null) {
1480: renderingDirty();
1481: }
1482: }
1483:
1484: /**
1485: * @param newFontSize the new computed font-size property value.
1486: */
1487: void setComputedFontSize(final float newFontSize) {
1488: this .fontSize = newFontSize;
1489:
1490: if (stroke != null || fill != null) {
1491: renderingDirty();
1492: }
1493:
1494: computeCanRenderFontSizeBit(newFontSize);
1495: }
1496:
1497: /**
1498: * @param newFontFamily the new computed font-family property value.
1499: */
1500: void setComputedFontFamily(final String[] newFontFamily) {
1501: this .fontFamily = newFontFamily;
1502:
1503: clearLayoutsQuiet();
1504:
1505: if (stroke != null || fill != null) {
1506: renderingDirty();
1507: }
1508: }
1509:
1510: /**
1511: * Sets the value of the computed text anchor property.
1512: *
1513: * @param newTextAnchor the new value for the computed text anchor property.
1514: */
1515: void setComputedTextAnchor(final int newTextAnchor) {
1516: super .setComputedTextAnchor(newTextAnchor);
1517:
1518: if (stroke != null || fill != null) {
1519: renderingDirty();
1520: }
1521: }
1522:
1523: /**
1524: * @param newFontWeight new computed value for the font-weight property.
1525: */
1526: void setComputedFontWeight(final int newFontWeight) {
1527: super .setComputedFontWeight(newFontWeight);
1528:
1529: clearLayoutsQuiet();
1530:
1531: if (stroke != null || fill != null) {
1532: renderingDirty();
1533: }
1534: }
1535:
1536: /**
1537: * @param newFontStyle the new computed font-style property.
1538: */
1539: void setComputedFontStyle(final int newFontStyle) {
1540: super.setComputedFontStyle(newFontStyle);
1541:
1542: clearLayoutsQuiet();
1543:
1544: if (stroke != null || fill != null) {
1545: renderingDirty();
1546: }
1547: }
1548: }
|