0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.bridge;
0020:
0021: import java.awt.AlphaComposite;
0022: import java.awt.Color;
0023: import java.awt.RenderingHints;
0024: import java.awt.Shape;
0025: import java.awt.font.TextAttribute;
0026: import java.awt.geom.AffineTransform;
0027: import java.awt.geom.GeneralPath;
0028: import java.awt.geom.Point2D;
0029: import java.awt.geom.Rectangle2D;
0030: import java.lang.ref.SoftReference;
0031: import java.text.AttributedCharacterIterator;
0032: import java.text.AttributedString;
0033: import java.text.AttributedCharacterIterator.Attribute;
0034: import java.util.ArrayList;
0035: import java.util.HashMap;
0036: import java.util.HashSet;
0037: import java.util.Iterator;
0038: import java.util.List;
0039: import java.util.Map;
0040: import java.util.Set;
0041: import java.util.WeakHashMap;
0042:
0043: import org.apache.batik.css.engine.CSSEngineEvent;
0044: import org.apache.batik.css.engine.CSSStylableElement;
0045: import org.apache.batik.css.engine.SVGCSSEngine;
0046: import org.apache.batik.css.engine.StyleMap;
0047: import org.apache.batik.css.engine.value.ListValue;
0048: import org.apache.batik.css.engine.value.Value;
0049: import org.apache.batik.dom.events.NodeEventTarget;
0050: import org.apache.batik.dom.svg.AbstractSVGAnimatedLength;
0051: import org.apache.batik.dom.svg.AnimatedLiveAttributeValue;
0052: import org.apache.batik.dom.svg.LiveAttributeException;
0053: import org.apache.batik.dom.svg.SVGContext;
0054: import org.apache.batik.dom.svg.SVGOMElement;
0055: import org.apache.batik.dom.svg.SVGOMTextPositioningElement;
0056: import org.apache.batik.dom.svg.SVGTextContent;
0057: import org.apache.batik.dom.util.XLinkSupport;
0058: import org.apache.batik.dom.util.XMLSupport;
0059: import org.apache.batik.gvt.GraphicsNode;
0060: import org.apache.batik.gvt.TextNode;
0061: import org.apache.batik.gvt.font.FontFamilyResolver;
0062: import org.apache.batik.gvt.font.GVTFont;
0063: import org.apache.batik.gvt.font.GVTFontFamily;
0064: import org.apache.batik.gvt.font.GVTGlyphMetrics;
0065: import org.apache.batik.gvt.font.GVTGlyphVector;
0066: import org.apache.batik.gvt.font.UnresolvedFontFamily;
0067: import org.apache.batik.gvt.renderer.StrokingTextPainter;
0068: import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
0069: import org.apache.batik.gvt.text.Mark;
0070: import org.apache.batik.gvt.text.TextHit;
0071: import org.apache.batik.gvt.text.TextPaintInfo;
0072: import org.apache.batik.gvt.text.TextPath;
0073: import org.apache.batik.gvt.text.TextSpanLayout;
0074: import org.apache.batik.util.XMLConstants;
0075:
0076: import org.w3c.dom.Element;
0077: import org.w3c.dom.Node;
0078: import org.w3c.dom.css.CSSPrimitiveValue;
0079: import org.w3c.dom.css.CSSValue;
0080: import org.w3c.dom.events.Event;
0081: import org.w3c.dom.events.EventListener;
0082: import org.w3c.dom.events.MutationEvent;
0083: import org.w3c.dom.svg.SVGLengthList;
0084: import org.w3c.dom.svg.SVGNumberList;
0085: import org.w3c.dom.svg.SVGTextContentElement;
0086: import org.w3c.dom.svg.SVGTextPositioningElement;
0087:
0088: /**
0089: * Bridge class for the <text> element.
0090: *
0091: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
0092: * @author <a href="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a>
0093: * @version $Id: SVGTextElementBridge.java 516914 2007-03-11 14:53:18Z dvholten $
0094: */
0095: public class SVGTextElementBridge extends AbstractGraphicsNodeBridge
0096: implements SVGTextContent {
0097:
0098: protected static final Integer ZERO = new Integer(0);
0099:
0100: public static final AttributedCharacterIterator.Attribute TEXT_COMPOUND_DELIMITER = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER;
0101:
0102: public static final AttributedCharacterIterator.Attribute TEXT_COMPOUND_ID = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_ID;
0103:
0104: public static final AttributedCharacterIterator.Attribute PAINT_INFO = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO;
0105:
0106: public static final AttributedCharacterIterator.Attribute ALT_GLYPH_HANDLER = GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER;
0107:
0108: public static final AttributedCharacterIterator.Attribute TEXTPATH = GVTAttributedCharacterIterator.TextAttribute.TEXTPATH;
0109:
0110: public static final AttributedCharacterIterator.Attribute ANCHOR_TYPE = GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE;
0111:
0112: public static final AttributedCharacterIterator.Attribute GVT_FONT_FAMILIES = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES;
0113:
0114: public static final AttributedCharacterIterator.Attribute GVT_FONTS = GVTAttributedCharacterIterator.TextAttribute.GVT_FONTS;
0115:
0116: public static final AttributedCharacterIterator.Attribute BASELINE_SHIFT = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT;
0117:
0118: protected AttributedString laidoutText;
0119:
0120: // This is used to track the TextPainterInfo for each element
0121: // in this text element.
0122: protected WeakHashMap elemTPI = new WeakHashMap();
0123:
0124: // This is true if any of the spans of this text element
0125: // use a 'complex' SVG font (meaning the font uses more
0126: // and just the 'd' attribute on the glyph element.
0127: // In this case we need to recreate the font when ever
0128: // CSS attributes change on the text - so we can capture
0129: // the effects of CSS inheritence.
0130: protected boolean usingComplexSVGFont = false;
0131:
0132: /**
0133: * Constructs a new bridge for the <text> element.
0134: */
0135: public SVGTextElementBridge() {
0136: }
0137:
0138: /**
0139: * Returns 'text'.
0140: */
0141: public String getLocalName() {
0142: return SVG_TEXT_TAG;
0143: }
0144:
0145: /**
0146: * Returns a new instance of this bridge.
0147: */
0148: public Bridge getInstance() {
0149: return new SVGTextElementBridge();
0150: }
0151:
0152: protected TextNode getTextNode() {
0153: return (TextNode) node;
0154: }
0155:
0156: /**
0157: * Creates a <tt>GraphicsNode</tt> according to the specified parameters.
0158: *
0159: * @param ctx the bridge context to use
0160: * @param e the element that describes the graphics node to build
0161: * @return a graphics node that represents the specified element
0162: */
0163: public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
0164: TextNode node = (TextNode) super .createGraphicsNode(ctx, e);
0165: if (node == null)
0166: return null;
0167:
0168: associateSVGContext(ctx, e, node);
0169:
0170: // traverse the children to add context on
0171: // <tspan>, <tref> and <textPath>
0172: Node child = getFirstChild(e);
0173: while (child != null) {
0174: if (child.getNodeType() == Node.ELEMENT_NODE) {
0175: addContextToChild(ctx, (Element) child);
0176: }
0177: child = getNextSibling(child);
0178: }
0179:
0180: // specify the text painter to use
0181: if (ctx.getTextPainter() != null)
0182: node.setTextPainter(ctx.getTextPainter());
0183:
0184: // 'text-rendering' and 'color-rendering'
0185: RenderingHints hints = null;
0186: hints = CSSUtilities.convertColorRendering(e, hints);
0187: hints = CSSUtilities.convertTextRendering(e, hints);
0188: if (hints != null)
0189: node.setRenderingHints(hints);
0190:
0191: node.setLocation(getLocation(ctx, e));
0192:
0193: return node;
0194: }
0195:
0196: /**
0197: * Creates the GraphicsNode depending on the GraphicsNodeBridge
0198: * implementation.
0199: */
0200: protected GraphicsNode instantiateGraphicsNode() {
0201: return new TextNode();
0202: }
0203:
0204: /**
0205: * Returns the text node location according to the 'x' and 'y'
0206: * attributes of the specified text element.
0207: *
0208: * @param ctx the bridge context to use
0209: * @param e the text element
0210: */
0211: protected Point2D getLocation(BridgeContext ctx, Element e) {
0212: try {
0213: SVGOMTextPositioningElement te = (SVGOMTextPositioningElement) e;
0214:
0215: // 'x' attribute - default is 0
0216: SVGLengthList xs = te.getX().getAnimVal();
0217: float x = 0;
0218: if (xs.getNumberOfItems() > 0) {
0219: x = xs.getItem(0).getValue();
0220: }
0221:
0222: // 'y' attribute - default is 0
0223: SVGLengthList ys = te.getY().getAnimVal();
0224: float y = 0;
0225: if (ys.getNumberOfItems() > 0) {
0226: y = ys.getItem(0).getValue();
0227: }
0228:
0229: return new Point2D.Float(x, y);
0230: } catch (LiveAttributeException ex) {
0231: throw new BridgeException(ctx, ex);
0232: }
0233: }
0234:
0235: protected boolean isTextElement(Element e) {
0236: if (!SVG_NAMESPACE_URI.equals(e.getNamespaceURI()))
0237: return false;
0238: String nodeName = e.getLocalName();
0239: return (nodeName.equals(SVG_TEXT_TAG)
0240: || nodeName.equals(SVG_TSPAN_TAG)
0241: || nodeName.equals(SVG_ALT_GLYPH_TAG)
0242: || nodeName.equals(SVG_A_TAG)
0243: || nodeName.equals(SVG_TEXT_PATH_TAG) || nodeName
0244: .equals(SVG_TREF_TAG));
0245: }
0246:
0247: protected boolean isTextChild(Element e) {
0248: if (!SVG_NAMESPACE_URI.equals(e.getNamespaceURI()))
0249: return false;
0250: String nodeName = e.getLocalName();
0251: return (nodeName.equals(SVG_TSPAN_TAG)
0252: || nodeName.equals(SVG_ALT_GLYPH_TAG)
0253: || nodeName.equals(SVG_A_TAG)
0254: || nodeName.equals(SVG_TEXT_PATH_TAG) || nodeName
0255: .equals(SVG_TREF_TAG));
0256: }
0257:
0258: /**
0259: * Builds using the specified BridgeContext and element, the
0260: * specified graphics node.
0261: *
0262: * @param ctx the bridge context to use
0263: * @param e the element that describes the graphics node to build
0264: * @param node the graphics node to build
0265: */
0266: public void buildGraphicsNode(BridgeContext ctx, Element e,
0267: GraphicsNode node) {
0268: e.normalize();
0269: computeLaidoutText(ctx, e, node);
0270:
0271: //
0272: // DO NOT CALL super, 'opacity' is handle during addPaintAttributes()
0273: //
0274: // 'opacity'
0275: node.setComposite(CSSUtilities.convertOpacity(e));
0276: // 'filter'
0277: node.setFilter(CSSUtilities.convertFilter(e, node, ctx));
0278: // 'mask'
0279: node.setMask(CSSUtilities.convertMask(e, node, ctx));
0280: // 'clip-path'
0281: node.setClip(CSSUtilities.convertClipPath(e, node, ctx));
0282: // 'pointer-events'
0283: node.setPointerEventType(CSSUtilities.convertPointerEvents(e));
0284:
0285: initializeDynamicSupport(ctx, e, node);
0286: if (!ctx.isDynamic()) {
0287: elemTPI.clear();
0288: }
0289: }
0290:
0291: /**
0292: * Returns false as text is not a container.
0293: */
0294: public boolean isComposite() {
0295: return false;
0296: }
0297:
0298: // Tree navigation ------------------------------------------------------
0299:
0300: /**
0301: * Returns the first child node of the given node that should be
0302: * processed by the text bridge.
0303: */
0304: protected Node getFirstChild(Node n) {
0305: return n.getFirstChild();
0306: }
0307:
0308: /**
0309: * Returns the next sibling node of the given node that should be
0310: * processed by the text bridge.
0311: */
0312: protected Node getNextSibling(Node n) {
0313: return n.getNextSibling();
0314: }
0315:
0316: /**
0317: * Returns the parent node of the given node that should be
0318: * processed by the text bridge.
0319: */
0320: protected Node getParentNode(Node n) {
0321: return n.getParentNode();
0322: }
0323:
0324: // Listener implementation ----------------------------------------------
0325:
0326: /**
0327: * The DOM EventListener to receive 'DOMNodeRemoved' event.
0328: */
0329: protected DOMChildNodeRemovedEventListener childNodeRemovedEventListener;
0330:
0331: /**
0332: * The DOM EventListener invoked when a node is removed.
0333: */
0334: protected class DOMChildNodeRemovedEventListener implements
0335: EventListener {
0336:
0337: /**
0338: * Handles 'DOMNodeRemoved' event type.
0339: */
0340: public void handleEvent(Event evt) {
0341: handleDOMChildNodeRemovedEvent((MutationEvent) evt);
0342: }
0343: }
0344:
0345: /**
0346: * The DOM EventListener to receive 'DOMSubtreeModified' event.
0347: */
0348: protected DOMSubtreeModifiedEventListener subtreeModifiedEventListener;
0349:
0350: /**
0351: * The DOM EventListener invoked when the subtree is modified.
0352: */
0353: protected class DOMSubtreeModifiedEventListener implements
0354: EventListener {
0355:
0356: /**
0357: * Handles 'DOMSubtreeModified' event type.
0358: */
0359: public void handleEvent(Event evt) {
0360: handleDOMSubtreeModifiedEvent((MutationEvent) evt);
0361: }
0362: }
0363:
0364: // BridgeUpdateHandler implementation -----------------------------------
0365:
0366: /**
0367: * This method ensures that any modification to a text
0368: * element and its children is going to be reflected
0369: * into the GVT tree.
0370: */
0371: protected void initializeDynamicSupport(BridgeContext ctx,
0372: Element e, GraphicsNode node) {
0373: super .initializeDynamicSupport(ctx, e, node);
0374:
0375: if (ctx.isDynamic()) {
0376: // Only add the listeners if we are dynamic.
0377: addTextEventListeners(ctx, (NodeEventTarget) e);
0378: }
0379: }
0380:
0381: /**
0382: * Adds the DOM listeners for this text bridge.
0383: */
0384: protected void addTextEventListeners(BridgeContext ctx,
0385: NodeEventTarget e) {
0386: if (childNodeRemovedEventListener == null) {
0387: childNodeRemovedEventListener = new DOMChildNodeRemovedEventListener();
0388: }
0389: if (subtreeModifiedEventListener == null) {
0390: subtreeModifiedEventListener = new DOMSubtreeModifiedEventListener();
0391: }
0392:
0393: //to be notified when a child is removed from the
0394: //<text> element.
0395: e.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0396: "DOMNodeRemoved", childNodeRemovedEventListener, true,
0397: null);
0398: ctx.storeEventListenerNS(e,
0399: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0400: "DOMNodeRemoved", childNodeRemovedEventListener, true);
0401:
0402: //to be notified when the modification of the subtree
0403: //of the <text> element is done
0404: e.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0405: "DOMSubtreeModified", subtreeModifiedEventListener,
0406: false, null);
0407: ctx.storeEventListenerNS(e,
0408: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0409: "DOMSubtreeModified", subtreeModifiedEventListener,
0410: false);
0411: }
0412:
0413: /**
0414: * Removes the DOM listeners for this text bridge.
0415: */
0416: protected void removeTextEventListeners(BridgeContext ctx,
0417: NodeEventTarget e) {
0418: e.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0419: "DOMNodeRemoved", childNodeRemovedEventListener, true);
0420: e.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0421: "DOMSubtreeModified", subtreeModifiedEventListener,
0422: false);
0423: }
0424:
0425: /**
0426: * Add to the element children of the node, a
0427: * <code>SVGContext</code> to support dynamic update. This is
0428: * recursive, the children of the nodes are also traversed to add
0429: * to the support elements their context
0430: *
0431: * @param ctx a <code>BridgeContext</code> value
0432: * @param e an <code>Element</code> value
0433: *
0434: * @see org.apache.batik.dom.svg.SVGContext
0435: * @see org.apache.batik.bridge.BridgeUpdateHandler
0436: */
0437: protected void addContextToChild(BridgeContext ctx, Element e) {
0438: if (SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) {
0439: if (e.getLocalName().equals(SVG_TSPAN_TAG)) {
0440: ((SVGOMElement) e).setSVGContext(new TspanBridge(ctx,
0441: this , e));
0442: } else if (e.getLocalName().equals(SVG_TEXT_PATH_TAG)) {
0443: ((SVGOMElement) e).setSVGContext(new TextPathBridge(
0444: ctx, this , e));
0445: } else if (e.getLocalName().equals(SVG_TREF_TAG)) {
0446: ((SVGOMElement) e).setSVGContext(new TRefBridge(ctx,
0447: this , e));
0448: }
0449: }
0450:
0451: Node child = getFirstChild(e);
0452: while (child != null) {
0453: if (child.getNodeType() == Node.ELEMENT_NODE) {
0454: addContextToChild(ctx, (Element) child);
0455: }
0456: child = getNextSibling(child);
0457: }
0458: }
0459:
0460: /**
0461: * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
0462: */
0463: public void handleDOMNodeInsertedEvent(MutationEvent evt) {
0464: Node childNode = (Node) evt.getTarget();
0465:
0466: //check the type of the node inserted before discard the layout
0467: //in the case of <title> or <desc> or <metadata>, the layout
0468: //is unchanged
0469: switch (childNode.getNodeType()) {
0470: case Node.TEXT_NODE: // fall-through is intended
0471: case Node.CDATA_SECTION_NODE:
0472: laidoutText = null;
0473: break;
0474: case Node.ELEMENT_NODE: {
0475: Element childElement = (Element) childNode;
0476: if (isTextChild(childElement)) {
0477: addContextToChild(ctx, childElement);
0478: laidoutText = null;
0479: }
0480: }
0481: break;
0482: default:
0483: }
0484: if (laidoutText == null) {
0485: computeLaidoutText(ctx, e, getTextNode());
0486: }
0487: }
0488:
0489: /**
0490: * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
0491: */
0492: public void handleDOMNodeRemovedEvent(MutationEvent evt) {
0493: removeTextEventListeners(ctx, (NodeEventTarget) evt.getTarget());
0494: super .handleDOMNodeRemovedEvent(evt);
0495: }
0496:
0497: /**
0498: * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
0499: */
0500: public void handleDOMChildNodeRemovedEvent(MutationEvent evt) {
0501: Node childNode = (Node) evt.getTarget();
0502:
0503: //check the type of the node inserted before discard the layout
0504: //in the case of <title> or <desc> or <metadata>, the layout
0505: //is unchanged
0506: switch (childNode.getNodeType()) {
0507: case Node.TEXT_NODE: // fall-through is intended
0508: case Node.CDATA_SECTION_NODE:
0509: //the parent has to be a displayed node
0510: if (isParentDisplayed(childNode)) {
0511: laidoutText = null;
0512: }
0513: break;
0514: case Node.ELEMENT_NODE:
0515: if (isTextChild((Element) childNode)) {
0516: laidoutText = null;
0517: }
0518: break;
0519: default:
0520: }
0521: //if the laidoutText was set to null,
0522: //then wait for DOMSubtreeChange to recompute it.
0523: }
0524:
0525: /**
0526: * Invoked when an MutationEvent of type 'DOMSubtree' is fired.
0527: */
0528: public void handleDOMSubtreeModifiedEvent(MutationEvent evt) {
0529: //an operation occured onto the children of the
0530: //text element, check if the layout was discarded
0531: if (laidoutText == null) {
0532: computeLaidoutText(ctx, e, getTextNode());
0533: }
0534: }
0535:
0536: /**
0537: * Invoked when an MutationEvent of type 'DOMCharacterDataModified'
0538: * is fired.
0539: */
0540: public void handleDOMCharacterDataModified(MutationEvent evt) {
0541: Node childNode = (Node) evt.getTarget();
0542: //if the parent is displayed, then discard the layout.
0543: if (isParentDisplayed(childNode)) {
0544: laidoutText = null;
0545: }
0546: }
0547:
0548: /**
0549: * Indicate of the parent of a node is
0550: * a displayed element.
0551: * <title>, <desc> and <metadata>
0552: * are non displayable elements.
0553: *
0554: * @return true if the parent of the node is <text>,
0555: * <tspan>, <tref>, <textPath>, <a>,
0556: * <altGlyph>
0557: */
0558: protected boolean isParentDisplayed(Node childNode) {
0559: Node parentNode = getParentNode(childNode);
0560: return isTextElement((Element) parentNode);
0561: }
0562:
0563: /**
0564: * Recompute the layout of the <text> node.
0565: *
0566: * Assign onto the TextNode pending to the element
0567: * the new recomputed AttributedString. Also
0568: * update <code>laidoutText</code> with the new
0569: * value.
0570: */
0571: protected void computeLaidoutText(BridgeContext ctx, Element e,
0572: GraphicsNode node) {
0573: TextNode tn = (TextNode) node;
0574: elemTPI.clear();
0575:
0576: AttributedString as = buildAttributedString(ctx, e);
0577: if (as == null) {
0578: tn.setAttributedCharacterIterator(null);
0579: return;
0580: }
0581:
0582: addGlyphPositionAttributes(as, e, ctx);
0583: if (ctx.isDynamic()) {
0584: laidoutText = new AttributedString(as.getIterator());
0585: }
0586:
0587: // Install the ACI in the text node.
0588: tn.setAttributedCharacterIterator(as.getIterator());
0589:
0590: // Now get the real paint into - this needs to
0591: // wait until the text node is laidout so we can get
0592: // objectBoundingBox info.
0593: TextPaintInfo pi = new TextPaintInfo();
0594: setBaseTextPaintInfo(pi, e, node, ctx);
0595: // This get's Overline/underline info.
0596: setDecorationTextPaintInfo(pi, e);
0597: // Install the attributes.
0598: addPaintAttributes(as, e, tn, pi, ctx);
0599:
0600: if (usingComplexSVGFont) {
0601: // Force Complex SVG fonts to be recreated, if we have them.
0602: tn.setAttributedCharacterIterator(as.getIterator());
0603: }
0604:
0605: if (ctx.isDynamic()) {
0606: checkBBoxChange();
0607: }
0608: }
0609:
0610: /**
0611: * This flag bit indicates if a new ACI has been created in
0612: * response to a CSSEngineEvent.
0613: * Avoid creating one ShapePainter per CSS property change
0614: */
0615: private boolean hasNewACI;
0616:
0617: /**
0618: * This is the element a CSS property has changed.
0619: */
0620: private Element cssProceedElement;
0621:
0622: /**
0623: * Invoked when the animated value of an animatable attribute has changed.
0624: */
0625: public void handleAnimatedAttributeChanged(
0626: AnimatedLiveAttributeValue alav) {
0627: if (alav.getNamespaceURI() == null) {
0628: String ln = alav.getLocalName();
0629: if (ln.equals(SVG_X_ATTRIBUTE)
0630: || ln.equals(SVG_Y_ATTRIBUTE)
0631: || ln.equals(SVG_DX_ATTRIBUTE)
0632: || ln.equals(SVG_DY_ATTRIBUTE)
0633: || ln.equals(SVG_ROTATE_ATTRIBUTE)
0634: || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE)
0635: || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) {
0636: char c = ln.charAt(0);
0637: if (c == 'x' || c == 'y') {
0638: getTextNode().setLocation(getLocation(ctx, e));
0639: }
0640: computeLaidoutText(ctx, e, getTextNode());
0641: return;
0642: }
0643: }
0644: super .handleAnimatedAttributeChanged(alav);
0645: }
0646:
0647: /**
0648: * Invoked when CSS properties have changed on an element.
0649: *
0650: * @param evt the CSSEngine event that describes the update
0651: */
0652: public void handleCSSEngineEvent(CSSEngineEvent evt) {
0653: hasNewACI = false;
0654: int[] properties = evt.getProperties();
0655: // first try to find CSS properties that change the layout
0656: for (int i = 0; i < properties.length; ++i) {
0657: switch (properties[i]) { // fall-through is intended
0658: case SVGCSSEngine.BASELINE_SHIFT_INDEX:
0659: case SVGCSSEngine.DIRECTION_INDEX:
0660: case SVGCSSEngine.DISPLAY_INDEX:
0661: case SVGCSSEngine.FONT_FAMILY_INDEX:
0662: case SVGCSSEngine.FONT_SIZE_INDEX:
0663: case SVGCSSEngine.FONT_STRETCH_INDEX:
0664: case SVGCSSEngine.FONT_STYLE_INDEX:
0665: case SVGCSSEngine.FONT_WEIGHT_INDEX:
0666: case SVGCSSEngine.GLYPH_ORIENTATION_HORIZONTAL_INDEX:
0667: case SVGCSSEngine.GLYPH_ORIENTATION_VERTICAL_INDEX:
0668: case SVGCSSEngine.KERNING_INDEX:
0669: case SVGCSSEngine.LETTER_SPACING_INDEX:
0670: case SVGCSSEngine.TEXT_ANCHOR_INDEX:
0671: case SVGCSSEngine.UNICODE_BIDI_INDEX:
0672: case SVGCSSEngine.WORD_SPACING_INDEX:
0673: case SVGCSSEngine.WRITING_MODE_INDEX: {
0674: if (!hasNewACI) {
0675: hasNewACI = true;
0676: computeLaidoutText(ctx, e, getTextNode());
0677: }
0678: break;
0679: }
0680: }
0681: }
0682: //optimize the calculation of
0683: //the painting attributes and decoration
0684: //by only recomputing the section for the element
0685: cssProceedElement = evt.getElement();
0686: // go for the other CSS properties
0687: super .handleCSSEngineEvent(evt);
0688: cssProceedElement = null;
0689: }
0690:
0691: /**
0692: * Invoked for each CSS property that has changed.
0693: */
0694: protected void handleCSSPropertyChanged(int property) {
0695: switch (property) { // fall-through is intended
0696: case SVGCSSEngine.FILL_INDEX:
0697: case SVGCSSEngine.FILL_OPACITY_INDEX:
0698: case SVGCSSEngine.STROKE_INDEX:
0699: case SVGCSSEngine.STROKE_OPACITY_INDEX:
0700: case SVGCSSEngine.STROKE_WIDTH_INDEX:
0701: case SVGCSSEngine.STROKE_LINECAP_INDEX:
0702: case SVGCSSEngine.STROKE_LINEJOIN_INDEX:
0703: case SVGCSSEngine.STROKE_MITERLIMIT_INDEX:
0704: case SVGCSSEngine.STROKE_DASHARRAY_INDEX:
0705: case SVGCSSEngine.STROKE_DASHOFFSET_INDEX:
0706: case SVGCSSEngine.TEXT_DECORATION_INDEX:
0707: rebuildACI();
0708: break;
0709:
0710: case SVGCSSEngine.VISIBILITY_INDEX:
0711: rebuildACI();
0712: super .handleCSSPropertyChanged(property);
0713: break;
0714: case SVGCSSEngine.TEXT_RENDERING_INDEX: {
0715: RenderingHints hints = node.getRenderingHints();
0716: hints = CSSUtilities.convertTextRendering(e, hints);
0717: if (hints != null) {
0718: node.setRenderingHints(hints);
0719: }
0720: break;
0721: }
0722: case SVGCSSEngine.COLOR_RENDERING_INDEX: {
0723: RenderingHints hints = node.getRenderingHints();
0724: hints = CSSUtilities.convertColorRendering(e, hints);
0725: if (hints != null) {
0726: node.setRenderingHints(hints);
0727: }
0728: break;
0729: }
0730: default:
0731: super .handleCSSPropertyChanged(property);
0732: }
0733: }
0734:
0735: protected void rebuildACI() {
0736: if (hasNewACI)
0737: return;
0738:
0739: TextNode textNode = getTextNode();
0740: if (textNode.getAttributedCharacterIterator() == null)
0741: return;
0742:
0743: TextPaintInfo pi, oldPI;
0744: if (cssProceedElement == e) {
0745: pi = new TextPaintInfo();
0746: setBaseTextPaintInfo(pi, e, node, ctx);
0747: setDecorationTextPaintInfo(pi, e);
0748: oldPI = (TextPaintInfo) elemTPI.get(e);
0749: } else {
0750: //if a child CSS property has changed, then
0751: //retrieve the parent text decoration
0752: //and only update the section of the AtrtibutedString of
0753: //the child
0754: TextPaintInfo parentPI;
0755: parentPI = getParentTextPaintInfo(cssProceedElement);
0756: pi = getTextPaintInfo(cssProceedElement, textNode,
0757: parentPI, ctx);
0758: oldPI = (TextPaintInfo) elemTPI.get(cssProceedElement);
0759: }
0760: if (oldPI == null)
0761: return;
0762:
0763: textNode.swapTextPaintInfo(pi, oldPI);
0764: if (usingComplexSVGFont)
0765: // Force Complex SVG fonts to be recreated
0766: textNode.setAttributedCharacterIterator(textNode
0767: .getAttributedCharacterIterator());
0768: }
0769:
0770: int getElementStartIndex(Element element) {
0771: TextPaintInfo tpi = (TextPaintInfo) elemTPI.get(element);
0772: if (tpi == null)
0773: return -1;
0774: return tpi.startChar;
0775: }
0776:
0777: int getElementEndIndex(Element element) {
0778: TextPaintInfo tpi = (TextPaintInfo) elemTPI.get(element);
0779: if (tpi == null)
0780: return -1;
0781: return tpi.endChar;
0782: }
0783:
0784: // -----------------------------------------------------------------------
0785: // -----------------------------------------------------------------------
0786: // -----------------------------------------------------------------------
0787: // -----------------------------------------------------------------------
0788:
0789: /**
0790: * Creates the attributed string which represents the given text
0791: * element children.
0792: *
0793: * @param ctx the bridge context to use
0794: * @param element the text element
0795: */
0796: protected AttributedString buildAttributedString(BridgeContext ctx,
0797: Element element) {
0798:
0799: AttributedStringBuffer asb = new AttributedStringBuffer();
0800: fillAttributedStringBuffer(ctx, element, true, null, null, asb);
0801: return asb.toAttributedString();
0802: }
0803:
0804: /**
0805: * This is used to store the end of the last piece of text
0806: * content from an element with xml:space="preserve". When
0807: * we are stripping trailing spaces we need to make sure
0808: * we don't strip anything before this point.
0809: */
0810: protected int endLimit;
0811:
0812: /**
0813: * Fills the given AttributedStringBuffer.
0814: */
0815: protected void fillAttributedStringBuffer(BridgeContext ctx,
0816: Element element, boolean top, TextPath textPath,
0817: Integer bidiLevel, AttributedStringBuffer asb) {
0818: // 'requiredFeatures', 'requiredExtensions', 'systemLanguage' &
0819: // 'display="none".
0820: if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent()))
0821: || (!CSSUtilities.convertDisplay(element))) {
0822: return;
0823: }
0824:
0825: String s = XMLSupport.getXMLSpace(element);
0826: boolean preserve = s.equals(SVG_PRESERVE_VALUE);
0827: boolean prevEndsWithSpace;
0828: Element nodeElement = element;
0829: int elementStartChar = asb.length();
0830:
0831: if (top)
0832: endLimit = 0;
0833: if (preserve)
0834: endLimit = asb.length();
0835:
0836: Map map = getAttributeMap(ctx, element, textPath, bidiLevel);
0837: Object o = map.get(TextAttribute.BIDI_EMBEDDING);
0838: Integer subBidiLevel = bidiLevel;
0839: if (o != null)
0840: subBidiLevel = ((Integer) o);
0841:
0842: for (Node n = getFirstChild(element); n != null; n = getNextSibling(n)) {
0843:
0844: if (preserve) {
0845: prevEndsWithSpace = false;
0846: } else {
0847: if (asb.length() == 0)
0848: prevEndsWithSpace = true;
0849: else
0850: prevEndsWithSpace = (asb.getLastChar() == ' ');
0851: }
0852:
0853: switch (n.getNodeType()) {
0854: case Node.ELEMENT_NODE:
0855: if (!SVG_NAMESPACE_URI.equals(n.getNamespaceURI()))
0856: break;
0857:
0858: nodeElement = (Element) n;
0859:
0860: String ln = n.getLocalName();
0861:
0862: if (ln.equals(SVG_TSPAN_TAG)
0863: || ln.equals(SVG_ALT_GLYPH_TAG)) {
0864: fillAttributedStringBuffer(ctx, nodeElement, false,
0865: textPath, subBidiLevel, asb);
0866: } else if (ln.equals(SVG_TEXT_PATH_TAG)) {
0867: SVGTextPathElementBridge textPathBridge = (SVGTextPathElementBridge) ctx
0868: .getBridge(nodeElement);
0869: TextPath newTextPath = textPathBridge
0870: .createTextPath(ctx, nodeElement);
0871: if (newTextPath != null) {
0872: fillAttributedStringBuffer(ctx, nodeElement,
0873: false, newTextPath, subBidiLevel, asb);
0874: }
0875: } else if (ln.equals(SVG_TREF_TAG)) {
0876: String uriStr = XLinkSupport
0877: .getXLinkHref((Element) n);
0878: Element ref = ctx.getReferencedElement((Element) n,
0879: uriStr);
0880: s = TextUtilities.getElementContent(ref);
0881: s = normalizeString(s, preserve, prevEndsWithSpace);
0882: if (s != null) {
0883: int trefStart = asb.length();
0884: Map m = getAttributeMap(ctx, nodeElement,
0885: textPath, bidiLevel);
0886: asb.append(s, m);
0887: int trefEnd = asb.length() - 1;
0888: TextPaintInfo tpi;
0889: tpi = (TextPaintInfo) elemTPI.get(nodeElement);
0890: tpi.startChar = trefStart;
0891: tpi.endChar = trefEnd;
0892: }
0893: } else if (ln.equals(SVG_A_TAG)) {
0894: NodeEventTarget target = (NodeEventTarget) nodeElement;
0895: UserAgent ua = ctx.getUserAgent();
0896: SVGAElementBridge.CursorHolder ch;
0897: ch = new SVGAElementBridge.CursorHolder(
0898: CursorManager.DEFAULT_CURSOR);
0899: EventListener l;
0900: l = new SVGAElementBridge.AnchorListener(ua, ch);
0901: target.addEventListenerNS(
0902: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0903: SVG_EVENT_CLICK, l, false, null);
0904: ctx.storeEventListenerNS(target,
0905: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0906: SVG_EVENT_CLICK, l, false);
0907:
0908: fillAttributedStringBuffer(ctx, nodeElement, false,
0909: textPath, subBidiLevel, asb);
0910: }
0911: break;
0912: case Node.TEXT_NODE: // fall-through is intended
0913: case Node.CDATA_SECTION_NODE:
0914: s = n.getNodeValue();
0915: s = normalizeString(s, preserve, prevEndsWithSpace);
0916: asb.append(s, map);
0917: if (preserve)
0918: endLimit = asb.length();
0919: }
0920: }
0921:
0922: if (top) {
0923: boolean strippedSome = false;
0924: while ((endLimit < asb.length())
0925: && (asb.getLastChar() == ' ')) {
0926: asb.stripLast();
0927: strippedSome = true;
0928: }
0929: if (strippedSome) {
0930: Iterator iter = elemTPI.values().iterator();
0931: while (iter.hasNext()) {
0932: TextPaintInfo tpi = (TextPaintInfo) iter.next();
0933: if (tpi.endChar >= asb.length()) {
0934: tpi.endChar = asb.length() - 1;
0935: if (tpi.startChar > tpi.endChar)
0936: tpi.startChar = tpi.endChar;
0937: }
0938: }
0939: }
0940: }
0941: int elementEndChar = asb.length() - 1;
0942: TextPaintInfo tpi = (TextPaintInfo) elemTPI.get(element);
0943: tpi.startChar = elementStartChar;
0944: tpi.endChar = elementEndChar;
0945: }
0946:
0947: /**
0948: * Normalizes the given string.
0949: */
0950: protected String normalizeString(String s, boolean preserve,
0951: boolean stripfirst) {
0952: StringBuffer sb = new StringBuffer(s.length());
0953: if (preserve) {
0954: for (int i = 0; i < s.length(); i++) {
0955: char c = s.charAt(i);
0956: switch (c) { // fall-through is intended
0957: case 10:
0958: case 13:
0959: case '\t':
0960: sb.append(' ');
0961: break;
0962: default:
0963: sb.append(c);
0964: }
0965: }
0966: return sb.toString();
0967: }
0968:
0969: int idx = 0;
0970: if (stripfirst) {
0971: loop: while (idx < s.length()) {
0972: switch (s.charAt(idx)) {
0973: default:
0974: break loop;
0975: case 10: // fall-through is intended
0976: case 13:
0977: case ' ':
0978: case '\t':
0979: idx++;
0980: }
0981: }
0982: }
0983:
0984: boolean space = false;
0985: for (int i = idx; i < s.length(); i++) {
0986: char c = s.charAt(i);
0987: switch (c) {
0988: case 10: // fall-through is intended
0989: case 13:
0990: break;
0991: case ' ': // fall-through is intended
0992: case '\t':
0993: if (!space) {
0994: sb.append(' ');
0995: space = true;
0996: }
0997: break;
0998: default:
0999: sb.append(c);
1000: space = false;
1001: }
1002: }
1003:
1004: return sb.toString();
1005: }
1006:
1007: /**
1008: * This class is used to build an AttributedString.
1009: */
1010: protected static class AttributedStringBuffer {
1011:
1012: /**
1013: * The strings.
1014: */
1015: protected List strings;
1016:
1017: /**
1018: * The attributes.
1019: */
1020: protected List attributes;
1021:
1022: /**
1023: * The number of items.
1024: */
1025: protected int count;
1026:
1027: /**
1028: * The length of the attributed string.
1029: */
1030: protected int length;
1031:
1032: /**
1033: * Creates a new empty AttributedStringBuffer.
1034: */
1035: public AttributedStringBuffer() {
1036: strings = new ArrayList();
1037: attributes = new ArrayList();
1038: count = 0;
1039: length = 0;
1040: }
1041:
1042: /**
1043: * Tells whether this AttributedStringBuffer is empty.
1044: */
1045: public boolean isEmpty() {
1046: return count == 0;
1047: }
1048:
1049: /**
1050: * Returns the length in chars of the current Attributed String
1051: */
1052: public int length() {
1053: return length;
1054: }
1055:
1056: /**
1057: * Appends a String and its associated attributes.
1058: */
1059: public void append(String s, Map m) {
1060: if (s.length() == 0)
1061: return;
1062: strings.add(s);
1063: attributes.add(m);
1064: count++;
1065: length += s.length();
1066: }
1067:
1068: /**
1069: * Returns the value of the last char or -1.
1070: */
1071: public int getLastChar() {
1072: if (count == 0) {
1073: return -1;
1074: }
1075: String s = (String) strings.get(count - 1);
1076: return s.charAt(s.length() - 1);
1077: }
1078:
1079: /**
1080: * Strips the last string character.
1081: */
1082: public void stripFirst() {
1083: String s = (String) strings.get(0);
1084: if (s.charAt(s.length() - 1) != ' ')
1085: return;
1086:
1087: length--;
1088:
1089: if (s.length() == 1) {
1090: attributes.remove(0);
1091: strings.remove(0);
1092: count--;
1093: return;
1094: }
1095:
1096: strings.set(0, s.substring(1));
1097: }
1098:
1099: /**
1100: * Strips the last string character.
1101: */
1102: public void stripLast() {
1103: String s = (String) strings.get(count - 1);
1104: if (s.charAt(s.length() - 1) != ' ')
1105: return;
1106:
1107: length--;
1108:
1109: if (s.length() == 1) {
1110: attributes.remove(--count);
1111: strings.remove(count);
1112: return;
1113: }
1114:
1115: strings.set(count - 1, s.substring(0, s.length() - 1));
1116: }
1117:
1118: /**
1119: * Builds an attributed string from the content of this
1120: * buffer.
1121: */
1122: public AttributedString toAttributedString() {
1123: switch (count) {
1124: case 0:
1125: return null;
1126: case 1:
1127: return new AttributedString((String) strings.get(0),
1128: (Map) attributes.get(0));
1129: }
1130:
1131: StringBuffer sb = new StringBuffer(strings.size() * 5);
1132: Iterator it = strings.iterator();
1133: while (it.hasNext()) {
1134: sb.append((String) it.next());
1135: }
1136:
1137: AttributedString result = new AttributedString(sb
1138: .toString());
1139:
1140: // Set the attributes
1141:
1142: Iterator sit = strings.iterator();
1143: Iterator ait = attributes.iterator();
1144: int idx = 0;
1145: while (sit.hasNext()) {
1146: String s = (String) sit.next();
1147: int nidx = idx + s.length();
1148: Map m = (Map) ait.next();
1149: Iterator kit = m.keySet().iterator();
1150: Iterator vit = m.values().iterator();
1151: while (kit.hasNext()) {
1152: Attribute attr = (Attribute) kit.next();
1153: Object val = vit.next();
1154: result.addAttribute(attr, val, idx, nidx);
1155: }
1156: idx = nidx;
1157: }
1158:
1159: return result;
1160: }
1161:
1162: public String toString() {
1163: switch (count) {
1164: case 0:
1165: return "";
1166: case 1:
1167: return (String) strings.get(0);
1168: }
1169:
1170: StringBuffer sb = new StringBuffer(strings.size() * 5);
1171: Iterator it = strings.iterator();
1172: while (it.hasNext()) {
1173: sb.append((String) it.next());
1174: }
1175: return sb.toString();
1176: }
1177: }
1178:
1179: /**
1180: * Returns true if node1 is an ancestor of node2
1181: */
1182: protected boolean nodeAncestorOf(Node node1, Node node2) {
1183: if (node2 == null || node1 == null) {
1184: return false;
1185: }
1186: Node parent = getParentNode(node2);
1187: while (parent != null && parent != node1) {
1188: parent = getParentNode(parent);
1189: }
1190: return (parent == node1);
1191: }
1192:
1193: /**
1194: * Adds glyph position attributes to an AttributedString.
1195: */
1196: protected void addGlyphPositionAttributes(AttributedString as,
1197: Element element, BridgeContext ctx) {
1198:
1199: // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage'
1200: if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent()))
1201: || (!CSSUtilities.convertDisplay(element))) {
1202: return;
1203: }
1204: if (element.getLocalName().equals(SVG_TEXT_PATH_TAG)) {
1205: // 'textPath' doesn't support position attributes.
1206: addChildGlyphPositionAttributes(as, element, ctx);
1207: return;
1208: }
1209:
1210: // calculate which chars in the string belong to this element
1211: int firstChar = getElementStartIndex(element);
1212: // No match so no chars to annotate.
1213: if (firstChar == -1)
1214: return;
1215:
1216: int lastChar = getElementEndIndex(element);
1217:
1218: // 'a' elements aren't SVGTextPositioningElements, so don't process
1219: // their positioning attributes on them.
1220: if (!(element instanceof SVGTextPositioningElement)) {
1221: addChildGlyphPositionAttributes(as, element, ctx);
1222: return;
1223: }
1224:
1225: // get all of the glyph position attribute values
1226: SVGTextPositioningElement te = (SVGTextPositioningElement) element;
1227:
1228: try {
1229: SVGLengthList xs = te.getX().getAnimVal();
1230: SVGLengthList ys = te.getY().getAnimVal();
1231: SVGLengthList dxs = te.getDx().getAnimVal();
1232: SVGLengthList dys = te.getDy().getAnimVal();
1233: SVGNumberList rs = te.getRotate().getAnimVal();
1234:
1235: int len;
1236:
1237: // process the x attribute
1238: len = xs.getNumberOfItems();
1239: for (int i = 0; i < len && firstChar + i <= lastChar; i++) {
1240: as.addAttribute(
1241: GVTAttributedCharacterIterator.TextAttribute.X,
1242: new Float(xs.getItem(i).getValue()), firstChar
1243: + i, firstChar + i + 1);
1244: }
1245:
1246: // process the y attribute
1247: len = ys.getNumberOfItems();
1248: for (int i = 0; i < len && firstChar + i <= lastChar; i++) {
1249: as.addAttribute(
1250: GVTAttributedCharacterIterator.TextAttribute.Y,
1251: new Float(ys.getItem(i).getValue()), firstChar
1252: + i, firstChar + i + 1);
1253: }
1254:
1255: // process dx attribute
1256: len = dxs.getNumberOfItems();
1257: for (int i = 0; i < len && firstChar + i <= lastChar; i++) {
1258: as
1259: .addAttribute(
1260: GVTAttributedCharacterIterator.TextAttribute.DX,
1261: new Float(dxs.getItem(i).getValue()),
1262: firstChar + i, firstChar + i + 1);
1263: }
1264:
1265: // process dy attribute
1266: len = dys.getNumberOfItems();
1267: for (int i = 0; i < len && firstChar + i <= lastChar; i++) {
1268: as
1269: .addAttribute(
1270: GVTAttributedCharacterIterator.TextAttribute.DY,
1271: new Float(dys.getItem(i).getValue()),
1272: firstChar + i, firstChar + i + 1);
1273: }
1274:
1275: // process rotate attribute
1276: len = rs.getNumberOfItems();
1277: if (len == 1) { // not a list
1278: // each char will have the same rotate value
1279: Float rad = new Float(Math.toRadians(rs.getItem(0)
1280: .getValue()));
1281: as
1282: .addAttribute(
1283: GVTAttributedCharacterIterator.TextAttribute.ROTATION,
1284: rad, firstChar, lastChar + 1);
1285:
1286: } else if (len > 1) { // it's a list
1287: // set each rotate value from the list
1288: for (int i = 0; i < len && firstChar + i <= lastChar; i++) {
1289: Float rad = new Float(Math.toRadians(rs.getItem(i)
1290: .getValue()));
1291: as
1292: .addAttribute(
1293: GVTAttributedCharacterIterator.TextAttribute.ROTATION,
1294: rad, firstChar + i, firstChar + i
1295: + 1);
1296: }
1297: }
1298:
1299: addChildGlyphPositionAttributes(as, element, ctx);
1300: } catch (LiveAttributeException ex) {
1301: throw new BridgeException(ctx, ex);
1302: }
1303: }
1304:
1305: protected void addChildGlyphPositionAttributes(AttributedString as,
1306: Element element, BridgeContext ctx) {
1307: // do the same for each child element
1308: for (Node child = getFirstChild(element); child != null; child = getNextSibling(child)) {
1309: if (child.getNodeType() != Node.ELEMENT_NODE)
1310: continue;
1311:
1312: Element childElement = (Element) child;
1313: if (isTextChild(childElement)) {
1314: addGlyphPositionAttributes(as, childElement, ctx);
1315: }
1316: }
1317: }
1318:
1319: /**
1320: * Adds painting attributes to an AttributedString.
1321: */
1322: protected void addPaintAttributes(AttributedString as,
1323: Element element, TextNode node, TextPaintInfo pi,
1324: BridgeContext ctx) {
1325: // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage'
1326: if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent()))
1327: || (!CSSUtilities.convertDisplay(element))) {
1328: return;
1329: }
1330: Object o = elemTPI.get(element);
1331: if (o != null) {
1332: node.swapTextPaintInfo(pi, (TextPaintInfo) o);
1333: }
1334: addChildPaintAttributes(as, element, node, pi, ctx);
1335: }
1336:
1337: protected void addChildPaintAttributes(AttributedString as,
1338: Element element, TextNode node, TextPaintInfo parentPI,
1339: BridgeContext ctx) {
1340: // Add Paint attributres for children of text element
1341: for (Node child = getFirstChild(element); child != null; child = getNextSibling(child)) {
1342: if (child.getNodeType() != Node.ELEMENT_NODE) {
1343: continue;
1344: }
1345: Element childElement = (Element) child;
1346: if (isTextChild(childElement)) {
1347: TextPaintInfo pi = getTextPaintInfo(childElement, node,
1348: parentPI, ctx);
1349: addPaintAttributes(as, childElement, node, pi, ctx);
1350: }
1351: }
1352: }
1353:
1354: /**
1355: * This method adds all the font related properties to <tt>result</tt>
1356: * It also builds a List of the GVTFonts and returns it.
1357: */
1358: protected List getFontList(BridgeContext ctx, Element element,
1359: Map result) {
1360:
1361: // Unique value for text element - used for run identification.
1362: result.put(TEXT_COMPOUND_ID, new SoftReference(element));
1363:
1364: Float fsFloat = TextUtilities.convertFontSize(element);
1365: float fontSize = fsFloat.floatValue();
1366: // Font size.
1367: result.put(TextAttribute.SIZE, fsFloat);
1368:
1369: // Font stretch
1370: result.put(TextAttribute.WIDTH, TextUtilities
1371: .convertFontStretch(element));
1372:
1373: // Font style
1374: result.put(TextAttribute.POSTURE, TextUtilities
1375: .convertFontStyle(element));
1376:
1377: // Font weight
1378: result.put(TextAttribute.WEIGHT, TextUtilities
1379: .convertFontWeight(element));
1380:
1381: // Font weight
1382: Value v = CSSUtilities.getComputedStyle(element,
1383: SVGCSSEngine.FONT_WEIGHT_INDEX);
1384: String fontWeightString = v.getCssText();
1385:
1386: // Font style
1387: String fontStyleString = CSSUtilities.getComputedStyle(element,
1388: SVGCSSEngine.FONT_STYLE_INDEX).getStringValue();
1389:
1390: // Needed for SVG fonts (also for dynamic documents).
1391: result.put(TEXT_COMPOUND_DELIMITER, element);
1392:
1393: // make a list of GVTFont objects
1394: Value val = CSSUtilities.getComputedStyle(element,
1395: SVGCSSEngine.FONT_FAMILY_INDEX);
1396: List fontFamilyList = new ArrayList();
1397: List fontList = new ArrayList();
1398: int len = val.getLength();
1399: for (int i = 0; i < len; i++) {
1400: Value it = val.item(i);
1401: String fontFamilyName = it.getStringValue();
1402: GVTFontFamily fontFamily;
1403: fontFamily = SVGFontUtilities.getFontFamily(element, ctx,
1404: fontFamilyName, fontWeightString, fontStyleString);
1405: if (fontFamily == null)
1406: continue;
1407: if (fontFamily instanceof UnresolvedFontFamily) {
1408: fontFamily = FontFamilyResolver
1409: .resolve((UnresolvedFontFamily) fontFamily);
1410: if (fontFamily == null)
1411: continue;
1412: }
1413: fontFamilyList.add(fontFamily);
1414: if (fontFamily instanceof SVGFontFamily) {
1415: SVGFontFamily svgFF = (SVGFontFamily) fontFamily;
1416: if (svgFF.isComplex()) {
1417: usingComplexSVGFont = true;
1418: }
1419: }
1420: GVTFont ft = fontFamily.deriveFont(fontSize, result);
1421: fontList.add(ft);
1422: }
1423:
1424: // Eventually this will need to go for SVG fonts it
1425: // holds hard ref to DOM.
1426: result.put(GVT_FONT_FAMILIES, fontFamilyList);
1427:
1428: if (!ctx.isDynamic()) {
1429: // Only leave this in the map for dynamic documents.
1430: // Otherwise it will cause the whole DOM to stay when
1431: // we don't really need it.
1432: result.remove(TEXT_COMPOUND_DELIMITER);
1433: }
1434: return fontList;
1435: }
1436:
1437: /**
1438: * Returns the map to pass to the current characters.
1439: */
1440: protected Map getAttributeMap(BridgeContext ctx, Element element,
1441: TextPath textPath, Integer bidiLevel) {
1442: SVGTextContentElement tce = null;
1443: if (element instanceof SVGTextContentElement) {
1444: // 'a' elements aren't SVGTextContentElements, so they shouldn't
1445: // be checked for 'textLength' or 'lengthAdjust' attributes.
1446: tce = (SVGTextContentElement) element;
1447: }
1448:
1449: Map result = new HashMap();
1450: String s;
1451:
1452: if (SVG_NAMESPACE_URI.equals(element.getNamespaceURI())
1453: && element.getLocalName().equals(SVG_ALT_GLYPH_TAG)) {
1454: result.put(ALT_GLYPH_HANDLER, new SVGAltGlyphHandler(ctx,
1455: element));
1456: }
1457:
1458: // Add null TPI objects to the text (after we set it on the
1459: // Text we will swap in the correct values.
1460: TextPaintInfo pi = new TextPaintInfo();
1461: // Set some basic props so we can get bounds info for complex paints.
1462: pi.visible = true;
1463: pi.fillPaint = Color.black;
1464: result.put(PAINT_INFO, pi);
1465: elemTPI.put(element, pi);
1466:
1467: if (textPath != null) {
1468: result.put(TEXTPATH, textPath);
1469: }
1470:
1471: // Text-anchor
1472: TextNode.Anchor a = TextUtilities.convertTextAnchor(element);
1473: result.put(ANCHOR_TYPE, a);
1474:
1475: // Font family
1476: List fontList = getFontList(ctx, element, result);
1477: result.put(GVT_FONTS, fontList);
1478:
1479: // Text baseline adjustment.
1480: Object bs = TextUtilities.convertBaselineShift(element);
1481: if (bs != null) {
1482: result.put(BASELINE_SHIFT, bs);
1483: }
1484:
1485: // Unicode-bidi mode
1486: Value val = CSSUtilities.getComputedStyle(element,
1487: SVGCSSEngine.UNICODE_BIDI_INDEX);
1488: s = val.getStringValue();
1489: if (s.charAt(0) == 'n') {
1490: if (bidiLevel != null)
1491: result.put(TextAttribute.BIDI_EMBEDDING, bidiLevel);
1492: } else {
1493:
1494: // Text direction
1495: // XXX: this needs to coordinate with the unicode-bidi
1496: // property, so that when an explicit reversal
1497: // occurs, the BIDI_EMBEDDING level is
1498: // appropriately incremented or decremented.
1499: // Note that direction is implicitly handled by unicode
1500: // BiDi algorithm in most cases, this property
1501: // is only needed when one wants to override the
1502: // normal writing direction for a string/substring.
1503:
1504: val = CSSUtilities.getComputedStyle(element,
1505: SVGCSSEngine.DIRECTION_INDEX);
1506: String rs = val.getStringValue();
1507: int cbidi = 0;
1508: if (bidiLevel != null)
1509: cbidi = bidiLevel.intValue();
1510:
1511: // We don't care if it was embed or override we just want
1512: // it's level here. So map override to positive value.
1513: if (cbidi < 0)
1514: cbidi = -cbidi;
1515:
1516: switch (rs.charAt(0)) {
1517: case 'l':
1518: result.put(TextAttribute.RUN_DIRECTION,
1519: TextAttribute.RUN_DIRECTION_LTR);
1520: if ((cbidi & 0x1) == 1)
1521: cbidi++; // was odd now even
1522: else
1523: cbidi += 2; // next greater even number
1524: break;
1525: case 'r':
1526: result.put(TextAttribute.RUN_DIRECTION,
1527: TextAttribute.RUN_DIRECTION_RTL);
1528: if ((cbidi & 0x1) == 1)
1529: cbidi += 2; // next greater odd number
1530: else
1531: cbidi++; // was even now odd
1532: break;
1533: }
1534:
1535: switch (s.charAt(0)) {
1536: case 'b': // bidi-override
1537: cbidi = -cbidi; // For bidi-override we want a negative number.
1538: break;
1539: }
1540:
1541: result
1542: .put(TextAttribute.BIDI_EMBEDDING, new Integer(
1543: cbidi));
1544: }
1545:
1546: // Writing mode
1547:
1548: val = CSSUtilities.getComputedStyle(element,
1549: SVGCSSEngine.WRITING_MODE_INDEX);
1550: s = val.getStringValue();
1551: switch (s.charAt(0)) {
1552: case 'l':
1553: result
1554: .put(
1555: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE,
1556: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR);
1557: break;
1558: case 'r':
1559: result
1560: .put(
1561: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE,
1562: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL);
1563: break;
1564: case 't':
1565: result
1566: .put(
1567: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE,
1568: GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB);
1569: break;
1570: }
1571:
1572: // glyph-orientation-vertical
1573:
1574: val = CSSUtilities.getComputedStyle(element,
1575: SVGCSSEngine.GLYPH_ORIENTATION_VERTICAL_INDEX);
1576: int primitiveType = val.getPrimitiveType();
1577: switch (primitiveType) {
1578: case CSSPrimitiveValue.CSS_IDENT: // auto
1579: result
1580: .put(
1581: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION,
1582: GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_AUTO);
1583: break;
1584: case CSSPrimitiveValue.CSS_DEG:
1585: result
1586: .put(
1587: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION,
1588: GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE);
1589: result
1590: .put(
1591: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE,
1592: new Float(val.getFloatValue()));
1593: break;
1594: case CSSPrimitiveValue.CSS_RAD:
1595: result
1596: .put(
1597: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION,
1598: GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE);
1599: result
1600: .put(
1601: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE,
1602: new Float(Math.toDegrees(val
1603: .getFloatValue())));
1604: break;
1605: case CSSPrimitiveValue.CSS_GRAD:
1606: result
1607: .put(
1608: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION,
1609: GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE);
1610: result
1611: .put(
1612: GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE,
1613: new Float(val.getFloatValue() * 9 / 5));
1614: break;
1615: default:
1616: // Cannot happen
1617: throw new IllegalStateException(
1618: "unexpected primitiveType (V):" + primitiveType);
1619: }
1620:
1621: // glyph-orientation-horizontal
1622:
1623: val = CSSUtilities.getComputedStyle(element,
1624: SVGCSSEngine.GLYPH_ORIENTATION_HORIZONTAL_INDEX);
1625: primitiveType = val.getPrimitiveType();
1626: switch (primitiveType) {
1627: case CSSPrimitiveValue.CSS_DEG:
1628: result
1629: .put(
1630: GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE,
1631: new Float(val.getFloatValue()));
1632: break;
1633: case CSSPrimitiveValue.CSS_RAD:
1634: result
1635: .put(
1636: GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE,
1637: new Float(Math.toDegrees(val
1638: .getFloatValue())));
1639: break;
1640: case CSSPrimitiveValue.CSS_GRAD:
1641: result
1642: .put(
1643: GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE,
1644: new Float(val.getFloatValue() * 9 / 5));
1645: break;
1646: default:
1647: // Cannot happen
1648: throw new IllegalStateException(
1649: "unexpected primitiveType (H):" + primitiveType);
1650: }
1651:
1652: // text spacing properties...
1653:
1654: // Letter Spacing
1655: Float sp = TextUtilities.convertLetterSpacing(element);
1656: if (sp != null) {
1657: result
1658: .put(
1659: GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING,
1660: sp);
1661: result
1662: .put(
1663: GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING,
1664: Boolean.TRUE);
1665: }
1666:
1667: // Word spacing
1668: sp = TextUtilities.convertWordSpacing(element);
1669: if (sp != null) {
1670: result
1671: .put(
1672: GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING,
1673: sp);
1674: result
1675: .put(
1676: GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING,
1677: Boolean.TRUE);
1678: }
1679:
1680: // Kerning
1681: sp = TextUtilities.convertKerning(element);
1682: if (sp != null) {
1683: result
1684: .put(
1685: GVTAttributedCharacterIterator.TextAttribute.KERNING,
1686: sp);
1687: result
1688: .put(
1689: GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING,
1690: Boolean.TRUE);
1691: }
1692:
1693: if (tce == null) {
1694: return result;
1695: }
1696:
1697: try {
1698: // textLength
1699: AbstractSVGAnimatedLength textLength = (AbstractSVGAnimatedLength) tce
1700: .getTextLength();
1701: if (textLength.isSpecified()) {
1702: result
1703: .put(
1704: GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH,
1705: new Float(textLength.getAnimVal()
1706: .getValue()));
1707:
1708: // lengthAdjust
1709: if (tce.getLengthAdjust().getAnimVal() == SVGTextContentElement.LENGTHADJUST_SPACINGANDGLYPHS) {
1710: result
1711: .put(
1712: GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST,
1713: GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL);
1714: } else {
1715: result
1716: .put(
1717: GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST,
1718: GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING);
1719: result
1720: .put(
1721: GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING,
1722: Boolean.TRUE);
1723: }
1724: }
1725: } catch (LiveAttributeException ex) {
1726: throw new BridgeException(ctx, ex);
1727: }
1728:
1729: return result;
1730: }
1731:
1732: /**
1733: * Retrieve in the AttributeString the closest parent
1734: * of the node 'child' and extract the text decorations
1735: * of the parent.
1736: *
1737: * @param child an <code>Element</code> value
1738: * @return a <code>TextDecoration</code> value
1739: */
1740: protected TextPaintInfo getParentTextPaintInfo(Element child) {
1741: Node parent = getParentNode(child);
1742: while (parent != null) {
1743: TextPaintInfo tpi = (TextPaintInfo) elemTPI.get(parent);
1744: if (tpi != null)
1745: return tpi;
1746: parent = getParentNode(parent);
1747: }
1748: return null;
1749: }
1750:
1751: /**
1752: * Constructs a TextDecoration object for the specified element. This will
1753: * contain all of the decoration properties to be used when drawing the
1754: * text.
1755: */
1756: protected TextPaintInfo getTextPaintInfo(Element element,
1757: GraphicsNode node, TextPaintInfo parentTPI,
1758: BridgeContext ctx) {
1759: // Force the engine to update stuff..
1760: CSSUtilities.getComputedStyle(element,
1761: SVGCSSEngine.TEXT_DECORATION_INDEX);
1762:
1763: TextPaintInfo pi = new TextPaintInfo(parentTPI);
1764:
1765: // Was text-decoration explicity set on this element?
1766: StyleMap sm = ((CSSStylableElement) element)
1767: .getComputedStyleMap(null);
1768: if ((sm.isNullCascaded(SVGCSSEngine.TEXT_DECORATION_INDEX))
1769: && (sm.isNullCascaded(SVGCSSEngine.FILL_INDEX))
1770: && (sm.isNullCascaded(SVGCSSEngine.STROKE_INDEX))
1771: && (sm.isNullCascaded(SVGCSSEngine.STROKE_WIDTH_INDEX))
1772: && (sm.isNullCascaded(SVGCSSEngine.OPACITY_INDEX))) {
1773: // If not, keep the same decorations.
1774: return pi;
1775: }
1776:
1777: setBaseTextPaintInfo(pi, element, node, ctx);
1778:
1779: if (!sm.isNullCascaded(SVGCSSEngine.TEXT_DECORATION_INDEX))
1780: setDecorationTextPaintInfo(pi, element);
1781:
1782: return pi;
1783: }
1784:
1785: public void setBaseTextPaintInfo(TextPaintInfo pi, Element element,
1786: GraphicsNode node, BridgeContext ctx) {
1787: if (!element.getLocalName().equals(SVG_TEXT_TAG))
1788: pi.composite = CSSUtilities.convertOpacity(element);
1789: else
1790: pi.composite = AlphaComposite.SrcOver;
1791:
1792: pi.visible = CSSUtilities.convertVisibility(element);
1793: pi.fillPaint = PaintServer.convertFillPaint(element, node, ctx);
1794: pi.strokePaint = PaintServer.convertStrokePaint(element, node,
1795: ctx);
1796: pi.strokeStroke = PaintServer.convertStroke(element);
1797: }
1798:
1799: public void setDecorationTextPaintInfo(TextPaintInfo pi,
1800: Element element) {
1801: Value val = CSSUtilities.getComputedStyle(element,
1802: SVGCSSEngine.TEXT_DECORATION_INDEX);
1803:
1804: switch (val.getCssValueType()) {
1805: case CSSValue.CSS_VALUE_LIST:
1806: ListValue lst = (ListValue) val;
1807:
1808: int len = lst.getLength();
1809: for (int i = 0; i < len; i++) {
1810: Value v = lst.item(i);
1811: String s = v.getStringValue();
1812: switch (s.charAt(0)) {
1813: case 'u':
1814: if (pi.fillPaint != null) {
1815: pi.underlinePaint = pi.fillPaint;
1816: }
1817: if (pi.strokePaint != null) {
1818: pi.underlineStrokePaint = pi.strokePaint;
1819: }
1820: if (pi.strokeStroke != null) {
1821: pi.underlineStroke = pi.strokeStroke;
1822: }
1823: break;
1824: case 'o':
1825: if (pi.fillPaint != null) {
1826: pi.overlinePaint = pi.fillPaint;
1827: }
1828: if (pi.strokePaint != null) {
1829: pi.overlineStrokePaint = pi.strokePaint;
1830: }
1831: if (pi.strokeStroke != null) {
1832: pi.overlineStroke = pi.strokeStroke;
1833: }
1834: break;
1835: case 'l':
1836: if (pi.fillPaint != null) {
1837: pi.strikethroughPaint = pi.fillPaint;
1838: }
1839: if (pi.strokePaint != null) {
1840: pi.strikethroughStrokePaint = pi.strokePaint;
1841: }
1842: if (pi.strokeStroke != null) {
1843: pi.strikethroughStroke = pi.strokeStroke;
1844: }
1845: break;
1846: }
1847: }
1848: break;
1849:
1850: default: // None
1851: pi.underlinePaint = null;
1852: pi.underlineStrokePaint = null;
1853: pi.underlineStroke = null;
1854:
1855: pi.overlinePaint = null;
1856: pi.overlineStrokePaint = null;
1857: pi.overlineStroke = null;
1858:
1859: pi.strikethroughPaint = null;
1860: pi.strikethroughStrokePaint = null;
1861: pi.strikethroughStroke = null;
1862: break;
1863: }
1864: }
1865:
1866: /**
1867: * Implementation of <code>SVGContext</code> for
1868: * the children of <text>
1869: */
1870: public abstract class AbstractTextChildSVGContext extends
1871: AnimatableSVGBridge {
1872:
1873: /** Text bridge parent */
1874: protected SVGTextElementBridge textBridge;
1875:
1876: /**
1877: * Initialize the <code>SVGContext</code> implementation
1878: * with the bridgeContext, the parent bridge, and the
1879: * element supervised by this context
1880: */
1881: public AbstractTextChildSVGContext(BridgeContext ctx,
1882: SVGTextElementBridge parent, Element e) {
1883: this .ctx = ctx;
1884: this .textBridge = parent;
1885: this .e = e;
1886: }
1887:
1888: /**
1889: * Returns the namespace URI of the element this <tt>Bridge</tt> is
1890: * dedicated to.
1891: */
1892: public String getNamespaceURI() {
1893: return null;
1894: }
1895:
1896: /**
1897: * Returns the local name of the element this <tt>Bridge</tt> is dedicated
1898: * to.
1899: */
1900: public String getLocalName() {
1901: return null;
1902: }
1903:
1904: /**
1905: * Returns a new instance of this bridge.
1906: */
1907: public Bridge getInstance() {
1908: return null;
1909: }
1910:
1911: public SVGTextElementBridge getTextBridge() {
1912: return textBridge;
1913: }
1914:
1915: /**
1916: * Returns the size of a px CSS unit in millimeters.
1917: */
1918: public float getPixelUnitToMillimeter() {
1919: return ctx.getUserAgent().getPixelUnitToMillimeter();
1920: }
1921:
1922: /**
1923: * Returns the size of a px CSS unit in millimeters.
1924: * This will be removed after next release.
1925: * @see #getPixelUnitToMillimeter()
1926: */
1927: public float getPixelToMM() {
1928: return getPixelUnitToMillimeter();
1929:
1930: }
1931:
1932: /**
1933: * Returns the tight bounding box in current user space (i.e.,
1934: * after application of the transform attribute, if any) on the
1935: * geometry of all contained graphics elements, exclusive of
1936: * stroke-width and filter effects).
1937: */
1938: public Rectangle2D getBBox() {
1939: //text children does not support getBBox
1940: //return textBridge.getBBox();
1941: return null;
1942: }
1943:
1944: /**
1945: * Returns the transformation matrix from current user units
1946: * (i.e., after application of the transform attribute, if any) to
1947: * the viewport coordinate system for the nearestViewportElement.
1948: */
1949: public AffineTransform getCTM() {
1950: // text children does not support transform attribute
1951: //return textBridge.getCTM();
1952: return null;
1953: }
1954:
1955: /**
1956: * Returns the global transformation matrix from the current
1957: * element to the root.
1958: */
1959: public AffineTransform getGlobalTransform() {
1960: //return node.getGlobalTransform();
1961: return null;
1962: }
1963:
1964: /**
1965: * Returns the transformation matrix from the userspace of
1966: * the root element to the screen.
1967: */
1968: public AffineTransform getScreenTransform() {
1969: //return node.getScreenTransform();
1970: return null;
1971: }
1972:
1973: /**
1974: * Sets the transformation matrix to be used from the
1975: * userspace of the root element to the screen.
1976: */
1977: public void setScreenTransform(AffineTransform at) {
1978: //return node.setScreenTransform(at);
1979: return;
1980: }
1981:
1982: /**
1983: * Returns the width of the viewport which directly contains the
1984: * given element.
1985: */
1986: public float getViewportWidth() {
1987: return ctx.getBlockWidth(e);
1988: }
1989:
1990: /**
1991: * Returns the height of the viewport which directly contains the
1992: * given element.
1993: */
1994: public float getViewportHeight() {
1995: return ctx.getBlockHeight(e);
1996: }
1997:
1998: /**
1999: * Returns the font-size on the associated element.
2000: */
2001: public float getFontSize() {
2002: return CSSUtilities.getComputedStyle(e,
2003: SVGCSSEngine.FONT_SIZE_INDEX).getFloatValue();
2004: }
2005: }
2006:
2007: /**
2008: * Implementation for the <code>BridgeUpdateHandler</code>
2009: * for the child elements of <text>.
2010: * This implementation relies on the parent bridge
2011: * which contains the <code>TextNode</code>
2012: * representing the node this context supervised.
2013: * All operations are done by the parent bridge
2014: * <code>SVGTextElementBridge</code> which can determine
2015: * the impact of a change of one of its children for the others.
2016: */
2017: protected abstract class AbstractTextChildBridgeUpdateHandler
2018: extends AbstractTextChildSVGContext implements
2019: BridgeUpdateHandler {
2020:
2021: /**
2022: * Initialize the BridgeUpdateHandler implementation.
2023: */
2024: protected AbstractTextChildBridgeUpdateHandler(
2025: BridgeContext ctx, SVGTextElementBridge parent,
2026: Element e) {
2027:
2028: super (ctx, parent, e);
2029: }
2030:
2031: /**
2032: * Invoked when an MutationEvent of type 'DOMAttrModified' is fired.
2033: */
2034: public void handleDOMAttrModifiedEvent(MutationEvent evt) {
2035: //nothing to do
2036: }
2037:
2038: /**
2039: * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
2040: */
2041: public void handleDOMNodeInsertedEvent(MutationEvent evt) {
2042: textBridge.handleDOMNodeInsertedEvent(evt);
2043: }
2044:
2045: /**
2046: * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
2047: */
2048: public void handleDOMNodeRemovedEvent(MutationEvent evt) {
2049: //nothing to do
2050: dispose();
2051: }
2052:
2053: /**
2054: * Invoked when an MutationEvent of type 'DOMCharacterDataModified'
2055: * is fired.
2056: */
2057: public void handleDOMCharacterDataModified(MutationEvent evt) {
2058: textBridge.handleDOMCharacterDataModified(evt);
2059: }
2060:
2061: /**
2062: * Invoked when an CSSEngineEvent is fired.
2063: */
2064: public void handleCSSEngineEvent(CSSEngineEvent evt) {
2065: textBridge.handleCSSEngineEvent(evt);
2066: }
2067:
2068: /**
2069: * Invoked when the animated value of an animatable attribute has
2070: * changed.
2071: */
2072: public void handleAnimatedAttributeChanged(
2073: AnimatedLiveAttributeValue alav) {
2074: }
2075:
2076: /**
2077: * Invoked when an 'other' animation value has changed.
2078: */
2079: public void handleOtherAnimationChanged(String type) {
2080: }
2081:
2082: /**
2083: * Disposes this BridgeUpdateHandler and releases all resources.
2084: */
2085: public void dispose() {
2086: ((SVGOMElement) e).setSVGContext(null);
2087: elemTPI.remove(e);
2088: }
2089: }
2090:
2091: protected class AbstractTextChildTextContent extends
2092: AbstractTextChildBridgeUpdateHandler implements
2093: SVGTextContent {
2094:
2095: /**
2096: * Initialize the AbstractTextChildBridgeUpdateHandler implementation.
2097: */
2098: protected AbstractTextChildTextContent(BridgeContext ctx,
2099: SVGTextElementBridge parent, Element e) {
2100:
2101: super (ctx, parent, e);
2102: }
2103:
2104: //Implementation of TextContent
2105:
2106: public int getNumberOfChars() {
2107: return textBridge.getNumberOfChars(e);
2108: }
2109:
2110: public Rectangle2D getExtentOfChar(int charnum) {
2111: return textBridge.getExtentOfChar(e, charnum);
2112: }
2113:
2114: public Point2D getStartPositionOfChar(int charnum) {
2115: return textBridge.getStartPositionOfChar(e, charnum);
2116: }
2117:
2118: public Point2D getEndPositionOfChar(int charnum) {
2119: return textBridge.getEndPositionOfChar(e, charnum);
2120: }
2121:
2122: public void selectSubString(int charnum, int nchars) {
2123: textBridge.selectSubString(e, charnum, nchars);
2124: }
2125:
2126: public float getRotationOfChar(int charnum) {
2127: return textBridge.getRotationOfChar(e, charnum);
2128: }
2129:
2130: public float getComputedTextLength() {
2131: return textBridge.getComputedTextLength(e);
2132: }
2133:
2134: public float getSubStringLength(int charnum, int nchars) {
2135: return textBridge.getSubStringLength(e, charnum, nchars);
2136: }
2137:
2138: public int getCharNumAtPosition(float x, float y) {
2139: return textBridge.getCharNumAtPosition(e, x, y);
2140: }
2141: }
2142:
2143: /**
2144: * BridgeUpdateHandle for <tref> element.
2145: */
2146: protected class TRefBridge extends AbstractTextChildTextContent {
2147:
2148: protected TRefBridge(BridgeContext ctx,
2149: SVGTextElementBridge parent, Element e) {
2150: super (ctx, parent, e);
2151: }
2152:
2153: /**
2154: * Invoked when the animated value of an animatable attribute has
2155: * changed on a 'tref' element.
2156: */
2157: public void handleAnimatedAttributeChanged(
2158: AnimatedLiveAttributeValue alav) {
2159: if (alav.getNamespaceURI() == null) {
2160: String ln = alav.getLocalName();
2161: if (ln.equals(SVG_X_ATTRIBUTE)
2162: || ln.equals(SVG_Y_ATTRIBUTE)
2163: || ln.equals(SVG_DX_ATTRIBUTE)
2164: || ln.equals(SVG_DY_ATTRIBUTE)
2165: || ln.equals(SVG_ROTATE_ATTRIBUTE)
2166: || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE)
2167: || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) {
2168: // Recompute the layout of the text node.
2169: textBridge.computeLaidoutText(ctx, textBridge.e,
2170: textBridge.getTextNode());
2171: return;
2172: }
2173: }
2174: super .handleAnimatedAttributeChanged(alav);
2175: }
2176: }
2177:
2178: /**
2179: * BridgeUpdateHandle for <textPath> element.
2180: */
2181: protected class TextPathBridge extends AbstractTextChildTextContent {
2182:
2183: protected TextPathBridge(BridgeContext ctx,
2184: SVGTextElementBridge parent, Element e) {
2185: super (ctx, parent, e);
2186: }
2187: }
2188:
2189: /**
2190: * BridgeUpdateHandle for <tspan> element.
2191: */
2192: protected class TspanBridge extends AbstractTextChildTextContent {
2193:
2194: protected TspanBridge(BridgeContext ctx,
2195: SVGTextElementBridge parent, Element e) {
2196: super (ctx, parent, e);
2197: }
2198:
2199: /**
2200: * Invoked when the animated value of an animatable attribute has
2201: * changed on a 'tspan' element.
2202: */
2203: public void handleAnimatedAttributeChanged(
2204: AnimatedLiveAttributeValue alav) {
2205: if (alav.getNamespaceURI() == null) {
2206: String ln = alav.getLocalName();
2207: if (ln.equals(SVG_X_ATTRIBUTE)
2208: || ln.equals(SVG_Y_ATTRIBUTE)
2209: || ln.equals(SVG_DX_ATTRIBUTE)
2210: || ln.equals(SVG_DY_ATTRIBUTE)
2211: || ln.equals(SVG_ROTATE_ATTRIBUTE)
2212: || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE)
2213: || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) {
2214: // Recompute the layout of the text node.
2215: textBridge.computeLaidoutText(ctx, textBridge.e,
2216: textBridge.getTextNode());
2217: return;
2218: }
2219: }
2220: super .handleAnimatedAttributeChanged(alav);
2221: }
2222: }
2223:
2224: //Implementation of TextContent
2225: public int getNumberOfChars() {
2226: return getNumberOfChars(e);
2227: }
2228:
2229: public Rectangle2D getExtentOfChar(int charnum) {
2230: return getExtentOfChar(e, charnum);
2231: }
2232:
2233: public Point2D getStartPositionOfChar(int charnum) {
2234: return getStartPositionOfChar(e, charnum);
2235: }
2236:
2237: public Point2D getEndPositionOfChar(int charnum) {
2238: return getEndPositionOfChar(e, charnum);
2239: }
2240:
2241: public void selectSubString(int charnum, int nchars) {
2242: selectSubString(e, charnum, nchars);
2243: }
2244:
2245: public float getRotationOfChar(int charnum) {
2246: return getRotationOfChar(e, charnum);
2247: }
2248:
2249: public float getComputedTextLength() {
2250: return getComputedTextLength(e);
2251: }
2252:
2253: public float getSubStringLength(int charnum, int nchars) {
2254: return getSubStringLength(e, charnum, nchars);
2255: }
2256:
2257: public int getCharNumAtPosition(float x, float y) {
2258: return getCharNumAtPosition(e, x, y);
2259: }
2260:
2261: /**
2262: * Implementation of {@link
2263: * org.w3c.dom.svg.SVGTextContentElement#getNumberOfChars()}.
2264: */
2265: protected int getNumberOfChars(Element element) {
2266:
2267: AttributedCharacterIterator aci;
2268: aci = getTextNode().getAttributedCharacterIterator();
2269: if (aci == null)
2270: return 0;
2271:
2272: //get the index in the aci for the first character
2273: //of the element
2274: int firstChar = getElementStartIndex(element);
2275:
2276: if (firstChar == -1)
2277: return 0; // Element not part of aci (no chars in elem usually)
2278:
2279: int lastChar = getElementEndIndex(element);
2280:
2281: return (lastChar - firstChar + 1);
2282: }
2283:
2284: /**
2285: * Implementation of {@link
2286: * org.w3c.dom.svg.SVGTextContentElement#getExtentOfChar(int charnum)}.
2287: */
2288: protected Rectangle2D getExtentOfChar(Element element, int charnum) {
2289: TextNode textNode = getTextNode();
2290:
2291: AttributedCharacterIterator aci;
2292: aci = textNode.getAttributedCharacterIterator();
2293: if (aci == null)
2294: return null;
2295:
2296: int firstChar = getElementStartIndex(element);
2297:
2298: if (firstChar == -1)
2299: return null;
2300:
2301: //retrieve the text run for the text node
2302: List list = getTextRuns(textNode);
2303:
2304: //find the character 'charnum' in the text run
2305: CharacterInformation info;
2306: info = getCharacterInformation(list, firstChar, charnum, aci);
2307:
2308: if (info == null)
2309: return null;
2310:
2311: //retrieve the glyphvector containing the glyph
2312: //for 'charnum'
2313: GVTGlyphVector it = info.layout.getGlyphVector();
2314:
2315: Shape b = null;
2316:
2317: if (info.glyphIndexStart == info.glyphIndexEnd) {
2318: if (it.isGlyphVisible(info.glyphIndexStart)) {
2319: b = it.getGlyphCellBounds(info.glyphIndexStart);
2320: }
2321: } else {
2322: GeneralPath path = null;
2323: for (int k = info.glyphIndexStart; k <= info.glyphIndexEnd; k++) {
2324: if (it.isGlyphVisible(k)) {
2325: Rectangle2D gb = it.getGlyphCellBounds(k);
2326: if (path == null) {
2327: path = new GeneralPath(gb);
2328: } else {
2329: path.append(gb, false);
2330: }
2331: }
2332: }
2333: b = path;
2334: }
2335:
2336: if (b == null) {
2337: return null;
2338: }
2339:
2340: //return the bounding box of the outline
2341: return b.getBounds2D();
2342: }
2343:
2344: /**
2345: * Implementation of {@link
2346: * org.w3c.dom.svg.SVGTextContentElement#getStartPositionOfChar(int charnum)}.
2347: */
2348: protected Point2D getStartPositionOfChar(Element element,
2349: int charnum) {
2350: TextNode textNode = getTextNode();
2351:
2352: AttributedCharacterIterator aci;
2353: aci = textNode.getAttributedCharacterIterator();
2354: if (aci == null)
2355: return null;
2356:
2357: int firstChar = getElementStartIndex(element);
2358: if (firstChar == -1)
2359: return null;
2360:
2361: //retrieve the text run for the text node
2362: List list = getTextRuns(textNode);
2363:
2364: //find the character 'charnum' in the text run
2365: CharacterInformation info;
2366: info = getCharacterInformation(list, firstChar, charnum, aci);
2367:
2368: if (info == null)
2369: return null;
2370:
2371: return getStartPoint(info);
2372: }
2373:
2374: protected Point2D getStartPoint(CharacterInformation info) {
2375:
2376: GVTGlyphVector it = info.layout.getGlyphVector();
2377: if (!it.isGlyphVisible(info.glyphIndexStart))
2378: return null;
2379:
2380: Point2D b = it.getGlyphPosition(info.glyphIndexStart);
2381:
2382: AffineTransform glyphTransform;
2383: glyphTransform = it.getGlyphTransform(info.glyphIndexStart);
2384:
2385: //glyph are defined starting at position (0,0)
2386: Point2D.Float result = new Point2D.Float(0, 0);
2387: if (glyphTransform != null)
2388: //apply the glyph transformation to the start point
2389: glyphTransform.transform(result, result);
2390:
2391: result.x += b.getX();
2392: result.y += b.getY();
2393: return result;
2394: }
2395:
2396: /**
2397: * Implementation of {@link
2398: * org.w3c.dom.svg.SVGTextContentElement#getEndPositionOfChar(int charnum)}.
2399: */
2400: protected Point2D getEndPositionOfChar(Element element, int charnum) {
2401: TextNode textNode = getTextNode();
2402:
2403: AttributedCharacterIterator aci;
2404: aci = textNode.getAttributedCharacterIterator();
2405: if (aci == null)
2406: return null;
2407:
2408: int firstChar = getElementStartIndex(element);
2409: if (firstChar == -1)
2410: return null;
2411:
2412: //retrieve the text run for the text node
2413: List list = getTextRuns(textNode);
2414:
2415: //find the glyph information for the character 'charnum'
2416: CharacterInformation info;
2417: info = getCharacterInformation(list, firstChar, charnum, aci);
2418:
2419: if (info == null)
2420: return null;
2421: return getEndPoint(info);
2422: }
2423:
2424: protected Point2D getEndPoint(CharacterInformation info) {
2425:
2426: GVTGlyphVector it = info.layout.getGlyphVector();
2427: if (!it.isGlyphVisible(info.glyphIndexEnd))
2428: return null;
2429:
2430: Point2D b = it.getGlyphPosition(info.glyphIndexEnd);
2431:
2432: AffineTransform glyphTransform;
2433: glyphTransform = it.getGlyphTransform(info.glyphIndexEnd);
2434:
2435: GVTGlyphMetrics metrics = it
2436: .getGlyphMetrics(info.glyphIndexEnd);
2437:
2438: Point2D.Float result = new Point2D.Float(metrics
2439: .getHorizontalAdvance(), 0);
2440:
2441: if (glyphTransform != null)
2442: glyphTransform.transform(result, result);
2443:
2444: result.x += b.getX();
2445: result.y += b.getY();
2446: return result;
2447: }
2448:
2449: /**
2450: * Implementation of {@link
2451: * org.w3c.dom.svg.SVGTextContentElement#getRotationOfChar(int charnum)}.
2452: */
2453: protected float getRotationOfChar(Element element, int charnum) {
2454: TextNode textNode = getTextNode();
2455:
2456: AttributedCharacterIterator aci;
2457: aci = textNode.getAttributedCharacterIterator();
2458: if (aci == null)
2459: return 0;
2460:
2461: //first the first character for the element
2462: int firstChar = getElementStartIndex(element);
2463: if (firstChar == -1)
2464: return 0;
2465:
2466: //retrieve the text run for the text node
2467: List list = getTextRuns(textNode);
2468:
2469: //find the glyph information for the character 'charnum'
2470: CharacterInformation info;
2471: info = getCharacterInformation(list, firstChar, charnum, aci);
2472:
2473: double angle = 0.0;
2474: int nbGlyphs = 0;
2475:
2476: if (info != null) {
2477: GVTGlyphVector it = info.layout.getGlyphVector();
2478:
2479: for (int k = info.glyphIndexStart; k <= info.glyphIndexEnd; k++) {
2480: if (!it.isGlyphVisible(k))
2481: continue;
2482:
2483: nbGlyphs++;
2484:
2485: //the glyph transform contains only a scale and a rotate.
2486: AffineTransform glyphTransform = it
2487: .getGlyphTransform(k);
2488: if (glyphTransform == null)
2489: continue;
2490:
2491: double glyphAngle = 0.0;
2492: double cosTheta = glyphTransform.getScaleX();
2493: double sinTheta = glyphTransform.getShearX();
2494:
2495: //extract the angle
2496: if (cosTheta == 0.0) {
2497: if (sinTheta > 0)
2498: glyphAngle = Math.PI;
2499: else
2500: glyphAngle = -Math.PI;
2501: } else {
2502: glyphAngle = Math.atan(sinTheta / cosTheta); // todo is this safe??
2503: if (cosTheta < 0)
2504: glyphAngle += Math.PI;
2505: }
2506: //get a degrees value for the angle
2507: //SVG angle are clock wise java anticlockwise
2508:
2509: glyphAngle = (Math.toDegrees(-glyphAngle)) % 360.0;
2510:
2511: //remove the orientation from the value
2512: angle += glyphAngle
2513: - info.getComputedOrientationAngle();
2514: }
2515: }
2516: if (nbGlyphs == 0)
2517: return 0;
2518: return (float) (angle / nbGlyphs);
2519: }
2520:
2521: /**
2522: * Implementation of {@link
2523: * org.w3c.dom.svg.SVGTextContentElement#getComputedTextLength()}.
2524: */
2525: protected float getComputedTextLength(Element e) {
2526: return getSubStringLength(e, 0, getNumberOfChars(e));
2527: }
2528:
2529: /**
2530: * Implementation of {@link
2531: * org.w3c.dom.svg.SVGTextContentElement#getSubStringLength(int charnum,int nchars)}.
2532: */
2533: protected float getSubStringLength(Element element, int charnum,
2534: int nchars) {
2535: if (nchars == 0) {
2536: return 0;
2537: }
2538:
2539: float length = 0;
2540:
2541: TextNode textNode = getTextNode();
2542:
2543: AttributedCharacterIterator aci;
2544: aci = textNode.getAttributedCharacterIterator();
2545: if (aci == null)
2546: return -1;
2547:
2548: int firstChar = getElementStartIndex(element);
2549:
2550: if (firstChar == -1)
2551: return -1;
2552:
2553: List list = getTextRuns(textNode);
2554:
2555: CharacterInformation currentInfo;
2556: currentInfo = getCharacterInformation(list, firstChar, charnum,
2557: aci);
2558: CharacterInformation lastCharacterInRunInfo = null;
2559: int chIndex = currentInfo.characterIndex + 1;
2560: GVTGlyphVector vector = currentInfo.layout.getGlyphVector();
2561: float[] advs = currentInfo.layout.getGlyphAdvances();
2562: boolean[] glyphTrack = new boolean[advs.length];
2563: for (int k = charnum + 1; k < charnum + nchars; k++) {
2564: if (currentInfo.layout.isOnATextPath()) {
2565: for (int gi = currentInfo.glyphIndexStart; gi <= currentInfo.glyphIndexEnd; gi++) {
2566: if ((vector.isGlyphVisible(gi)) && !glyphTrack[gi])
2567: length += advs[gi + 1] - advs[gi];
2568: glyphTrack[gi] = true;
2569: }
2570: CharacterInformation newInfo;
2571: newInfo = getCharacterInformation(list, firstChar, k,
2572: aci);
2573: if (newInfo.layout != currentInfo.layout) {
2574: vector = newInfo.layout.getGlyphVector();
2575: advs = newInfo.layout.getGlyphAdvances();
2576: glyphTrack = new boolean[advs.length];
2577: chIndex = currentInfo.characterIndex + 1;
2578: }
2579: currentInfo = newInfo;
2580: } else {
2581: //reach the next run
2582: if (currentInfo.layout.hasCharacterIndex(chIndex)) {
2583: chIndex++;
2584: continue;
2585: }
2586:
2587: lastCharacterInRunInfo = getCharacterInformation(list,
2588: firstChar, k - 1, aci);
2589:
2590: //if the text run change compute the distance between the
2591: //first character of the run and the last
2592: length += distanceFirstLastCharacterInRun(currentInfo,
2593: lastCharacterInRunInfo);
2594:
2595: currentInfo = getCharacterInformation(list, firstChar,
2596: k, aci);
2597: chIndex = currentInfo.characterIndex + 1;
2598: vector = currentInfo.layout.getGlyphVector();
2599: advs = currentInfo.layout.getGlyphAdvances();
2600: glyphTrack = new boolean[advs.length];
2601: lastCharacterInRunInfo = null;
2602: }
2603: }
2604:
2605: if (currentInfo.layout.isOnATextPath()) {
2606: for (int gi = currentInfo.glyphIndexStart; gi <= currentInfo.glyphIndexEnd; gi++) {
2607: if ((vector.isGlyphVisible(gi)) && !glyphTrack[gi])
2608: length += advs[gi + 1] - advs[gi];
2609: glyphTrack[gi] = true;
2610: }
2611: } else {
2612: if (lastCharacterInRunInfo == null) {
2613: lastCharacterInRunInfo = getCharacterInformation(list,
2614: firstChar, charnum + nchars - 1, aci);
2615: }
2616: //add the length between the end position of the last character
2617: //and the first character in the run
2618: length += distanceFirstLastCharacterInRun(currentInfo,
2619: lastCharacterInRunInfo);
2620: }
2621:
2622: return length;
2623: }
2624:
2625: protected float distanceFirstLastCharacterInRun(
2626: CharacterInformation first, CharacterInformation last) {
2627:
2628: float[] advs = first.layout.getGlyphAdvances();
2629:
2630: int firstStart = first.glyphIndexStart;
2631: int firstEnd = first.glyphIndexEnd;
2632: int lastStart = last.glyphIndexStart;
2633: int lastEnd = last.glyphIndexEnd;
2634:
2635: int start = (firstStart < lastStart) ? firstStart : lastStart;
2636: int end = (firstEnd < lastEnd) ? lastEnd : firstEnd;
2637: return advs[end + 1] - advs[start];
2638: }
2639:
2640: protected float distanceBetweenRun(CharacterInformation last,
2641: CharacterInformation first) {
2642:
2643: float distance;
2644: Point2D startPoint;
2645: Point2D endPoint;
2646: CharacterInformation info = new CharacterInformation();
2647:
2648: //determine where the last run stops
2649:
2650: info.layout = last.layout;
2651: info.glyphIndexEnd = last.layout.getGlyphCount() - 1;
2652:
2653: startPoint = getEndPoint(info);
2654:
2655: //determine where the next run starts
2656: info.layout = first.layout;
2657: info.glyphIndexStart = 0;
2658:
2659: endPoint = getStartPoint(info);
2660:
2661: if (first.isVertical()) {
2662: distance = (float) (endPoint.getY() - startPoint.getY());
2663: } else {
2664: distance = (float) (endPoint.getX() - startPoint.getX());
2665: }
2666:
2667: return distance;
2668: }
2669:
2670: /**
2671: * Select an ensemble of characters for that element.
2672: *
2673: * TODO : report the selection to the selection
2674: * manager in JSVGComponent.
2675: */
2676: protected void selectSubString(Element element, int charnum,
2677: int nchars) {
2678: TextNode textNode = getTextNode();
2679:
2680: AttributedCharacterIterator aci;
2681: aci = textNode.getAttributedCharacterIterator();
2682: if (aci == null)
2683: return;
2684:
2685: int firstChar = getElementStartIndex(element);
2686:
2687: if (firstChar == -1)
2688: return;
2689:
2690: List list = getTextRuns(textNode);
2691:
2692: int lastChar = getElementEndIndex(element);
2693:
2694: CharacterInformation firstInfo, lastInfo;
2695: firstInfo = getCharacterInformation(list, firstChar, charnum,
2696: aci);
2697: lastInfo = getCharacterInformation(list, firstChar, charnum
2698: + nchars - 1, aci);
2699:
2700: Mark firstMark, lastMark;
2701: firstMark = textNode.getMarkerForChar(firstInfo.characterIndex,
2702: true);
2703:
2704: if (lastInfo != null && lastInfo.characterIndex <= lastChar) {
2705: lastMark = textNode.getMarkerForChar(
2706: lastInfo.characterIndex, false);
2707: } else {
2708: lastMark = textNode.getMarkerForChar(lastChar, false);
2709: }
2710:
2711: ctx.getUserAgent().setTextSelection(firstMark, lastMark);
2712: }
2713:
2714: protected int getCharNumAtPosition(Element e, float x, float y) {
2715: TextNode textNode = getTextNode();
2716:
2717: AttributedCharacterIterator aci;
2718: aci = textNode.getAttributedCharacterIterator();
2719: if (aci == null)
2720: return -1;
2721:
2722: //check if there is an hit
2723: List list = getTextRuns(textNode);
2724:
2725: //going backward in the list to catch the last character
2726: // displayed at that position
2727: TextHit hit = null;
2728:
2729: for (int i = list.size() - 1; i >= 0 && hit == null; i--) {
2730: StrokingTextPainter.TextRun textRun;
2731: textRun = (StrokingTextPainter.TextRun) list.get(i);
2732: hit = textRun.getLayout().hitTestChar(x, y);
2733: }
2734:
2735: if (hit == null)
2736: return -1;
2737:
2738: //found an hit, check if it belong to the element
2739: int first = getElementStartIndex(e);
2740: int last = getElementEndIndex(e);
2741:
2742: int hitIndex = hit.getCharIndex();
2743:
2744: if (hitIndex >= first && hitIndex <= last)
2745: return hitIndex - first;
2746:
2747: return -1;
2748: }
2749:
2750: /**
2751: * Retrieve the list of layout for the
2752: * text node.
2753: */
2754: protected List getTextRuns(TextNode node) {
2755: //System.out.println(node.getTextRuns());
2756: if (node.getTextRuns() == null) {
2757: //TODO : need to work out a solution
2758: //to compute the text runs
2759: node.getPrimitiveBounds();
2760: }
2761: //System.out.println(node.getTextRuns());
2762: return node.getTextRuns();
2763: }
2764:
2765: /**
2766: * Retrieve the information about a character
2767: * of en element. The element first character in
2768: * the ACI is 'firstChar' and the character
2769: * look for is the charnum th character in the
2770: * element
2771: *
2772: * @param list list of the layouts
2773: * @param startIndex index in the ACI of the first
2774: * character for the element
2775: * @param charnum index of the character (among the
2776: * characters of the element) looked for.
2777: *
2778: * @return information about the glyph representing the
2779: * character
2780: */
2781: protected CharacterInformation getCharacterInformation(List list,
2782: int startIndex, int charnum, AttributedCharacterIterator aci) {
2783: CharacterInformation info = new CharacterInformation();
2784: info.characterIndex = startIndex + charnum;
2785:
2786: for (int i = 0; i < list.size(); i++) {
2787: StrokingTextPainter.TextRun run;
2788: run = (StrokingTextPainter.TextRun) list.get(i);
2789:
2790: if (!run.getLayout().hasCharacterIndex(info.characterIndex))
2791: continue;
2792:
2793: info.layout = run.getLayout();
2794:
2795: aci.setIndex(info.characterIndex);
2796:
2797: //check is it is a altGlyph
2798: if (aci.getAttribute(ALT_GLYPH_HANDLER) != null) {
2799: info.glyphIndexStart = 0;
2800: info.glyphIndexEnd = info.layout.getGlyphCount() - 1;
2801: } else {
2802: info.glyphIndexStart = info.layout
2803: .getGlyphIndex(info.characterIndex);
2804:
2805: //special case when the glyph does not have a unicode
2806: //associated to it, it will return -1
2807: if (info.glyphIndexStart == -1) {
2808: info.glyphIndexStart = 0;
2809: info.glyphIndexEnd = info.layout.getGlyphCount() - 1;
2810: } else {
2811: info.glyphIndexEnd = info.glyphIndexStart;
2812: }
2813: }
2814: return info;
2815: }
2816: return null;
2817: }
2818:
2819: /**
2820: * Helper class to collect information about one Glyph
2821: * in the GlyphVector
2822: */
2823: protected static class CharacterInformation {
2824: ///layout associated to the Glyph
2825: TextSpanLayout layout;
2826: ///GlyphIndex in the vector
2827: int glyphIndexStart;
2828:
2829: int glyphIndexEnd;
2830:
2831: ///Character index in the ACI.
2832: int characterIndex;
2833:
2834: /// Indicates is the glyph is vertical
2835: public boolean isVertical() {
2836: return layout.isVertical();
2837: }
2838:
2839: /// Retrieve the orientation angle for the Glyph
2840: public double getComputedOrientationAngle() {
2841: return layout.getComputedOrientationAngle(characterIndex);
2842: }
2843: }
2844:
2845: public Set getTextIntersectionSet(AffineTransform at,
2846: Rectangle2D rect) {
2847: Set elems = new HashSet();
2848:
2849: TextNode tn = getTextNode();
2850:
2851: List list = tn.getTextRuns();
2852: if (list == null)
2853: return elems;
2854:
2855: for (int i = 0; i < list.size(); i++) {
2856: StrokingTextPainter.TextRun run;
2857: run = (StrokingTextPainter.TextRun) list.get(i);
2858: TextSpanLayout layout = run.getLayout();
2859: AttributedCharacterIterator aci = run.getACI();
2860: aci.first();
2861: SoftReference sr;
2862: sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID);
2863: Element elem = (Element) sr.get();
2864:
2865: if (elem == null)
2866: continue;
2867: if (elems.contains(elem))
2868: continue;
2869: if (!isTextSensitive(elem))
2870: continue;
2871:
2872: Rectangle2D glBounds = layout.getBounds2D();
2873: if (glBounds != null)
2874: glBounds = at.createTransformedShape(glBounds)
2875: .getBounds2D();
2876:
2877: if (!rect.intersects(glBounds))
2878: continue;
2879:
2880: GVTGlyphVector gv = layout.getGlyphVector();
2881: for (int g = 0; g < gv.getNumGlyphs(); g++) {
2882: Shape gBounds = gv.getGlyphLogicalBounds(g);
2883: if (gBounds != null)
2884: gBounds = at.createTransformedShape(gBounds)
2885: .getBounds2D();
2886:
2887: if (gBounds.intersects(rect)) {
2888: elems.add(elem);
2889: break;
2890: }
2891: }
2892: }
2893: return elems;
2894: }
2895:
2896: public Set getTextEnclosureSet(AffineTransform at, Rectangle2D rect) {
2897: TextNode tn = getTextNode();
2898:
2899: Set elems = new HashSet();
2900: List list = tn.getTextRuns();
2901: if (list == null)
2902: return elems;
2903:
2904: Set reject = new HashSet();
2905: for (int i = 0; i < list.size(); i++) {
2906: StrokingTextPainter.TextRun run;
2907: run = (StrokingTextPainter.TextRun) list.get(i);
2908: TextSpanLayout layout = run.getLayout();
2909: AttributedCharacterIterator aci = run.getACI();
2910: aci.first();
2911: SoftReference sr;
2912: sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID);
2913: Element elem = (Element) sr.get();
2914:
2915: if (elem == null)
2916: continue;
2917: if (reject.contains(elem))
2918: continue;
2919: if (!isTextSensitive(elem)) {
2920: reject.add(elem);
2921: continue;
2922: }
2923:
2924: Rectangle2D glBounds = layout.getBounds2D();
2925: if (glBounds == null) {
2926: continue;
2927: }
2928:
2929: glBounds = at.createTransformedShape(glBounds)
2930: .getBounds2D();
2931:
2932: if (rect.contains(glBounds)) {
2933: elems.add(elem);
2934: } else {
2935: reject.add(elem);
2936: elems.remove(elem);
2937: }
2938: }
2939:
2940: return elems;
2941: }
2942:
2943: public static boolean getTextIntersection(BridgeContext ctx,
2944: Element elem, AffineTransform ati, Rectangle2D rect,
2945: boolean checkSensitivity) {
2946: SVGContext svgCtx = null;
2947: if (elem instanceof SVGOMElement)
2948: svgCtx = ((SVGOMElement) elem).getSVGContext();
2949: if (svgCtx == null)
2950: return false;
2951:
2952: SVGTextElementBridge txtBridge = null;
2953: if (svgCtx instanceof SVGTextElementBridge)
2954: txtBridge = (SVGTextElementBridge) svgCtx;
2955: else if (svgCtx instanceof AbstractTextChildSVGContext) {
2956: AbstractTextChildSVGContext childCtx;
2957: childCtx = (AbstractTextChildSVGContext) svgCtx;
2958: txtBridge = childCtx.getTextBridge();
2959: }
2960: if (txtBridge == null)
2961: return false;
2962:
2963: TextNode tn = txtBridge.getTextNode();
2964: List list = tn.getTextRuns();
2965: if (list == null)
2966: return false;
2967:
2968: Element txtElem = txtBridge.e;
2969:
2970: AffineTransform at = tn.getGlobalTransform();
2971: at.preConcatenate(ati);
2972:
2973: Rectangle2D tnRect;
2974: tnRect = tn.getBounds();
2975: tnRect = at.createTransformedShape(tnRect).getBounds2D();
2976: if (!rect.intersects(tnRect))
2977: return false;
2978:
2979: for (int i = 0; i < list.size(); i++) {
2980: StrokingTextPainter.TextRun run;
2981: run = (StrokingTextPainter.TextRun) list.get(i);
2982: TextSpanLayout layout = run.getLayout();
2983: AttributedCharacterIterator aci = run.getACI();
2984: aci.first();
2985: SoftReference sr;
2986: sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID);
2987: Element runElem = (Element) sr.get();
2988: if (runElem == null)
2989: continue;
2990:
2991: // Only consider runElem if it is sensitive.
2992: if (checkSensitivity && !isTextSensitive(runElem))
2993: continue;
2994:
2995: Element p = runElem;
2996: while ((p != null) && (p != txtElem) && (p != elem)) {
2997: p = (Element) txtBridge.getParentNode(p);
2998: }
2999: if (p != elem)
3000: continue;
3001:
3002: // runElem is a child of elem so check it out.
3003: Rectangle2D glBounds = layout.getBounds2D();
3004: if (glBounds == null)
3005: continue;
3006: glBounds = at.createTransformedShape(glBounds)
3007: .getBounds2D();
3008: if (!rect.intersects(glBounds))
3009: continue;
3010:
3011: GVTGlyphVector gv = layout.getGlyphVector();
3012: for (int g = 0; g < gv.getNumGlyphs(); g++) {
3013: Shape gBounds = gv.getGlyphLogicalBounds(g);
3014: if (gBounds != null)
3015: gBounds = at.createTransformedShape(gBounds)
3016: .getBounds2D();
3017:
3018: if (gBounds.intersects(rect))
3019: return true;
3020: }
3021: }
3022: return false;
3023: }
3024:
3025: public static Rectangle2D getTextBounds(BridgeContext ctx,
3026: Element elem, boolean checkSensitivity) {
3027: SVGContext svgCtx = null;
3028: if (elem instanceof SVGOMElement)
3029: svgCtx = ((SVGOMElement) elem).getSVGContext();
3030: if (svgCtx == null)
3031: return null;
3032:
3033: SVGTextElementBridge txtBridge = null;
3034: if (svgCtx instanceof SVGTextElementBridge)
3035: txtBridge = (SVGTextElementBridge) svgCtx;
3036: else if (svgCtx instanceof AbstractTextChildSVGContext) {
3037: AbstractTextChildSVGContext childCtx;
3038: childCtx = (AbstractTextChildSVGContext) svgCtx;
3039: txtBridge = childCtx.getTextBridge();
3040: }
3041: if (txtBridge == null)
3042: return null;
3043:
3044: TextNode tn = txtBridge.getTextNode();
3045: List list = tn.getTextRuns();
3046: if (list == null)
3047: return null;
3048:
3049: Element txtElem = txtBridge.e;
3050: Rectangle2D ret = null;
3051:
3052: for (int i = 0; i < list.size(); i++) {
3053: StrokingTextPainter.TextRun run;
3054: run = (StrokingTextPainter.TextRun) list.get(i);
3055: TextSpanLayout layout = run.getLayout();
3056: AttributedCharacterIterator aci = run.getACI();
3057: aci.first();
3058: SoftReference sr;
3059: sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID);
3060: Element runElem = (Element) sr.get();
3061: if (runElem == null)
3062: continue;
3063:
3064: // Only consider runElem if it is sensitive.
3065: if (checkSensitivity && !isTextSensitive(runElem))
3066: continue;
3067:
3068: Element p = runElem;
3069: while ((p != null) && (p != txtElem) && (p != elem)) {
3070: p = (Element) txtBridge.getParentNode(p);
3071: }
3072: if (p != elem)
3073: continue;
3074:
3075: // runElem is a child of elem so include it's bounds.
3076: Rectangle2D glBounds = layout.getBounds2D();
3077: if (glBounds != null) {
3078: if (ret == null)
3079: ret = (Rectangle2D) glBounds.clone();
3080: else
3081: ret.add(glBounds);
3082: }
3083: }
3084: return ret;
3085: }
3086:
3087: public static boolean isTextSensitive(Element e) {
3088: int ptrEvts = CSSUtilities.convertPointerEvents(e);
3089: switch (ptrEvts) {
3090: case GraphicsNode.VISIBLE_PAINTED: // fall-through is intended
3091: case GraphicsNode.VISIBLE_FILL:
3092: case GraphicsNode.VISIBLE_STROKE:
3093: case GraphicsNode.VISIBLE:
3094: return CSSUtilities.convertVisibility(e);
3095: case GraphicsNode.PAINTED:
3096: case GraphicsNode.FILL: // fall-through is intended
3097: case GraphicsNode.STROKE:
3098: case GraphicsNode.ALL:
3099: return true;
3100: case GraphicsNode.NONE:
3101: default:
3102: return false;
3103: }
3104: }
3105: }
|