protected boolean isTextChild(Element e) {
   if (!SVG_NAMESPACE_URI.equals(e.getNamespaceURI())) return false;
   String nodeName = e.getLocalName();
   return (nodeName.equals(SVG12Constants.SVG_A_TAG)
       || nodeName.equals(SVG12Constants.SVG_FLOW_LINE_TAG)
       || nodeName.equals(SVG12Constants.SVG_FLOW_PARA_TAG)
       || nodeName.equals(SVG12Constants.SVG_FLOW_REGION_BREAK_TAG)
       || nodeName.equals(SVG12Constants.SVG_FLOW_SPAN_TAG));
 }
  /**
   * 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())) {
      String ln = e.getLocalName();
      if (ln.equals(SVG12Constants.SVG_FLOW_DIV_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_LINE_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_PARA_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_SPAN_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);
    }
  }
  /**
   * Add to the element children of the node, a <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())) {
      String ln = e.getLocalName();
      if (ln.equals(SVG12Constants.SVG_FLOW_DIV_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_LINE_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_PARA_TAG)
          || ln.equals(SVG12Constants.SVG_FLOW_SPAN_TAG)) {
        ((SVGOMElement) e).setSVGContext(new FlowContentBridge(ctx, this, e));
      }
    }

    // traverse the children to add SVGContext
    Node child = getFirstChild(e);
    while (child != null) {
      if (child.getNodeType() == Node.ELEMENT_NODE) {
        addContextToChild(ctx, (Element) child);
      }
      child = getNextSibling(child);
    }
  }
  protected Element getFlowDivElement(Element elem) {
    String eNS = elem.getNamespaceURI();
    if (!eNS.equals(SVG_NAMESPACE_URI)) return null;

    String nodeName = elem.getLocalName();
    if (nodeName.equals(SVG12Constants.SVG_FLOW_DIV_TAG)) return elem;

    if (!nodeName.equals(SVG12Constants.SVG_FLOW_ROOT_TAG)) return null;

    for (Node n = getFirstChild(elem); n != null; n = getNextSibling(n)) {
      if (n.getNodeType() != Node.ELEMENT_NODE) continue;

      String nNS = n.getNamespaceURI();
      if (!SVG_NAMESPACE_URI.equals(nNS)) continue;

      Element e = (Element) n;
      String ln = e.getLocalName();
      if (ln.equals(SVG12Constants.SVG_FLOW_DIV_TAG)) return e;
    }
    return null;
  }
  /** Fills the given AttributedStringBuffer. */
  protected void fillAttributedStringBuffer(
      BridgeContext ctx,
      Element element,
      boolean top,
      Integer bidiLevel,
      Map initialAttributes,
      AttributedStringBuffer asb,
      List lnLocs) {
    // '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 = startLen = asb.length();
    }
    if (preserve) {
      endLimit = startLen;
    }

    Map map = initialAttributes == null ? new HashMap() : new HashMap(initialAttributes);
    initialAttributes = getAttributeMap(ctx, element, null, bidiLevel, map);
    Object o = map.get(TextAttribute.BIDI_EMBEDDING);
    Integer subBidiLevel = bidiLevel;
    if (o != null) {
      subBidiLevel = (Integer) o;
    }

    int lineBreak = -1;
    if (lnLocs.size() != 0) {
      lineBreak = ((Integer) lnLocs.get(lnLocs.size() - 1)).intValue();
    }

    for (Node n = getFirstChild(element); n != null; n = getNextSibling(n)) {

      if (preserve) {
        prevEndsWithSpace = false;
      } else {
        int len = asb.length();
        if (len == startLen) {
          prevEndsWithSpace = true;
        } else {
          prevEndsWithSpace = (asb.getLastChar() == ' ');
          int idx = lnLocs.size() - 1;
          if (!prevEndsWithSpace && (idx >= 0)) {
            Integer i = (Integer) lnLocs.get(idx);
            if (i.intValue() == len) {
              prevEndsWithSpace = true;
            }
          }
        }
      }

      switch (n.getNodeType()) {
        case Node.ELEMENT_NODE:
          // System.out.println("Element: " + n);
          if (!SVG_NAMESPACE_URI.equals(n.getNamespaceURI())) break;

          nodeElement = (Element) n;

          String ln = n.getLocalName();

          if (ln.equals(SVG12Constants.SVG_FLOW_LINE_TAG)) {
            int before = asb.length();
            fillAttributedStringBuffer(
                ctx, nodeElement, false, subBidiLevel, initialAttributes, asb, lnLocs);
            // System.out.println("Line: " + asb.length() +
            //                    " - '" +  asb + "'");
            lineBreak = asb.length();
            lnLocs.add(new Integer(lineBreak));
            if (before != lineBreak) {
              initialAttributes = null;
            }
          } else if (ln.equals(SVG12Constants.SVG_FLOW_SPAN_TAG)
              || ln.equals(SVG12Constants.SVG_ALT_GLYPH_TAG)) {
            int before = asb.length();
            fillAttributedStringBuffer(
                ctx, nodeElement, false, subBidiLevel, initialAttributes, asb, lnLocs);
            if (asb.length() != before) {
              initialAttributes = null;
            }
          } else if (ln.equals(SVG_A_TAG)) {
            if (ctx.isInteractive()) {
              NodeEventTarget target = (NodeEventTarget) nodeElement;
              UserAgent ua = ctx.getUserAgent();
              SVGAElementBridge.CursorHolder ch;
              ch = new SVGAElementBridge.CursorHolder(CursorManager.DEFAULT_CURSOR);
              target.addEventListenerNS(
                  XMLConstants.XML_EVENTS_NAMESPACE_URI,
                  SVG_EVENT_CLICK,
                  new SVGAElementBridge.AnchorListener(ua, ch),
                  false,
                  null);

              target.addEventListenerNS(
                  XMLConstants.XML_EVENTS_NAMESPACE_URI,
                  SVG_EVENT_MOUSEOVER,
                  new SVGAElementBridge.CursorMouseOverListener(ua, ch),
                  false,
                  null);

              target.addEventListenerNS(
                  XMLConstants.XML_EVENTS_NAMESPACE_URI,
                  SVG_EVENT_MOUSEOUT,
                  new SVGAElementBridge.CursorMouseOutListener(ua, ch),
                  false,
                  null);
            }
            int before = asb.length();
            fillAttributedStringBuffer(
                ctx, nodeElement, false, subBidiLevel, initialAttributes, asb, lnLocs);
            if (asb.length() != 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 = new HashMap();
              getAttributeMap(ctx, nodeElement, null, bidiLevel, m);
              asb.append(s, m);
              int trefEnd = asb.length() - 1;
              TextPaintInfo tpi;
              tpi = (TextPaintInfo) elemTPI.get(nodeElement);
              tpi.startChar = trefStart;
              tpi.endChar = trefEnd;
            }
          }
          break;

        case Node.TEXT_NODE:
        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() == ' ')) {
        int idx = lnLocs.size() - 1;
        int len = asb.length();
        if (idx >= 0) {
          Integer i = (Integer) lnLocs.get(idx);
          if (i.intValue() >= len) {
            i = new Integer(len - 1);
            lnLocs.set(idx, i);
            idx--;
            while (idx >= 0) {
              i = (Integer) lnLocs.get(idx);
              if (i.intValue() < len - 1) break;
              lnLocs.remove(idx);
              idx--;
            }
          }
        }
        asb.stripLast();
        strippedSome = true;
      }
      if (strippedSome) {
        Iterator iter = elemTPI.values().iterator();
        while (iter.hasNext()) {
          TextPaintInfo tpi = (TextPaintInfo) iter.next();
          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;
  }