/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */
package org.apache.batik.bridge;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.SoftReference;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.apache.batik.anim.dom.AbstractSVGAnimatedLength;
import org.apache.batik.anim.dom.AnimatedLiveAttributeValue;
import org.apache.batik.anim.dom.SVGOMAnimatedEnumeration;
import org.apache.batik.anim.dom.SVGOMAnimatedLengthList;
import org.apache.batik.anim.dom.SVGOMAnimatedNumberList;
import org.apache.batik.anim.dom.SVGOMElement;
import org.apache.batik.anim.dom.SVGOMTextPositioningElement;
import org.apache.batik.css.engine.CSSEngineEvent;
import org.apache.batik.css.engine.CSSStylableElement;
import org.apache.batik.css.engine.SVGCSSEngine;
import org.apache.batik.css.engine.StyleMap;
import org.apache.batik.css.engine.value.ListValue;
import org.apache.batik.css.engine.value.Value;
import org.apache.batik.dom.events.NodeEventTarget;
import org.apache.batik.dom.svg.LiveAttributeException;
import org.apache.batik.dom.svg.SVGContext;
import org.apache.batik.dom.svg.SVGTextContent;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.dom.util.XMLSupport;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTFontFamily;
import org.apache.batik.gvt.font.GVTGlyphMetrics;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.font.UnresolvedFontFamily;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextPaintInfo;
import org.apache.batik.gvt.text.TextPath;
import org.apache.batik.constants.XMLConstants;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.w3c.dom.css.CSSValue;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MutationEvent;
import org.w3c.dom.svg.SVGLengthList;
import org.w3c.dom.svg.SVGNumberList;
import org.w3c.dom.svg.SVGTextContentElement;
import org.w3c.dom.svg.SVGTextPositioningElement;

Bridge class for the <text> element.
Author:Stephane Hillion, Bill Haneman
Version:$Id: SVGTextElementBridge.java 1851346 2019-01-15 13:41:00Z ssteiner $
/** * Bridge class for the &lt;text&gt; element. * * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a> * @author <a href="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a> * @version $Id: SVGTextElementBridge.java 1851346 2019-01-15 13:41:00Z ssteiner $ */
public class SVGTextElementBridge extends AbstractGraphicsNodeBridge implements SVGTextContent { protected static final Integer ZERO = 0; public static final AttributedCharacterIterator.Attribute TEXT_COMPOUND_DELIMITER = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER; public static final AttributedCharacterIterator.Attribute TEXT_COMPOUND_ID = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_ID; public static final AttributedCharacterIterator.Attribute PAINT_INFO = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO; public static final AttributedCharacterIterator.Attribute ALT_GLYPH_HANDLER = GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER; public static final AttributedCharacterIterator.Attribute TEXTPATH = GVTAttributedCharacterIterator.TextAttribute.TEXTPATH; public static final AttributedCharacterIterator.Attribute ANCHOR_TYPE = GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE; public static final AttributedCharacterIterator.Attribute GVT_FONT_FAMILIES = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES; public static final AttributedCharacterIterator.Attribute GVT_FONTS = GVTAttributedCharacterIterator.TextAttribute.GVT_FONTS; public static final AttributedCharacterIterator.Attribute BASELINE_SHIFT = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT; protected AttributedString laidoutText; // This is used to track the TextPainterInfo for each element // in this text element. protected WeakHashMap elemTPI = new WeakHashMap(); // This is true if any of the spans of this text element // use a 'complex' SVG font (meaning the font uses more // and just the 'd' attribute on the glyph element. // In this case we need to recreate the font when ever // CSS attributes change on the text - so we can capture // the effects of CSS inheritence. protected boolean usingComplexSVGFont = false;
Constructs a new bridge for the <text> element.
/** * Constructs a new bridge for the &lt;text&gt; element. */
public SVGTextElementBridge() {}
Returns 'text'.
/** * Returns 'text'. */
public String getLocalName() { return SVG_TEXT_TAG; }
Returns a new instance of this bridge.
/** * Returns a new instance of this bridge. */
public Bridge getInstance() { return new SVGTextElementBridge(); } protected TextNode getTextNode() { return (TextNode)node; }
Creates a GraphicsNode according to the specified parameters.
Params:
  • ctx – the bridge context to use
  • e – the element that describes the graphics node to build
Returns:a graphics node that represents the specified element
/** * Creates a <code>GraphicsNode</code> according to the specified parameters. * * @param ctx the bridge context to use * @param e the element that describes the graphics node to build * @return a graphics node that represents the specified element */
public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { TextNode node = (TextNode)super.createGraphicsNode(ctx, e); if (node == null) return null; associateSVGContext(ctx, e, node); // traverse the children to add context on // <tspan>, <tref> and <textPath> Node child = getFirstChild(e); while (child != null) { if (child.getNodeType() == Node.ELEMENT_NODE) { addContextToChild(ctx,(Element)child); } child = getNextSibling(child); } // specify the text painter to use if (ctx.getTextPainter() != null) node.setTextPainter(ctx.getTextPainter()); // 'text-rendering' and 'color-rendering' RenderingHints hints = null; hints = CSSUtilities.convertColorRendering(e, hints); hints = CSSUtilities.convertTextRendering (e, hints); if (hints != null) node.setRenderingHints(hints); node.setLocation(getLocation(ctx, e)); return node; }
Creates the GraphicsNode depending on the GraphicsNodeBridge implementation.
/** * Creates the GraphicsNode depending on the GraphicsNodeBridge * implementation. */
protected GraphicsNode instantiateGraphicsNode() { return new TextNode(); }
Returns the text node location according to the 'x' and 'y' attributes of the specified text element.
Params:
  • ctx – the bridge context to use
  • e – the text element
/** * Returns the text node location according to the 'x' and 'y' * attributes of the specified text element. * * @param ctx the bridge context to use * @param e the text element */
protected Point2D getLocation(BridgeContext ctx, Element e) { try { SVGOMTextPositioningElement te = (SVGOMTextPositioningElement) e; // 'x' attribute - default is 0 SVGOMAnimatedLengthList _x = (SVGOMAnimatedLengthList) te.getX(); _x.check(); SVGLengthList xs = _x.getAnimVal(); float x = 0; if (xs.getNumberOfItems() > 0) { x = xs.getItem(0).getValue(); } // 'y' attribute - default is 0 SVGOMAnimatedLengthList _y = (SVGOMAnimatedLengthList) te.getY(); _y.check(); SVGLengthList ys = _y.getAnimVal(); float y = 0; if (ys.getNumberOfItems() > 0) { y = ys.getItem(0).getValue(); } return new Point2D.Float(x, y); } catch (LiveAttributeException ex) { throw new BridgeException(ctx, ex); } } protected boolean isTextElement(Element e) { if (!SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) return false; String nodeName = e.getLocalName(); return (nodeName.equals(SVG_TEXT_TAG) || nodeName.equals(SVG_TSPAN_TAG) || nodeName.equals(SVG_ALT_GLYPH_TAG) || nodeName.equals(SVG_A_TAG) || nodeName.equals(SVG_TEXT_PATH_TAG) || nodeName.equals(SVG_TREF_TAG)); } protected boolean isTextChild(Element e) { if (!SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) return false; String nodeName = e.getLocalName(); return (nodeName.equals(SVG_TSPAN_TAG) || nodeName.equals(SVG_ALT_GLYPH_TAG) || nodeName.equals(SVG_A_TAG) || nodeName.equals(SVG_TEXT_PATH_TAG) || nodeName.equals(SVG_TREF_TAG)); }
Builds using the specified BridgeContext and element, the specified graphics node.
Params:
  • ctx – the bridge context to use
  • e – the element that describes the graphics node to build
  • node – the graphics node to build
/** * Builds using the specified BridgeContext and element, the * specified graphics node. * * @param ctx the bridge context to use * @param e the element that describes the graphics node to build * @param node the graphics node to build */
public void buildGraphicsNode(BridgeContext ctx, Element e, GraphicsNode node) { e.normalize(); computeLaidoutText(ctx, e, node); // // DO NOT CALL super, 'opacity' is handle during addPaintAttributes() // // 'opacity' node.setComposite(CSSUtilities.convertOpacity(e)); // 'filter' node.setFilter(CSSUtilities.convertFilter(e, node, ctx)); // 'mask' node.setMask(CSSUtilities.convertMask(e, node, ctx)); // 'clip-path' node.setClip(CSSUtilities.convertClipPath(e, node, ctx)); // 'pointer-events' node.setPointerEventType(CSSUtilities.convertPointerEvents(e)); initializeDynamicSupport(ctx, e, node); if (!ctx.isDynamic()) { elemTPI.clear(); } }
Returns false as text is not a container.
/** * Returns false as text is not a container. */
public boolean isComposite() { return false; } // Tree navigation ------------------------------------------------------
Returns the first child node of the given node that should be processed by the text bridge.
/** * Returns the first child node of the given node that should be * processed by the text bridge. */
protected Node getFirstChild(Node n) { return n.getFirstChild(); }
Returns the next sibling node of the given node that should be processed by the text bridge.
/** * Returns the next sibling node of the given node that should be * processed by the text bridge. */
protected Node getNextSibling(Node n) { return n.getNextSibling(); }
Returns the parent node of the given node that should be processed by the text bridge.
/** * Returns the parent node of the given node that should be * processed by the text bridge. */
protected Node getParentNode(Node n) { return n.getParentNode(); } // Listener implementation ----------------------------------------------
The DOM EventListener to receive 'DOMNodeRemoved' event.
/** * The DOM EventListener to receive 'DOMNodeRemoved' event. */
protected DOMChildNodeRemovedEventListener childNodeRemovedEventListener;
The DOM EventListener invoked when a node is removed.
/** * The DOM EventListener invoked when a node is removed. */
protected class DOMChildNodeRemovedEventListener implements EventListener {
Handles 'DOMNodeRemoved' event type.
/** * Handles 'DOMNodeRemoved' event type. */
public void handleEvent(Event evt) { handleDOMChildNodeRemovedEvent((MutationEvent)evt); } }
The DOM EventListener to receive 'DOMSubtreeModified' event.
/** * The DOM EventListener to receive 'DOMSubtreeModified' event. */
protected DOMSubtreeModifiedEventListener subtreeModifiedEventListener;
The DOM EventListener invoked when the subtree is modified.
/** * The DOM EventListener invoked when the subtree is modified. */
protected class DOMSubtreeModifiedEventListener implements EventListener {
Handles 'DOMSubtreeModified' event type.
/** * Handles 'DOMSubtreeModified' event type. */
public void handleEvent(Event evt) { handleDOMSubtreeModifiedEvent((MutationEvent)evt); } } // BridgeUpdateHandler implementation -----------------------------------
This method ensures that any modification to a text element and its children is going to be reflected into the GVT tree.
/** * This method ensures that any modification to a text * element and its children is going to be reflected * into the GVT tree. */
protected void initializeDynamicSupport(BridgeContext ctx, Element e, GraphicsNode node) { super.initializeDynamicSupport(ctx, e, node); if (ctx.isDynamic()) { // Only add the listeners if we are dynamic. addTextEventListeners(ctx, (NodeEventTarget) e); } }
Adds the DOM listeners for this text bridge.
/** * Adds the DOM listeners for this text bridge. */
protected void addTextEventListeners(BridgeContext ctx, NodeEventTarget e) { if (childNodeRemovedEventListener == null) { childNodeRemovedEventListener = new DOMChildNodeRemovedEventListener(); } if (subtreeModifiedEventListener == null) { subtreeModifiedEventListener = new DOMSubtreeModifiedEventListener(); } //to be notified when a child is removed from the //<text> element. e.addEventListenerNS (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved", childNodeRemovedEventListener, true, null); ctx.storeEventListenerNS (e, XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved", childNodeRemovedEventListener, true); //to be notified when the modification of the subtree //of the <text> element is done e.addEventListenerNS (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMSubtreeModified", subtreeModifiedEventListener, false, null); ctx.storeEventListenerNS (e, XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMSubtreeModified", subtreeModifiedEventListener, false); }
Removes the DOM listeners for this text bridge.
/** * Removes the DOM listeners for this text bridge. */
protected void removeTextEventListeners(BridgeContext ctx, NodeEventTarget e) { e.removeEventListenerNS (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMNodeRemoved", childNodeRemovedEventListener, true); e.removeEventListenerNS (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMSubtreeModified", subtreeModifiedEventListener, false); }
Disposes this text element bridge by removing the text event listeners that were added in initializeDynamicSupport.
/** * Disposes this text element bridge by removing the text event listeners * that were added in {@link #initializeDynamicSupport}. */
public void dispose() { removeTextEventListeners(ctx, (NodeEventTarget) e); super.dispose(); }
Add to the element children of the node, an SVGContext to support dynamic update. This is recursive, the children of the nodes are also traversed to add to the support elements their context
Params:
  • ctx – a BridgeContext value
  • e – an Element value
See Also:
/** * Add to the element children of the node, an * <code>SVGContext</code> to support dynamic update. This is * recursive, the children of the nodes are also traversed to add * to the support elements their context * * @param ctx a <code>BridgeContext</code> value * @param e an <code>Element</code> value * * @see org.apache.batik.dom.svg.SVGContext * @see org.apache.batik.bridge.BridgeUpdateHandler */
protected void addContextToChild(BridgeContext ctx, Element e) { if (SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) { if (e.getLocalName().equals(SVG_TSPAN_TAG)) { ((SVGOMElement)e).setSVGContext (new TspanBridge(ctx, this, e)); } else if (e.getLocalName().equals(SVG_TEXT_PATH_TAG)) { ((SVGOMElement)e).setSVGContext (new TextPathBridge(ctx, this, e)); } else if (e.getLocalName().equals(SVG_TREF_TAG)) { ((SVGOMElement)e).setSVGContext (new TRefBridge(ctx, this, e)); } } Node child = getFirstChild(e); while (child != null) { if (child.getNodeType() == Node.ELEMENT_NODE) { addContextToChild(ctx, (Element)child); } child = getNextSibling(child); } }
From the SVGContext from the element children of the node.
Params:
  • ctx – the BridgeContext for the document
  • e – the Element whose subtree's elements will have threir SVGContexts removed
See Also:
/** * From the <code>SVGContext</code> from the element children of the node. * * @param ctx the <code>BridgeContext</code> for the document * @param e the <code>Element</code> whose subtree's elements will have * threir <code>SVGContext</code>s removed * * @see org.apache.batik.dom.svg.SVGContext * @see org.apache.batik.bridge.BridgeUpdateHandler */
protected void removeContextFromChild(BridgeContext ctx, Element e) { if (SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) { if (e.getLocalName().equals(SVG_TSPAN_TAG)) { ((AbstractTextChildBridgeUpdateHandler) ((SVGOMElement) e).getSVGContext()).dispose(); } else if (e.getLocalName().equals(SVG_TEXT_PATH_TAG)) { ((AbstractTextChildBridgeUpdateHandler) ((SVGOMElement) e).getSVGContext()).dispose(); } else if (e.getLocalName().equals(SVG_TREF_TAG)) { ((AbstractTextChildBridgeUpdateHandler) ((SVGOMElement) e).getSVGContext()).dispose(); } } Node child = getFirstChild(e); while (child != null) { if (child.getNodeType() == Node.ELEMENT_NODE) { removeContextFromChild(ctx, (Element)child); } child = getNextSibling(child); } }
Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
/** * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired. */
public void handleDOMNodeInsertedEvent(MutationEvent evt) { Node childNode = (Node)evt.getTarget(); //check the type of the node inserted before discard the layout //in the case of <title> or <desc> or <metadata>, the layout //is unchanged switch(childNode.getNodeType()) { case Node.TEXT_NODE: // fall-through is intended case Node.CDATA_SECTION_NODE: laidoutText = null; break; case Node.ELEMENT_NODE: { Element childElement = (Element)childNode; if (isTextChild(childElement)) { addContextToChild(ctx, childElement); laidoutText = null; } break; } } if (laidoutText == null) { computeLaidoutText(ctx, e, getTextNode()); } }
Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
/** * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired. */
public void handleDOMChildNodeRemovedEvent(MutationEvent evt) { Node childNode = (Node)evt.getTarget(); //check the type of the node inserted before discard the layout //in the case of <title> or <desc> or <metadata>, the layout //is unchanged switch (childNode.getNodeType()) { case Node.TEXT_NODE: // fall-through is intended case Node.CDATA_SECTION_NODE: //the parent has to be a displayed node if (isParentDisplayed(childNode)) { laidoutText = null; } break; case Node.ELEMENT_NODE: { Element childElt = (Element) childNode; if (isTextChild(childElt)) { laidoutText = null; removeContextFromChild(ctx, childElt); } break; } default: } //if the laidoutText was set to null, //then wait for DOMSubtreeChange to recompute it. }
Invoked when an MutationEvent of type 'DOMSubtree' is fired.
/** * Invoked when an MutationEvent of type 'DOMSubtree' is fired. */
public void handleDOMSubtreeModifiedEvent(MutationEvent evt) { //an operation occured onto the children of the //text element, check if the layout was discarded if (laidoutText == null) { computeLaidoutText(ctx, e, getTextNode()); } }
Invoked when an MutationEvent of type 'DOMCharacterDataModified' is fired.
/** * Invoked when an MutationEvent of type 'DOMCharacterDataModified' * is fired. */
public void handleDOMCharacterDataModified(MutationEvent evt){ Node childNode = (Node)evt.getTarget(); //if the parent is displayed, then discard the layout. if (isParentDisplayed(childNode)) { laidoutText = null; } }
Indicate of the parent of a node is a displayed element. <title>, <desc> and <metadata> are non displayable elements.
Returns:true if the parent of the node is <text>, <tspan>, <tref>, <textPath>, <a>, <altGlyph>
/** * Indicate of the parent of a node is * a displayed element. * &lt;title&gt;, &lt;desc&gt; and &lt;metadata&gt; * are non displayable elements. * * @return true if the parent of the node is &lt;text&gt;, * &lt;tspan&gt;, &lt;tref&gt;, &lt;textPath&gt;, &lt;a&gt;, * &lt;altGlyph&gt; */
protected boolean isParentDisplayed(Node childNode) { Node parentNode = getParentNode(childNode); return isTextElement((Element)parentNode); }
Recompute the layout of the <text> node. Assign onto the TextNode pending to the element the new recomputed AttributedString. Also update laidoutText with the new value.
/** * Recompute the layout of the &lt;text&gt; node. * * Assign onto the TextNode pending to the element * the new recomputed AttributedString. Also * update <code>laidoutText</code> with the new * value. */
protected void computeLaidoutText(BridgeContext ctx, Element e, GraphicsNode node) { TextNode tn = (TextNode)node; elemTPI.clear(); AttributedString as = buildAttributedString(ctx, e); if (as == null) { tn.setAttributedCharacterIterator(null); return; } addGlyphPositionAttributes(as, e, ctx); if (ctx.isDynamic()) { laidoutText = new AttributedString(as.getIterator()); } // Install the ACI in the text node. tn.setAttributedCharacterIterator(as.getIterator()); // Now get the real paint into - this needs to // wait until the text node is laidout so we can get // objectBoundingBox info. TextPaintInfo pi = new TextPaintInfo(); setBaseTextPaintInfo(pi, e, node, ctx); // This get's Overline/underline info. setDecorationTextPaintInfo(pi, e); // Install the attributes. addPaintAttributes(as, e, tn, pi, ctx); if (usingComplexSVGFont) { // Force Complex SVG fonts to be recreated, if we have them. tn.setAttributedCharacterIterator(as.getIterator()); } if (ctx.isDynamic()) { checkBBoxChange(); } }
This flag bit indicates if a new ACI has been created in response to a CSSEngineEvent. Avoid creating one ShapePainter per CSS property change
/** * This flag bit indicates if a new ACI has been created in * response to a CSSEngineEvent. * Avoid creating one ShapePainter per CSS property change */
private boolean hasNewACI;
This is the element a CSS property has changed.
/** * This is the element a CSS property has changed. */
private Element cssProceedElement;
Invoked when the animated value of an animatable attribute has changed.
/** * Invoked when the animated value of an animatable attribute has changed. */
public void handleAnimatedAttributeChanged (AnimatedLiveAttributeValue alav) { if (alav.getNamespaceURI() == null) { String ln = alav.getLocalName(); if (ln.equals(SVG_X_ATTRIBUTE) || ln.equals(SVG_Y_ATTRIBUTE) || ln.equals(SVG_DX_ATTRIBUTE) || ln.equals(SVG_DY_ATTRIBUTE) || ln.equals(SVG_ROTATE_ATTRIBUTE) || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE) || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) { char c = ln.charAt(0); if (c == 'x' || c == 'y') { getTextNode().setLocation(getLocation(ctx, e)); } computeLaidoutText(ctx, e, getTextNode()); return; } } super.handleAnimatedAttributeChanged(alav); }
Invoked when CSS properties have changed on an element.
Params:
  • evt – the CSSEngine event that describes the update
/** * Invoked when CSS properties have changed on an element. * * @param evt the CSSEngine event that describes the update */
public void handleCSSEngineEvent(CSSEngineEvent evt) { hasNewACI = false; int [] properties = evt.getProperties(); // first try to find CSS properties that change the layout for (int property : properties) { switch (property) { // fall-through is intended case SVGCSSEngine.BASELINE_SHIFT_INDEX: case SVGCSSEngine.DIRECTION_INDEX: case SVGCSSEngine.DISPLAY_INDEX: case SVGCSSEngine.FONT_FAMILY_INDEX: case SVGCSSEngine.FONT_SIZE_INDEX: case SVGCSSEngine.FONT_STRETCH_INDEX: case SVGCSSEngine.FONT_STYLE_INDEX: case SVGCSSEngine.FONT_WEIGHT_INDEX: case SVGCSSEngine.GLYPH_ORIENTATION_HORIZONTAL_INDEX: case SVGCSSEngine.GLYPH_ORIENTATION_VERTICAL_INDEX: case SVGCSSEngine.KERNING_INDEX: case SVGCSSEngine.LETTER_SPACING_INDEX: case SVGCSSEngine.TEXT_ANCHOR_INDEX: case SVGCSSEngine.UNICODE_BIDI_INDEX: case SVGCSSEngine.WORD_SPACING_INDEX: case SVGCSSEngine.WRITING_MODE_INDEX: { if (!hasNewACI) { hasNewACI = true; computeLaidoutText(ctx, e, getTextNode()); } break; } } } //optimize the calculation of //the painting attributes and decoration //by only recomputing the section for the element cssProceedElement = evt.getElement(); // go for the other CSS properties super.handleCSSEngineEvent(evt); cssProceedElement = null; }
Invoked for each CSS property that has changed.
/** * Invoked for each CSS property that has changed. */
protected void handleCSSPropertyChanged(int property) { switch(property) { // fall-through is intended case SVGCSSEngine.FILL_INDEX: case SVGCSSEngine.FILL_OPACITY_INDEX: case SVGCSSEngine.STROKE_INDEX: case SVGCSSEngine.STROKE_OPACITY_INDEX: case SVGCSSEngine.STROKE_WIDTH_INDEX: case SVGCSSEngine.STROKE_LINECAP_INDEX: case SVGCSSEngine.STROKE_LINEJOIN_INDEX: case SVGCSSEngine.STROKE_MITERLIMIT_INDEX: case SVGCSSEngine.STROKE_DASHARRAY_INDEX: case SVGCSSEngine.STROKE_DASHOFFSET_INDEX: case SVGCSSEngine.TEXT_DECORATION_INDEX: rebuildACI(); break; case SVGCSSEngine.VISIBILITY_INDEX: rebuildACI(); super.handleCSSPropertyChanged(property); break; case SVGCSSEngine.TEXT_RENDERING_INDEX: { RenderingHints hints = node.getRenderingHints(); hints = CSSUtilities.convertTextRendering(e, hints); if (hints != null) { node.setRenderingHints(hints); } break; } case SVGCSSEngine.COLOR_RENDERING_INDEX: { RenderingHints hints = node.getRenderingHints(); hints = CSSUtilities.convertColorRendering(e, hints); if (hints != null) { node.setRenderingHints(hints); } break; } default: super.handleCSSPropertyChanged(property); } } protected void rebuildACI() { if (hasNewACI) return; TextNode textNode = getTextNode(); if (textNode.getAttributedCharacterIterator() == null) return; TextPaintInfo pi, oldPI; if ( cssProceedElement == e ){ pi = new TextPaintInfo(); setBaseTextPaintInfo(pi, e, node, ctx); setDecorationTextPaintInfo(pi, e); oldPI = (TextPaintInfo)elemTPI.get(e); } else { //if a child CSS property has changed, then //retrieve the parent text decoration //and only update the section of the AtrtibutedString of //the child TextPaintInfo parentPI; parentPI = getParentTextPaintInfo(cssProceedElement); pi = getTextPaintInfo(cssProceedElement, textNode, parentPI, ctx); oldPI = (TextPaintInfo)elemTPI.get(cssProceedElement); } if (oldPI == null) return; textNode.swapTextPaintInfo(pi, oldPI); if (usingComplexSVGFont) // Force Complex SVG fonts to be recreated textNode.setAttributedCharacterIterator (textNode.getAttributedCharacterIterator()); } int getElementStartIndex(Element element) { TextPaintInfo tpi = (TextPaintInfo)elemTPI.get(element); if (tpi == null) return -1; return tpi.startChar; } int getElementEndIndex(Element element) { TextPaintInfo tpi = (TextPaintInfo)elemTPI.get(element); if (tpi == null) return -1; return tpi.endChar; } // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // -----------------------------------------------------------------------
Creates the attributed string which represents the given text element children.
Params:
  • ctx – the bridge context to use
  • element – the text element
/** * Creates the attributed string which represents the given text * element children. * * @param ctx the bridge context to use * @param element the text element */
protected AttributedString buildAttributedString(BridgeContext ctx, Element element) { AttributedStringBuffer asb = new AttributedStringBuffer(); fillAttributedStringBuffer(ctx, element, true, null, null, null, asb); return asb.toAttributedString(); }
This is used to store the end of the last piece of text content from an element with xml:space="preserve". When we are stripping trailing spaces we need to make sure we don't strip anything before this point.
/** * This is used to store the end of the last piece of text * content from an element with xml:space="preserve". When * we are stripping trailing spaces we need to make sure * we don't strip anything before this point. */
protected int endLimit;
Fills the given AttributedStringBuffer.
/** * Fills the given AttributedStringBuffer. */
protected void fillAttributedStringBuffer(BridgeContext ctx, Element element, boolean top, TextPath textPath, Integer bidiLevel, Map initialAttributes, AttributedStringBuffer asb) { // 'requiredFeatures', 'requiredExtensions', 'systemLanguage' & // 'display="none". if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent())) || (!CSSUtilities.convertDisplay(element))) { return; } String s = XMLSupport.getXMLSpace(element); boolean preserve = s.equals(SVG_PRESERVE_VALUE); boolean prevEndsWithSpace; Element nodeElement = element; int elementStartChar = asb.length(); if (top) { endLimit = 0; } if (preserve) { endLimit = asb.length(); } Map map = initialAttributes == null ? new HashMap() : new HashMap(initialAttributes); initialAttributes = getAttributeMap(ctx, element, textPath, bidiLevel, map); Object o = map.get(TextAttribute.BIDI_EMBEDDING); Integer subBidiLevel = bidiLevel; if (o != null) { subBidiLevel = (Integer) o; } for (Node n = getFirstChild(element); n != null; n = getNextSibling(n)) { if (preserve) { prevEndsWithSpace = false; } else { if (asb.length() == 0) { prevEndsWithSpace = true; } else { prevEndsWithSpace = (asb.getLastChar() == ' '); } } switch (n.getNodeType()) { case Node.ELEMENT_NODE: if (!SVG_NAMESPACE_URI.equals(n.getNamespaceURI())) break; nodeElement = (Element)n; String ln = n.getLocalName(); if (ln.equals(SVG_TSPAN_TAG) || ln.equals(SVG_ALT_GLYPH_TAG)) { int before = asb.count; fillAttributedStringBuffer(ctx, nodeElement, false, textPath, subBidiLevel, initialAttributes, asb); if (asb.count != before) { initialAttributes = null; } } else if (ln.equals(SVG_TEXT_PATH_TAG)) { SVGTextPathElementBridge textPathBridge = (SVGTextPathElementBridge)ctx.getBridge(nodeElement); TextPath newTextPath = textPathBridge.createTextPath(ctx, nodeElement); if (newTextPath != null) { int before = asb.count; fillAttributedStringBuffer(ctx, nodeElement, false, newTextPath, subBidiLevel, initialAttributes, asb); if (asb.count != before) { initialAttributes = null; } } } else if (ln.equals(SVG_TREF_TAG)) { String uriStr = XLinkSupport.getXLinkHref((Element)n); Element ref = ctx.getReferencedElement((Element)n, uriStr); s = TextUtilities.getElementContent(ref); s = normalizeString(s, preserve, prevEndsWithSpace); if (s.length() != 0) { int trefStart = asb.length(); Map m = initialAttributes == null ? new HashMap() : new HashMap(initialAttributes); getAttributeMap (ctx, nodeElement, textPath, bidiLevel, m); asb.append(s, m); int trefEnd = asb.length() - 1; TextPaintInfo tpi; tpi = (TextPaintInfo)elemTPI.get(nodeElement); tpi.startChar = trefStart; tpi.endChar = trefEnd; initialAttributes = null; } } else if (ln.equals(SVG_A_TAG)) { NodeEventTarget target = (NodeEventTarget)nodeElement; UserAgent ua = ctx.getUserAgent(); SVGAElementBridge.CursorHolder ch; ch = new SVGAElementBridge.CursorHolder (CursorManager.DEFAULT_CURSOR); EventListener l; l = new SVGAElementBridge.AnchorListener(ua, ch); target.addEventListenerNS (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, l, false, null); ctx.storeEventListenerNS (target, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, l, false); int before = asb.count; fillAttributedStringBuffer(ctx, nodeElement, false, textPath, subBidiLevel, initialAttributes, asb); if (asb.count != before) { initialAttributes = null; } } break; case Node.TEXT_NODE: // fall-through is intended case Node.CDATA_SECTION_NODE: s = n.getNodeValue(); s = normalizeString(s, preserve, prevEndsWithSpace); if (s.length() != 0) { asb.append(s, map); if (preserve) { endLimit = asb.length(); } initialAttributes = null; } } } if (top) { boolean strippedSome = false; while ((endLimit < asb.length()) && (asb.getLastChar() == ' ')) { asb.stripLast(); strippedSome = true; } if (strippedSome) { for (Object o1 : elemTPI.values()) { TextPaintInfo tpi = (TextPaintInfo) o1; if (tpi.endChar >= asb.length()) { tpi.endChar = asb.length() - 1; if (tpi.startChar > tpi.endChar) tpi.startChar = tpi.endChar; } } } } int elementEndChar = asb.length()-1; TextPaintInfo tpi = (TextPaintInfo)elemTPI.get(element); tpi.startChar = elementStartChar; tpi.endChar = elementEndChar; }
Normalizes the given string.
/** * Normalizes the given string. */
protected String normalizeString(String s, boolean preserve, boolean stripfirst) { StringBuffer sb = new StringBuffer(s.length()); if (preserve) { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { // fall-through is intended case 10: case 13: case '\t': sb.append(' '); break; default: sb.append(c); } } return sb.toString(); } int idx = 0; if (stripfirst) { loop: while (idx < s.length()) { switch (s.charAt(idx)) { default: break loop; case 10: // fall-through is intended case 13: case ' ': case '\t': idx++; } } } boolean space = false; for (int i = idx; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case 10: // fall-through is intended case 13: break; case ' ': // fall-through is intended case '\t': if (!space) { sb.append(' '); space = true; } break; default: sb.append(c); space = false; } } return sb.toString(); }
This class is used to build an AttributedString.
/** * This class is used to build an AttributedString. */
protected static class AttributedStringBuffer {
The strings.
/** * The strings. */
protected List strings;
The attributes.
/** * The attributes. */
protected List attributes;
The number of items.
/** * The number of items. */
protected int count;
The length of the attributed string.
/** * The length of the attributed string. */
protected int length;
Creates a new empty AttributedStringBuffer.
/** * Creates a new empty AttributedStringBuffer. */
public AttributedStringBuffer() { strings = new ArrayList(); attributes = new ArrayList(); count = 0; length = 0; }
Tells whether this AttributedStringBuffer is empty.
/** * Tells whether this AttributedStringBuffer is empty. */
public boolean isEmpty() { return count == 0; }
Returns the length in chars of the current Attributed String
/** * Returns the length in chars of the current Attributed String */
public int length() { return length; }
Appends a String and its associated attributes.
/** * Appends a String and its associated attributes. */
public void append(String s, Map m) { if (s.length() == 0) return; strings.add(s); attributes.add(m); count++; length += s.length(); }
Returns the value of the last char or -1.
/** * Returns the value of the last char or -1. */
public int getLastChar() { if (count == 0) { return -1; } String s = (String)strings.get(count - 1); return s.charAt(s.length() - 1); }
Strips the last string character.
/** * Strips the last string character. */
public void stripFirst() { String s = (String)strings.get(0); if (s.charAt(s.length() - 1) != ' ') return; length--; if (s.length() == 1) { attributes.remove(0); strings.remove(0); count--; return; } strings.set(0, s.substring(1)); }
Strips the last string character.
/** * Strips the last string character. */
public void stripLast() { String s = (String)strings.get(count - 1); if (s.charAt(s.length() - 1) != ' ') return; length--; if (s.length() == 1) { attributes.remove(--count); strings.remove(count); return; } strings.set(count-1, s.substring(0, s.length() - 1)); }
Builds an attributed string from the content of this buffer.
/** * Builds an attributed string from the content of this * buffer. */
public AttributedString toAttributedString() { switch (count) { case 0: return null; case 1: return new AttributedString((String)strings.get(0), (Map)attributes.get(0)); } StringBuffer sb = new StringBuffer( strings.size() * 5 ); for (Object string : strings) { sb.append((String) string); } AttributedString result = new AttributedString(sb.toString()); // Set the attributes Iterator sit = strings.iterator(); Iterator ait = attributes.iterator(); int idx = 0; while (sit.hasNext()) { String s = (String)sit.next(); int nidx = idx + s.length(); Map m = (Map)ait.next(); Iterator kit = m.keySet().iterator(); Iterator vit = m.values().iterator(); while (kit.hasNext()) { Attribute attr = (Attribute)kit.next(); Object val = vit.next(); result.addAttribute(attr, val, idx, nidx); } idx = nidx; } return result; } public String toString() { switch (count) { case 0: return ""; case 1: return (String)strings.get(0); } StringBuffer sb = new StringBuffer( strings.size() * 5 ); for (Object string : strings) { sb.append((String) string); } return sb.toString(); } }
Returns true if node1 is an ancestor of node2
/** * Returns true if node1 is an ancestor of node2 */
protected boolean nodeAncestorOf(Node node1, Node node2) { if (node2 == null || node1 == null) { return false; } Node parent = getParentNode(node2); while (parent != null && parent != node1) { parent = getParentNode(parent); } return (parent == node1); }
Adds glyph position attributes to an AttributedString.
/** * Adds glyph position attributes to an AttributedString. */
protected void addGlyphPositionAttributes(AttributedString as, Element element, BridgeContext ctx) { // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage' if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent())) || (!CSSUtilities.convertDisplay(element))) { return; } if (element.getLocalName().equals(SVG_TEXT_PATH_TAG)) { // 'textPath' doesn't support position attributes. addChildGlyphPositionAttributes(as, element, ctx); return; } // calculate which chars in the string belong to this element int firstChar = getElementStartIndex(element); // No match so no chars to annotate. if (firstChar == -1) return; int lastChar = getElementEndIndex(element); // 'a' elements aren't SVGTextPositioningElements, so don't process // their positioning attributes on them. if (!(element instanceof SVGTextPositioningElement)) { addChildGlyphPositionAttributes(as, element, ctx); return; } // get all of the glyph position attribute values SVGTextPositioningElement te = (SVGTextPositioningElement) element; try { SVGOMAnimatedLengthList _x = (SVGOMAnimatedLengthList) te.getX(); _x.check(); SVGOMAnimatedLengthList _y = (SVGOMAnimatedLengthList) te.getY(); _y.check(); SVGOMAnimatedLengthList _dx = (SVGOMAnimatedLengthList) te.getDx(); _dx.check(); SVGOMAnimatedLengthList _dy = (SVGOMAnimatedLengthList) te.getDy(); _dy.check(); SVGOMAnimatedNumberList _rotate = (SVGOMAnimatedNumberList) te.getRotate(); _rotate.check(); SVGLengthList xs = _x.getAnimVal(); SVGLengthList ys = _y.getAnimVal(); SVGLengthList dxs = _dx.getAnimVal(); SVGLengthList dys = _dy.getAnimVal(); SVGNumberList rs = _rotate.getAnimVal(); int len; // process the x attribute len = xs.getNumberOfItems(); for (int i = 0; i < len && firstChar + i <= lastChar; i++) { as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.X, xs.getItem(i).getValue(), firstChar + i, firstChar + i + 1); } // process the y attribute len = ys.getNumberOfItems(); for (int i = 0; i < len && firstChar + i <= lastChar; i++) { as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.Y, ys.getItem(i).getValue(), firstChar + i, firstChar + i + 1); } // process dx attribute len = dxs.getNumberOfItems(); for (int i = 0; i < len && firstChar + i <= lastChar; i++) { as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.DX, dxs.getItem(i).getValue(), firstChar + i, firstChar + i + 1); } // process dy attribute len = dys.getNumberOfItems(); for (int i = 0; i < len && firstChar + i <= lastChar; i++) { as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.DY, dys.getItem(i).getValue(), firstChar + i, firstChar + i + 1); } // process rotate attribute len = rs.getNumberOfItems(); if (len == 1) { // not a list // each char will have the same rotate value Float rad = (float) Math.toRadians(rs.getItem(0).getValue()); as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.ROTATION, rad, firstChar, lastChar + 1); } else if (len > 1) { // it's a list // set each rotate value from the list for (int i = 0; i < len && firstChar + i <= lastChar; i++) { Float rad = (float) Math.toRadians(rs.getItem(i).getValue()); as.addAttribute (GVTAttributedCharacterIterator.TextAttribute.ROTATION, rad, firstChar + i, firstChar + i + 1); } } addChildGlyphPositionAttributes(as, element, ctx); } catch (LiveAttributeException ex) { throw new BridgeException(ctx, ex); } } protected void addChildGlyphPositionAttributes(AttributedString as, Element element, BridgeContext ctx) { // do the same for each child element for (Node child = getFirstChild(element); child != null; child = getNextSibling(child)) { if (child.getNodeType() != Node.ELEMENT_NODE) continue; Element childElement = (Element)child; if (isTextChild(childElement)) { addGlyphPositionAttributes(as, childElement, ctx); } } }
Adds painting attributes to an AttributedString.
/** * Adds painting attributes to an AttributedString. */
protected void addPaintAttributes(AttributedString as, Element element, TextNode node, TextPaintInfo pi, BridgeContext ctx) { // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage' if ((!SVGUtilities.matchUserAgent(element, ctx.getUserAgent())) || (!CSSUtilities.convertDisplay(element))) { return; } Object o = elemTPI.get(element); if (o != null) { node.swapTextPaintInfo(pi, (TextPaintInfo)o); } addChildPaintAttributes(as, element, node, pi, ctx); } protected void addChildPaintAttributes(AttributedString as, Element element, TextNode node, TextPaintInfo parentPI, BridgeContext ctx) { // Add Paint attributres for children of text element for (Node child = getFirstChild(element); child != null; child = getNextSibling(child)) { if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } Element childElement = (Element)child; if (isTextChild(childElement)) { TextPaintInfo pi = getTextPaintInfo(childElement, node, parentPI, ctx); addPaintAttributes(as, childElement, node, pi, ctx); } } }
This method adds all the font related properties to result It also builds a List of the GVTFonts and returns it.
/** * This method adds all the font related properties to <code>result</code> * It also builds a List of the GVTFonts and returns it. */
protected List getFontList(BridgeContext ctx, Element element, Map result) { // Unique value for text element - used for run identification. result.put(TEXT_COMPOUND_ID, new SoftReference(element)); Float fsFloat = TextUtilities.convertFontSize(element); float fontSize = fsFloat; // Font size. result.put(TextAttribute.SIZE, fsFloat); // Font stretch result.put(TextAttribute.WIDTH, TextUtilities.convertFontStretch(element)); // Font style result.put(TextAttribute.POSTURE, TextUtilities.convertFontStyle(element)); // Font weight result.put(TextAttribute.WEIGHT, TextUtilities.convertFontWeight(element)); // Font weight Value v = CSSUtilities.getComputedStyle (element, SVGCSSEngine.FONT_WEIGHT_INDEX); String fontWeightString = v.getCssText(); // Font style String fontStyleString = CSSUtilities.getComputedStyle (element, SVGCSSEngine.FONT_STYLE_INDEX).getStringValue(); // Needed for SVG fonts (also for dynamic documents). result.put(TEXT_COMPOUND_DELIMITER, element); // make a list of GVTFont objects Value val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.FONT_FAMILY_INDEX); List fontFamilyList = new ArrayList(); List fontList = new ArrayList(); int len = val.getLength(); for (int i = 0; i < len; i++) { Value it = val.item(i); String fontFamilyName = it.getStringValue(); GVTFontFamily fontFamily; fontFamily = SVGFontUtilities.getFontFamily(element, ctx, fontFamilyName, fontWeightString, fontStyleString); if (fontFamily != null && fontFamily instanceof UnresolvedFontFamily) { fontFamily = ctx.getFontFamilyResolver().resolve(fontFamily.getFamilyName()); } if (fontFamily == null) { continue; } fontFamilyList.add(fontFamily); if (fontFamily.isComplex()) { usingComplexSVGFont = true; } GVTFont ft = fontFamily.deriveFont(fontSize, result); fontList.add(ft); } // Eventually this will need to go for SVG fonts it // holds hard ref to DOM. result.put(GVT_FONT_FAMILIES, fontFamilyList); if (!ctx.isDynamic()) { // Only leave this in the map for dynamic documents. // Otherwise it will cause the whole DOM to stay when // we don't really need it. result.remove(TEXT_COMPOUND_DELIMITER); } return fontList; }
Returns the map to pass to the current characters.
Params:
  • ctx – the BridgeContext to use for throwing exceptions
  • element – the text element whose attributes are being collected
  • textPath – the text path that the characters of element will be placed along
  • bidiLevel – the bidi level of element
  • result – a Map into which the attributes of element's characters will be stored
Returns:a new Map that contains the attributes that must be inherited into a child element if the given element has no characters before the child element
/** * Returns the map to pass to the current characters. * * @param ctx the BridgeContext to use for throwing exceptions * @param element the text element whose attributes are being collected * @param textPath the text path that the characters of <code>element</code> * will be placed along * @param bidiLevel the bidi level of <code>element</code> * @param result a Map into which the attributes of <code>element</code>'s * characters will be stored * @return a new Map that contains the attributes that must be inherited * into a child element if the given element has no characters before * the child element */
protected Map getAttributeMap(BridgeContext ctx, Element element, TextPath textPath, Integer bidiLevel, Map result) { SVGTextContentElement tce = null; if (element instanceof SVGTextContentElement) { // 'a' elements aren't SVGTextContentElements, so they shouldn't // be checked for 'textLength' or 'lengthAdjust' attributes. tce = (SVGTextContentElement) element; } Map inheritMap = null; String s; if (SVG_NAMESPACE_URI.equals(element.getNamespaceURI()) && element.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { result.put(ALT_GLYPH_HANDLER, new SVGAltGlyphHandler(ctx, element)); } // Add null TPI objects to the text (after we set it on the // Text we will swap in the correct values. TextPaintInfo pi = new TextPaintInfo(); // Set some basic props so we can get bounds info for complex paints. pi.visible = true; pi.fillPaint = Color.black; result.put(PAINT_INFO, pi); elemTPI.put(element, pi); if (textPath != null) { result.put(TEXTPATH, textPath); } // Text-anchor TextNode.Anchor a = TextUtilities.convertTextAnchor(element); result.put(ANCHOR_TYPE, a); // Font family List fontList = getFontList(ctx, element, result); result.put(GVT_FONTS, fontList); // Text baseline adjustment. Object bs = TextUtilities.convertBaselineShift(element); if (bs != null) { result.put(BASELINE_SHIFT, bs); } // Unicode-bidi mode Value val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.UNICODE_BIDI_INDEX); s = val.getStringValue(); if (s.charAt(0) == 'n') { if (bidiLevel != null) result.put(TextAttribute.BIDI_EMBEDDING, bidiLevel); } else { // Text direction // XXX: this needs to coordinate with the unicode-bidi // property, so that when an explicit reversal // occurs, the BIDI_EMBEDDING level is // appropriately incremented or decremented. // Note that direction is implicitly handled by unicode // BiDi algorithm in most cases, this property // is only needed when one wants to override the // normal writing direction for a string/substring. val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.DIRECTION_INDEX); String rs = val.getStringValue(); int cbidi = 0; if (bidiLevel != null) cbidi = bidiLevel; // We don't care if it was embed or override we just want // it's level here. So map override to positive value. if (cbidi < 0) cbidi = -cbidi; switch (rs.charAt(0)) { case 'l': result.put(TextAttribute.RUN_DIRECTION, TextAttribute.RUN_DIRECTION_LTR); if ((cbidi & 0x1) == 1) cbidi++; // was odd now even else cbidi+=2; // next greater even number break; case 'r': result.put(TextAttribute.RUN_DIRECTION, TextAttribute.RUN_DIRECTION_RTL); if ((cbidi & 0x1) == 1) cbidi+=2; // next greater odd number else cbidi++; // was even now odd break; } switch (s.charAt(0)) { case 'b': // bidi-override cbidi = -cbidi; // For bidi-override we want a negative number. break; } result.put(TextAttribute.BIDI_EMBEDDING, cbidi); } // Writing mode val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.WRITING_MODE_INDEX); s = val.getStringValue(); switch (s.charAt(0)) { case 'l': result.put(GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE, GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE_LTR); break; case 'r': result.put(GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE, GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE_RTL); break; case 't': result.put(GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE, GVTAttributedCharacterIterator. TextAttribute.WRITING_MODE_TTB); break; } // glyph-orientation-vertical val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.GLYPH_ORIENTATION_VERTICAL_INDEX); int primitiveType = val.getPrimitiveType(); switch ( primitiveType ) { case CSSPrimitiveValue.CSS_IDENT: // auto result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION, GVTAttributedCharacterIterator. TextAttribute.ORIENTATION_AUTO); break; case CSSPrimitiveValue.CSS_DEG: result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION, GVTAttributedCharacterIterator. TextAttribute.ORIENTATION_ANGLE); result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION_ANGLE, val.getFloatValue()); break; case CSSPrimitiveValue.CSS_RAD: result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION, GVTAttributedCharacterIterator. TextAttribute.ORIENTATION_ANGLE); result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION_ANGLE, (float) Math.toDegrees(val.getFloatValue())); break; case CSSPrimitiveValue.CSS_GRAD: result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION, GVTAttributedCharacterIterator. TextAttribute.ORIENTATION_ANGLE); result.put(GVTAttributedCharacterIterator. TextAttribute.VERTICAL_ORIENTATION_ANGLE, val.getFloatValue() * 9 / 5); break; default: // Cannot happen throw new IllegalStateException("unexpected primitiveType (V):" + primitiveType ); } // glyph-orientation-horizontal val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.GLYPH_ORIENTATION_HORIZONTAL_INDEX); primitiveType = val.getPrimitiveType(); switch ( primitiveType ) { case CSSPrimitiveValue.CSS_DEG: result.put(GVTAttributedCharacterIterator. TextAttribute.HORIZONTAL_ORIENTATION_ANGLE, val.getFloatValue()); break; case CSSPrimitiveValue.CSS_RAD: result.put(GVTAttributedCharacterIterator. TextAttribute.HORIZONTAL_ORIENTATION_ANGLE, (float) Math.toDegrees(val.getFloatValue())); break; case CSSPrimitiveValue.CSS_GRAD: result.put(GVTAttributedCharacterIterator. TextAttribute.HORIZONTAL_ORIENTATION_ANGLE, val.getFloatValue() * 9 / 5); break; default: // Cannot happen throw new IllegalStateException("unexpected primitiveType (H):" + primitiveType ); } // text spacing properties... // Letter Spacing Float sp = TextUtilities.convertLetterSpacing(element); if (sp != null) { result.put(GVTAttributedCharacterIterator. TextAttribute.LETTER_SPACING, sp); result.put(GVTAttributedCharacterIterator. TextAttribute.CUSTOM_SPACING, Boolean.TRUE); } // Word spacing sp = TextUtilities.convertWordSpacing(element); if (sp != null) { result.put(GVTAttributedCharacterIterator. TextAttribute.WORD_SPACING, sp); result.put(GVTAttributedCharacterIterator. TextAttribute.CUSTOM_SPACING, Boolean.TRUE); } // Kerning sp = TextUtilities.convertKerning(element); if (sp != null) { result.put(GVTAttributedCharacterIterator.TextAttribute.KERNING, sp); result.put(GVTAttributedCharacterIterator. TextAttribute.CUSTOM_SPACING, Boolean.TRUE); } if (tce == null) { return inheritMap; } try { // textLength AbstractSVGAnimatedLength textLength = (AbstractSVGAnimatedLength) tce.getTextLength(); if (textLength.isSpecified()) { if (inheritMap == null) { inheritMap = new HashMap(); } Object value = textLength.getCheckedValue(); result.put (GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH, value); inheritMap.put (GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH, value); // lengthAdjust SVGOMAnimatedEnumeration _lengthAdjust = (SVGOMAnimatedEnumeration) tce.getLengthAdjust(); if (_lengthAdjust.getCheckedVal() == SVGTextContentElement.LENGTHADJUST_SPACINGANDGLYPHS) { result.put(GVTAttributedCharacterIterator. TextAttribute.LENGTH_ADJUST, GVTAttributedCharacterIterator. TextAttribute.ADJUST_ALL); inheritMap.put(GVTAttributedCharacterIterator. TextAttribute.LENGTH_ADJUST, GVTAttributedCharacterIterator. TextAttribute.ADJUST_ALL); } else { result.put(GVTAttributedCharacterIterator. TextAttribute.LENGTH_ADJUST, GVTAttributedCharacterIterator. TextAttribute.ADJUST_SPACING); inheritMap.put(GVTAttributedCharacterIterator. TextAttribute.LENGTH_ADJUST, GVTAttributedCharacterIterator. TextAttribute.ADJUST_SPACING); result.put(GVTAttributedCharacterIterator. TextAttribute.CUSTOM_SPACING, Boolean.TRUE); inheritMap.put(GVTAttributedCharacterIterator. TextAttribute.CUSTOM_SPACING, Boolean.TRUE); } } } catch (LiveAttributeException ex) { throw new BridgeException(ctx, ex); } return inheritMap; }
Retrieve in the AttributeString the closest parent of the node 'child' and extract the text decorations of the parent.
Params:
  • child – an Element value
Returns:a TextDecoration value
/** * Retrieve in the AttributeString the closest parent * of the node 'child' and extract the text decorations * of the parent. * * @param child an <code>Element</code> value * @return a <code>TextDecoration</code> value */
protected TextPaintInfo getParentTextPaintInfo(Element child) { Node parent = getParentNode(child); while (parent != null) { TextPaintInfo tpi = (TextPaintInfo)elemTPI.get(parent); if (tpi != null) return tpi; parent = getParentNode(parent); } return null; }
Constructs a TextDecoration object for the specified element. This will contain all of the decoration properties to be used when drawing the text.
/** * Constructs a TextDecoration object for the specified element. This will * contain all of the decoration properties to be used when drawing the * text. */
protected TextPaintInfo getTextPaintInfo(Element element, GraphicsNode node, TextPaintInfo parentTPI, BridgeContext ctx) { // Force the engine to update stuff.. CSSUtilities.getComputedStyle (element, SVGCSSEngine.TEXT_DECORATION_INDEX); TextPaintInfo pi = new TextPaintInfo(parentTPI); // Was text-decoration explicity set on this element? StyleMap sm = ((CSSStylableElement)element).getComputedStyleMap(null); if ((sm.isNullCascaded(SVGCSSEngine.TEXT_DECORATION_INDEX)) && (sm.isNullCascaded(SVGCSSEngine.FILL_INDEX)) && (sm.isNullCascaded(SVGCSSEngine.STROKE_INDEX)) && (sm.isNullCascaded(SVGCSSEngine.STROKE_WIDTH_INDEX)) && (sm.isNullCascaded(SVGCSSEngine.OPACITY_INDEX))) { // If not, keep the same decorations. return pi; } setBaseTextPaintInfo(pi, element, node, ctx); if (!sm.isNullCascaded(SVGCSSEngine.TEXT_DECORATION_INDEX)) setDecorationTextPaintInfo(pi, element); return pi; } public void setBaseTextPaintInfo(TextPaintInfo pi, Element element, GraphicsNode node, BridgeContext ctx) { if (!element.getLocalName().equals(SVG_TEXT_TAG)) pi.composite = CSSUtilities.convertOpacity (element); else pi.composite = AlphaComposite.SrcOver; pi.visible = CSSUtilities.convertVisibility(element); pi.fillPaint = PaintServer.convertFillPaint (element, node, ctx); pi.strokePaint = PaintServer.convertStrokePaint(element, node, ctx); pi.strokeStroke = PaintServer.convertStroke (element); } public void setDecorationTextPaintInfo(TextPaintInfo pi, Element element) { Value val = CSSUtilities.getComputedStyle (element, SVGCSSEngine.TEXT_DECORATION_INDEX); switch (val.getCssValueType()) { case CSSValue.CSS_VALUE_LIST: ListValue lst = (ListValue)val; int len = lst.getLength(); for (int i = 0; i < len; i++) { Value v = lst.item(i); String s = v.getStringValue(); switch (s.charAt(0)) { case 'u': if (pi.fillPaint != null) { pi.underlinePaint = pi.fillPaint; } if (pi.strokePaint != null) { pi.underlineStrokePaint = pi.strokePaint; } if (pi.strokeStroke != null) { pi.underlineStroke = pi.strokeStroke; } break; case 'o': if (pi.fillPaint != null) { pi.overlinePaint = pi.fillPaint; } if (pi.strokePaint != null) { pi.overlineStrokePaint = pi.strokePaint; } if (pi.strokeStroke != null) { pi.overlineStroke = pi.strokeStroke; } break; case 'l': if (pi.fillPaint != null) { pi.strikethroughPaint = pi.fillPaint; } if (pi.strokePaint != null) { pi.strikethroughStrokePaint = pi.strokePaint; } if (pi.strokeStroke != null) { pi.strikethroughStroke = pi.strokeStroke; } break; } } break; default: // None pi.underlinePaint = null; pi.underlineStrokePaint = null; pi.underlineStroke = null; pi.overlinePaint = null; pi.overlineStrokePaint = null; pi.overlineStroke = null; pi.strikethroughPaint = null; pi.strikethroughStrokePaint = null; pi.strikethroughStroke = null; break; } }
Implementation of SVGContext for the children of <text>
/** * Implementation of <code>SVGContext</code> for * the children of &lt;text&gt; */
public abstract static class AbstractTextChildSVGContext extends AnimatableSVGBridge {
Text bridge parent
/** Text bridge parent */
protected SVGTextElementBridge textBridge;
Initialize the SVGContext implementation with the bridgeContext, the parent bridge, and the element supervised by this context
/** * Initialize the <code>SVGContext</code> implementation * with the bridgeContext, the parent bridge, and the * element supervised by this context */
public AbstractTextChildSVGContext(BridgeContext ctx, SVGTextElementBridge parent, Element e) { this.ctx = ctx; this.textBridge = parent; this.e = e; }
Returns the namespace URI of the element this Bridge is dedicated to.
/** * Returns the namespace URI of the element this <code>Bridge</code> is * dedicated to. */
public String getNamespaceURI() { return null; }
Returns the local name of the element this Bridge is dedicated to.
/** * Returns the local name of the element this <code>Bridge</code> is dedicated * to. */
public String getLocalName() { return null; }
Returns a new instance of this bridge.
/** * Returns a new instance of this bridge. */
public Bridge getInstance() { return null; } public SVGTextElementBridge getTextBridge() { return textBridge; }
Returns the size of a px CSS unit in millimeters.
/** * Returns the size of a px CSS unit in millimeters. */
public float getPixelUnitToMillimeter() { return ctx.getUserAgent().getPixelUnitToMillimeter(); }
Returns the size of a px CSS unit in millimeters. This will be removed after next release.
See Also:
  • getPixelUnitToMillimeter()
/** * Returns the size of a px CSS unit in millimeters. * This will be removed after next release. * @see #getPixelUnitToMillimeter() */
public float getPixelToMM() { return getPixelUnitToMillimeter(); }
Returns the tight bounding box in current user space (i.e., after application of the transform attribute, if any) on the geometry of all contained graphics elements, exclusive of stroke-width and filter effects).
/** * Returns the tight bounding box in current user space (i.e., * after application of the transform attribute, if any) on the * geometry of all contained graphics elements, exclusive of * stroke-width and filter effects). */
public Rectangle2D getBBox() { //text children does not support getBBox //return textBridge.getBBox(); return null; }
Returns the transformation matrix from current user units (i.e., after application of the transform attribute, if any) to the viewport coordinate system for the nearestViewportElement.
/** * Returns the transformation matrix from current user units * (i.e., after application of the transform attribute, if any) to * the viewport coordinate system for the nearestViewportElement. */
public AffineTransform getCTM() { // text children does not support transform attribute //return textBridge.getCTM(); return null; }
Returns the global transformation matrix from the current element to the root.
/** * Returns the global transformation matrix from the current * element to the root. */
public AffineTransform getGlobalTransform() { //return node.getGlobalTransform(); return null; }
Returns the transformation matrix from the userspace of the root element to the screen.
/** * Returns the transformation matrix from the userspace of * the root element to the screen. */
public AffineTransform getScreenTransform() { //return node.getScreenTransform(); return null; }
Sets the transformation matrix to be used from the userspace of the root element to the screen.
/** * Sets the transformation matrix to be used from the * userspace of the root element to the screen. */
public void setScreenTransform(AffineTransform at) { //return node.setScreenTransform(at); return; }
Returns the width of the viewport which directly contains the given element.
/** * Returns the width of the viewport which directly contains the * given element. */
public float getViewportWidth() { return ctx.getBlockWidth(e); }
Returns the height of the viewport which directly contains the given element.
/** * Returns the height of the viewport which directly contains the * given element. */
public float getViewportHeight() { return ctx.getBlockHeight(e); }
Returns the font-size on the associated element.
/** * Returns the font-size on the associated element. */
public float getFontSize() { return CSSUtilities.getComputedStyle (e, SVGCSSEngine.FONT_SIZE_INDEX).getFloatValue(); } }
Implementation for the BridgeUpdateHandler for the child elements of <text>. This implementation relies on the parent bridge which contains the TextNode representing the node this context supervised. All operations are done by the parent bridge SVGTextElementBridge which can determine the impact of a change of one of its children for the others.
/** * Implementation for the <code>BridgeUpdateHandler</code> * for the child elements of &lt;text&gt;. * This implementation relies on the parent bridge * which contains the <code>TextNode</code> * representing the node this context supervised. * All operations are done by the parent bridge * <code>SVGTextElementBridge</code> which can determine * the impact of a change of one of its children for the others. */
protected abstract class AbstractTextChildBridgeUpdateHandler extends AbstractTextChildSVGContext implements BridgeUpdateHandler {
Initialize the BridgeUpdateHandler implementation.
/** * Initialize the BridgeUpdateHandler implementation. */
protected AbstractTextChildBridgeUpdateHandler (BridgeContext ctx, SVGTextElementBridge parent, Element e) { super(ctx,parent,e); }
Invoked when an MutationEvent of type 'DOMAttrModified' is fired.
/** * Invoked when an MutationEvent of type 'DOMAttrModified' is fired. */
public void handleDOMAttrModifiedEvent(MutationEvent evt) { //nothing to do }
Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
/** * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired. */
public void handleDOMNodeInsertedEvent(MutationEvent evt) { textBridge.handleDOMNodeInsertedEvent(evt); }
Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
/** * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired. */
public void handleDOMNodeRemovedEvent(MutationEvent evt) { }
Invoked when an MutationEvent of type 'DOMCharacterDataModified' is fired.
/** * Invoked when an MutationEvent of type 'DOMCharacterDataModified' * is fired. */
public void handleDOMCharacterDataModified(MutationEvent evt) { textBridge.handleDOMCharacterDataModified(evt); }
Invoked when an CSSEngineEvent is fired.
/** * Invoked when an CSSEngineEvent is fired. */
public void handleCSSEngineEvent(CSSEngineEvent evt) { textBridge.handleCSSEngineEvent(evt); }
Invoked when the animated value of an animatable attribute has changed.
/** * Invoked when the animated value of an animatable attribute has * changed. */
public void handleAnimatedAttributeChanged (AnimatedLiveAttributeValue alav) { }
Invoked when an 'other' animation value has changed.
/** * Invoked when an 'other' animation value has changed. */
public void handleOtherAnimationChanged(String type) { }
Disposes this BridgeUpdateHandler and releases all resources.
/** * Disposes this BridgeUpdateHandler and releases all resources. */
public void dispose(){ ((SVGOMElement)e).setSVGContext(null); elemTPI.remove(e); } } protected class AbstractTextChildTextContent extends AbstractTextChildBridgeUpdateHandler implements SVGTextContent {
Initialize the AbstractTextChildBridgeUpdateHandler implementation.
/** * Initialize the AbstractTextChildBridgeUpdateHandler implementation. */
protected AbstractTextChildTextContent (BridgeContext ctx, SVGTextElementBridge parent, Element e) { super(ctx,parent,e); } //Implementation of TextContent public int getNumberOfChars(){ return textBridge.getNumberOfChars(e); } public Rectangle2D getExtentOfChar(int charnum ){ return textBridge.getExtentOfChar(e,charnum); } public Point2D getStartPositionOfChar(int charnum){ return textBridge.getStartPositionOfChar(e,charnum); } public Point2D getEndPositionOfChar(int charnum){ return textBridge.getEndPositionOfChar(e,charnum); } public void selectSubString(int charnum, int nchars){ textBridge.selectSubString(e,charnum,nchars); } public float getRotationOfChar(int charnum){ return textBridge.getRotationOfChar(e,charnum); } public float getComputedTextLength(){ return textBridge.getComputedTextLength(e); } public float getSubStringLength(int charnum, int nchars){ return textBridge.getSubStringLength(e,charnum,nchars); } public int getCharNumAtPosition(float x , float y){ return textBridge.getCharNumAtPosition(e,x,y); } }
BridgeUpdateHandle for <tref> element.
/** * BridgeUpdateHandle for &lt;tref&gt; element. */
protected class TRefBridge extends AbstractTextChildTextContent { protected TRefBridge(BridgeContext ctx, SVGTextElementBridge parent, Element e) { super(ctx,parent,e); }
Invoked when the animated value of an animatable attribute has changed on a 'tref' element.
/** * Invoked when the animated value of an animatable attribute has * changed on a 'tref' element. */
public void handleAnimatedAttributeChanged (AnimatedLiveAttributeValue alav) { if (alav.getNamespaceURI() == null) { String ln = alav.getLocalName(); if (ln.equals(SVG_X_ATTRIBUTE) || ln.equals(SVG_Y_ATTRIBUTE) || ln.equals(SVG_DX_ATTRIBUTE) || ln.equals(SVG_DY_ATTRIBUTE) || ln.equals(SVG_ROTATE_ATTRIBUTE) || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE) || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) { // Recompute the layout of the text node. textBridge.computeLaidoutText(ctx, textBridge.e, textBridge.getTextNode()); return; } } super.handleAnimatedAttributeChanged(alav); } }
BridgeUpdateHandle for <textPath> element.
/** * BridgeUpdateHandle for &lt;textPath&gt; element. */
protected class TextPathBridge extends AbstractTextChildTextContent{ protected TextPathBridge(BridgeContext ctx, SVGTextElementBridge parent, Element e){ super(ctx,parent,e); } }
BridgeUpdateHandle for <tspan> element.
/** * BridgeUpdateHandle for &lt;tspan&gt; element. */
protected class TspanBridge extends AbstractTextChildTextContent { protected TspanBridge(BridgeContext ctx, SVGTextElementBridge parent, Element e){ super(ctx,parent,e); }
Invoked when the animated value of an animatable attribute has changed on a 'tspan' element.
/** * Invoked when the animated value of an animatable attribute has * changed on a 'tspan' element. */
public void handleAnimatedAttributeChanged (AnimatedLiveAttributeValue alav) { if (alav.getNamespaceURI() == null) { String ln = alav.getLocalName(); if (ln.equals(SVG_X_ATTRIBUTE) || ln.equals(SVG_Y_ATTRIBUTE) || ln.equals(SVG_DX_ATTRIBUTE) || ln.equals(SVG_DY_ATTRIBUTE) || ln.equals(SVG_ROTATE_ATTRIBUTE) || ln.equals(SVG_TEXT_LENGTH_ATTRIBUTE) || ln.equals(SVG_LENGTH_ADJUST_ATTRIBUTE)) { // Recompute the layout of the text node. textBridge.computeLaidoutText(ctx, textBridge.e, textBridge.getTextNode()); return; } } super.handleAnimatedAttributeChanged(alav); } } //Implementation of TextContent public int getNumberOfChars(){ return getNumberOfChars(e); } public Rectangle2D getExtentOfChar(int charnum ){ return getExtentOfChar(e,charnum); } public Point2D getStartPositionOfChar(int charnum){ return getStartPositionOfChar(e,charnum); } public Point2D getEndPositionOfChar(int charnum){ return getEndPositionOfChar(e,charnum); } public void selectSubString(int charnum, int nchars){ selectSubString(e,charnum,nchars); } public float getRotationOfChar(int charnum){ return getRotationOfChar(e,charnum); } public float getComputedTextLength(){ return getComputedTextLength(e); } public float getSubStringLength(int charnum, int nchars){ return getSubStringLength(e,charnum,nchars); } public int getCharNumAtPosition(float x , float y){ return getCharNumAtPosition(e,x,y); } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getNumberOfChars()}. */ protected int getNumberOfChars(Element element){ AttributedCharacterIterator aci; aci = getTextNode().getAttributedCharacterIterator(); if (aci == null) return 0; //get the index in the aci for the first character //of the element int firstChar = getElementStartIndex(element); if (firstChar == -1) return 0; // Element not part of aci (no chars in elem usually) int lastChar = getElementEndIndex(element); return( lastChar - firstChar + 1 ); } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getExtentOfChar(int charnum)}. */ protected Rectangle2D getExtentOfChar(Element element,int charnum ){ TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return null; int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return null; //retrieve the text run for the text node List list = getTextRuns(textNode); //find the character 'charnum' in the text run CharacterInformation info; info = getCharacterInformation(list, firstChar,charnum, aci); if ( info == null ) return null; //retrieve the glyphvector containing the glyph //for 'charnum' GVTGlyphVector it = info.layout.getGlyphVector(); Shape b = null; if (info.glyphIndexStart == info.glyphIndexEnd) { if (it.isGlyphVisible(info.glyphIndexStart)) { b = it.getGlyphCellBounds(info.glyphIndexStart); } } else { GeneralPath path = null; for (int k = info.glyphIndexStart; k <= info.glyphIndexEnd; k++) { if (it.isGlyphVisible(k)) { Rectangle2D gb = it.getGlyphCellBounds(k); if (path == null) { path = new GeneralPath(gb); } else { path.append(gb, false); } } } b = path; } if (b == null) { return null; } //return the bounding box of the outline return b.getBounds2D(); } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getStartPositionOfChar(int charnum)}. */ protected Point2D getStartPositionOfChar(Element element,int charnum){ TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return null; int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return null; //retrieve the text run for the text node List list = getTextRuns(textNode); //find the character 'charnum' in the text run CharacterInformation info; info = getCharacterInformation(list, firstChar,charnum, aci); if ( info == null ) return null; return getStartPoint( info ); } protected Point2D getStartPoint(CharacterInformation info){ GVTGlyphVector it = info.layout.getGlyphVector(); if (!it.isGlyphVisible(info.glyphIndexStart)) return null; Point2D b = it.getGlyphPosition(info.glyphIndexStart); AffineTransform glyphTransform; glyphTransform = it.getGlyphTransform(info.glyphIndexStart); //glyph are defined starting at position (0,0) Point2D.Float result = new Point2D.Float(0, 0); if ( glyphTransform != null ) //apply the glyph transformation to the start point glyphTransform.transform(result,result); result.x += b.getX(); result.y += b.getY(); return result; } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getEndPositionOfChar(int charnum)}. */ protected Point2D getEndPositionOfChar(Element element,int charnum ){ TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return null; int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return null; //retrieve the text run for the text node List list = getTextRuns(textNode); //find the glyph information for the character 'charnum' CharacterInformation info; info = getCharacterInformation(list, firstChar,charnum, aci); if ( info == null ) return null; return getEndPoint(info); } protected Point2D getEndPoint(CharacterInformation info){ GVTGlyphVector it = info.layout.getGlyphVector(); if (!it.isGlyphVisible(info.glyphIndexEnd)) return null; Point2D b = it.getGlyphPosition(info.glyphIndexEnd); AffineTransform glyphTransform; glyphTransform = it.getGlyphTransform(info.glyphIndexEnd); GVTGlyphMetrics metrics = it.getGlyphMetrics(info.glyphIndexEnd); Point2D.Float result = new Point2D.Float (metrics.getHorizontalAdvance(), 0); if ( glyphTransform != null ) glyphTransform.transform(result,result); result.x += b.getX(); result.y += b.getY(); return result; } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getRotationOfChar(int charnum)}. */ protected float getRotationOfChar(Element element, int charnum){ TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return 0; //first the first character for the element int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return 0; //retrieve the text run for the text node List list = getTextRuns(textNode); //find the glyph information for the character 'charnum' CharacterInformation info; info = getCharacterInformation(list, firstChar,charnum, aci); double angle = 0.0; int nbGlyphs = 0; if ( info != null ){ GVTGlyphVector it = info.layout.getGlyphVector(); for( int k = info.glyphIndexStart ; k <= info.glyphIndexEnd ; k++ ){ if (!it.isGlyphVisible(k)) continue; nbGlyphs++; //the glyph transform contains only a scale and a rotate. AffineTransform glyphTransform = it.getGlyphTransform(k); if ( glyphTransform == null ) continue; double glyphAngle = 0.0; double cosTheta = glyphTransform.getScaleX(); double sinTheta = glyphTransform.getShearX(); //extract the angle if ( cosTheta == 0.0 ){ if ( sinTheta > 0 ) glyphAngle = Math.PI; else glyphAngle = -Math.PI; } else { glyphAngle = Math.atan(sinTheta/cosTheta); // todo is this safe?? if ( cosTheta < 0 ) glyphAngle += Math.PI; } //get a degrees value for the angle //SVG angle are clock wise java anticlockwise glyphAngle = (Math.toDegrees( - glyphAngle ) ) % 360.0; //remove the orientation from the value angle += glyphAngle - info.getComputedOrientationAngle(); } } if (nbGlyphs == 0) return 0; return (float)(angle / nbGlyphs ); } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getComputedTextLength()}. */ protected float getComputedTextLength(Element e) { return getSubStringLength(e,0,getNumberOfChars(e)); } /** * Implementation of {@link * org.w3c.dom.svg.SVGTextContentElement#getSubStringLength(int charnum,int nchars)}. */ protected float getSubStringLength(Element element, int charnum, int nchars){ if (nchars == 0) { return 0; } float length = 0; TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return -1; int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return -1; List list = getTextRuns(textNode); CharacterInformation currentInfo; currentInfo = getCharacterInformation(list, firstChar,charnum,aci); CharacterInformation lastCharacterInRunInfo = null; int chIndex = currentInfo.characterIndex+1; GVTGlyphVector vector = currentInfo.layout.getGlyphVector(); float [] advs = currentInfo.layout.getGlyphAdvances(); boolean [] glyphTrack = new boolean[advs.length]; for( int k = charnum +1; k < charnum +nchars ; k++) { if (currentInfo.layout.isOnATextPath() ){ for (int gi = currentInfo.glyphIndexStart; gi <= currentInfo.glyphIndexEnd; gi++) { if ((vector.isGlyphVisible(gi)) && !glyphTrack[gi]) length += advs[gi+1]-advs[gi]; glyphTrack[gi] = true; } CharacterInformation newInfo; newInfo = getCharacterInformation(list, firstChar, k, aci); if (newInfo.layout != currentInfo.layout) { vector = newInfo.layout.getGlyphVector(); advs = newInfo.layout.getGlyphAdvances(); glyphTrack = new boolean[advs.length]; chIndex = currentInfo.characterIndex+1; } currentInfo = newInfo; } else { //reach the next run if ( currentInfo.layout.hasCharacterIndex(chIndex) ){ chIndex++; continue; } lastCharacterInRunInfo = getCharacterInformation (list,firstChar,k-1,aci); //if the text run change compute the distance between the //first character of the run and the last length += distanceFirstLastCharacterInRun (currentInfo,lastCharacterInRunInfo); currentInfo = getCharacterInformation(list,firstChar,k,aci); chIndex = currentInfo.characterIndex+1; vector = currentInfo.layout.getGlyphVector(); advs = currentInfo.layout.getGlyphAdvances(); glyphTrack = new boolean[advs.length]; lastCharacterInRunInfo = null; } } if (currentInfo.layout.isOnATextPath() ){ for (int gi = currentInfo.glyphIndexStart; gi <= currentInfo.glyphIndexEnd; gi++) { if ((vector.isGlyphVisible(gi)) && !glyphTrack[gi]) length += advs[gi+1]-advs[gi]; glyphTrack[gi] = true; } } else { if ( lastCharacterInRunInfo == null ){ lastCharacterInRunInfo = getCharacterInformation (list,firstChar,charnum+nchars-1,aci); } //add the length between the end position of the last character //and the first character in the run length += distanceFirstLastCharacterInRun (currentInfo,lastCharacterInRunInfo); } return length; } protected float distanceFirstLastCharacterInRun (CharacterInformation first, CharacterInformation last){ float [] advs = first.layout.getGlyphAdvances(); int firstStart = first.glyphIndexStart; int firstEnd = first.glyphIndexEnd; int lastStart = last.glyphIndexStart; int lastEnd = last.glyphIndexEnd; int start = (firstStart<lastStart)?firstStart:lastStart; int end = (firstEnd<lastEnd)?lastEnd:firstEnd; return advs[end+1] - advs[start]; } protected float distanceBetweenRun (CharacterInformation last, CharacterInformation first){ float distance; Point2D startPoint; Point2D endPoint; CharacterInformation info = new CharacterInformation(); //determine where the last run stops info.layout = last.layout; info.glyphIndexEnd = last.layout.getGlyphCount()-1; startPoint = getEndPoint(info); //determine where the next run starts info.layout = first.layout; info.glyphIndexStart = 0; endPoint = getStartPoint(info); if( first.isVertical() ){ distance = (float)(endPoint.getY() - startPoint.getY()); } else{ distance = (float)(endPoint.getX() - startPoint.getX()); } return distance; }
Select an ensemble of characters for that element. TODO : report the selection to the selection manager in JSVGComponent.
/** * Select an ensemble of characters for that element. * * TODO : report the selection to the selection * manager in JSVGComponent. */
protected void selectSubString(Element element, int charnum, int nchars) { TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return; int firstChar = getElementStartIndex(element); if ( firstChar == -1 ) return; List list = getTextRuns(textNode); int lastChar = getElementEndIndex(element); CharacterInformation firstInfo, lastInfo; firstInfo = getCharacterInformation(list, firstChar,charnum,aci); lastInfo = getCharacterInformation(list, firstChar,charnum+nchars-1,aci); Mark firstMark, lastMark; firstMark = textNode.getMarkerForChar(firstInfo.characterIndex,true); if ( lastInfo != null && lastInfo.characterIndex <= lastChar ){ lastMark = textNode.getMarkerForChar(lastInfo.characterIndex,false); } else{ lastMark = textNode.getMarkerForChar(lastChar,false); } ctx.getUserAgent().setTextSelection(firstMark,lastMark); } protected int getCharNumAtPosition(Element e, float x, float y){ TextNode textNode = getTextNode(); AttributedCharacterIterator aci; aci = textNode.getAttributedCharacterIterator(); if (aci == null) return -1; //check if there is an hit List list = getTextRuns(textNode); //going backward in the list to catch the last character // displayed at that position TextHit hit = null; for( int i = list.size()-1 ; i>= 0 && hit == null; i-- ){ StrokingTextPainter.TextRun textRun; textRun = (StrokingTextPainter.TextRun)list.get(i); hit = textRun.getLayout().hitTestChar(x,y); } if ( hit == null ) return -1; //found an hit, check if it belong to the element int first = getElementStartIndex( e ); int last = getElementEndIndex( e ); int hitIndex = hit.getCharIndex(); if ( hitIndex >= first && hitIndex <= last ) return hitIndex - first; return -1; }
Retrieve the list of layout for the text node.
/** * Retrieve the list of layout for the * text node. */
protected List getTextRuns(TextNode node){ //System.out.println(node.getTextRuns()); if ( node.getTextRuns() == null ){ //TODO : need to work out a solution //to compute the text runs node.getPrimitiveBounds(); } //System.out.println(node.getTextRuns()); return node.getTextRuns(); }
Retrieve the information about a character of en element. The element first character in the ACI is 'firstChar' and the character look for is the charnum th character in the element
Params:
  • list – list of the layouts
  • startIndex – index in the ACI of the first character for the element
  • charnum – index of the character (among the characters of the element) looked for.
Returns:information about the glyph representing the character
/** * Retrieve the information about a character * of en element. The element first character in * the ACI is 'firstChar' and the character * look for is the charnum th character in the * element * * @param list list of the layouts * @param startIndex index in the ACI of the first * character for the element * @param charnum index of the character (among the * characters of the element) looked for. * * @return information about the glyph representing the * character */
protected CharacterInformation getCharacterInformation (List list,int startIndex, int charnum, AttributedCharacterIterator aci) { CharacterInformation info = new CharacterInformation(); info.characterIndex = startIndex+charnum; for (Object aList : list) { StrokingTextPainter.TextRun run; run = (StrokingTextPainter.TextRun) aList; if (!run.getLayout().hasCharacterIndex(info.characterIndex)) continue; info.layout = run.getLayout(); aci.setIndex(info.characterIndex); //check is it is a altGlyph if (aci.getAttribute(ALT_GLYPH_HANDLER) != null) { info.glyphIndexStart = 0; info.glyphIndexEnd = info.layout.getGlyphCount() - 1; } else { info.glyphIndexStart = info.layout.getGlyphIndex (info.characterIndex); //special case when the glyph does not have a unicode //associated to it, it will return -1 if (info.glyphIndexStart == -1) { info.glyphIndexStart = 0; info.glyphIndexEnd = info.layout.getGlyphCount() - 1; } else { info.glyphIndexEnd = info.glyphIndexStart; } } return info; } return null; }
Helper class to collect information about one Glyph in the GlyphVector
/** * Helper class to collect information about one Glyph * in the GlyphVector */
protected static class CharacterInformation{ ///layout associated to the Glyph TextSpanLayout layout; ///GlyphIndex in the vector int glyphIndexStart; int glyphIndexEnd; ///Character index in the ACI. int characterIndex; /// Indicates is the glyph is vertical public boolean isVertical(){ return layout.isVertical(); } /// Retrieve the orientation angle for the Glyph public double getComputedOrientationAngle(){ return layout.getComputedOrientationAngle(characterIndex); } } public Set getTextIntersectionSet(AffineTransform at, Rectangle2D rect) { Set elems = new HashSet(); TextNode tn = getTextNode(); List list = tn.getTextRuns(); if (list == null) return elems; for (Object aList : list) { StrokingTextPainter.TextRun run; run = (StrokingTextPainter.TextRun) aList; TextSpanLayout layout = run.getLayout(); AttributedCharacterIterator aci = run.getACI(); aci.first(); SoftReference sr; sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID); Element elem = (Element) sr.get(); if (elem == null) continue; if (elems.contains(elem)) continue; if (!isTextSensitive(elem)) continue; Rectangle2D glBounds = layout.getBounds2D(); if (glBounds != null) { glBounds = at.createTransformedShape(glBounds).getBounds2D(); if (!rect.intersects(glBounds)) { continue; } } GVTGlyphVector gv = layout.getGlyphVector(); for (int g = 0; g < gv.getNumGlyphs(); g++) { Shape gBounds = gv.getGlyphLogicalBounds(g); if (gBounds != null) { gBounds = at.createTransformedShape (gBounds).getBounds2D(); if (gBounds.intersects(rect)) { elems.add(elem); break; } } } } return elems; } public Set getTextEnclosureSet(AffineTransform at, Rectangle2D rect) { TextNode tn = getTextNode(); Set elems = new HashSet(); List list = tn.getTextRuns(); if (list == null) return elems; Set reject = new HashSet(); for (Object aList : list) { StrokingTextPainter.TextRun run; run = (StrokingTextPainter.TextRun) aList; TextSpanLayout layout = run.getLayout(); AttributedCharacterIterator aci = run.getACI(); aci.first(); SoftReference sr; sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID); Element elem = (Element) sr.get(); if (elem == null) continue; if (reject.contains(elem)) continue; if (!isTextSensitive(elem)) { reject.add(elem); continue; } Rectangle2D glBounds = layout.getBounds2D(); if (glBounds == null) { continue; } glBounds = at.createTransformedShape(glBounds).getBounds2D(); if (rect.contains(glBounds)) { elems.add(elem); } else { reject.add(elem); elems.remove(elem); } } return elems; } public static boolean getTextIntersection(BridgeContext ctx, Element elem, AffineTransform ati, Rectangle2D rect, boolean checkSensitivity) { SVGContext svgCtx = null; if (elem instanceof SVGOMElement) svgCtx = ((SVGOMElement)elem).getSVGContext(); if (svgCtx == null) return false; SVGTextElementBridge txtBridge = null; if (svgCtx instanceof SVGTextElementBridge) txtBridge = (SVGTextElementBridge)svgCtx; else if (svgCtx instanceof AbstractTextChildSVGContext) { AbstractTextChildSVGContext childCtx; childCtx = (AbstractTextChildSVGContext)svgCtx; txtBridge = childCtx.getTextBridge(); } if (txtBridge == null) return false; TextNode tn = txtBridge.getTextNode(); List list = tn.getTextRuns(); if (list == null) return false; Element txtElem = txtBridge.e; AffineTransform at = tn.getGlobalTransform(); at.preConcatenate(ati); Rectangle2D tnRect; tnRect = tn.getBounds(); tnRect = at.createTransformedShape(tnRect).getBounds2D(); if (!rect.intersects(tnRect)) return false; for (Object aList : list) { StrokingTextPainter.TextRun run; run = (StrokingTextPainter.TextRun) aList; TextSpanLayout layout = run.getLayout(); AttributedCharacterIterator aci = run.getACI(); aci.first(); SoftReference sr; sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID); Element runElem = (Element) sr.get(); if (runElem == null) continue; // Only consider runElem if it is sensitive. if (checkSensitivity && !isTextSensitive(runElem)) continue; Element p = runElem; while ((p != null) && (p != txtElem) && (p != elem)) { p = (Element) txtBridge.getParentNode(p); } if (p != elem) continue; // runElem is a child of elem so check it out. Rectangle2D glBounds = layout.getBounds2D(); if (glBounds == null) continue; glBounds = at.createTransformedShape(glBounds).getBounds2D(); if (!rect.intersects(glBounds)) continue; GVTGlyphVector gv = layout.getGlyphVector(); for (int g = 0; g < gv.getNumGlyphs(); g++) { Shape gBounds = gv.getGlyphLogicalBounds(g); if (gBounds != null) { gBounds = at.createTransformedShape (gBounds).getBounds2D(); if (gBounds.intersects(rect)) { return true; } } } } return false; } public static Rectangle2D getTextBounds(BridgeContext ctx, Element elem, boolean checkSensitivity) { SVGContext svgCtx = null; if (elem instanceof SVGOMElement) svgCtx = ((SVGOMElement)elem).getSVGContext(); if (svgCtx == null) return null; SVGTextElementBridge txtBridge = null; if (svgCtx instanceof SVGTextElementBridge) txtBridge = (SVGTextElementBridge)svgCtx; else if (svgCtx instanceof AbstractTextChildSVGContext) { AbstractTextChildSVGContext childCtx; childCtx = (AbstractTextChildSVGContext)svgCtx; txtBridge = childCtx.getTextBridge(); } if (txtBridge == null) return null; TextNode tn = txtBridge.getTextNode(); List list = tn.getTextRuns(); if (list == null) return null; Element txtElem = txtBridge.e; Rectangle2D ret = null; for (Object aList : list) { StrokingTextPainter.TextRun run; run = (StrokingTextPainter.TextRun) aList; TextSpanLayout layout = run.getLayout(); AttributedCharacterIterator aci = run.getACI(); aci.first(); SoftReference sr; sr = (SoftReference) aci.getAttribute(TEXT_COMPOUND_ID); Element runElem = (Element) sr.get(); if (runElem == null) continue; // Only consider runElem if it is sensitive. if (checkSensitivity && !isTextSensitive(runElem)) continue; Element p = runElem; while ((p != null) && (p != txtElem) && (p != elem)) { p = (Element) txtBridge.getParentNode(p); } if (p != elem) continue; // runElem is a child of elem so include it's bounds. Rectangle2D glBounds = layout.getBounds2D(); if (glBounds != null) { if (ret == null) ret = (Rectangle2D) glBounds.clone(); else ret.add(glBounds); } } return ret; } public static boolean isTextSensitive(Element e) { int ptrEvts = CSSUtilities.convertPointerEvents(e); switch (ptrEvts) { case GraphicsNode.VISIBLE_PAINTED: // fall-through is intended case GraphicsNode.VISIBLE_FILL: case GraphicsNode.VISIBLE_STROKE: case GraphicsNode.VISIBLE: return CSSUtilities.convertVisibility(e); case GraphicsNode.PAINTED: case GraphicsNode.FILL: // fall-through is intended case GraphicsNode.STROKE: case GraphicsNode.ALL: return true; case GraphicsNode.NONE: default: return false; } } }