/* * 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. */ /* $Id$ */ package org.apache.fop.render.xml; // Java import java.awt.Color; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.xml.sax.SAXException; import org.apache.xmlgraphics.util.QName; import org.apache.xmlgraphics.util.XMLizable; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.Area; import org.apache.fop.area.AreaTreeObject; import org.apache.fop.area.BeforeFloat; import org.apache.fop.area.Block; import org.apache.fop.area.BlockViewport; import org.apache.fop.area.BodyRegion; import org.apache.fop.area.BookmarkData; import org.apache.fop.area.CTM; import org.apache.fop.area.DestinationData; import org.apache.fop.area.Footnote; import org.apache.fop.area.LineArea; import org.apache.fop.area.MainReference; import org.apache.fop.area.NormalFlow; import org.apache.fop.area.OffDocumentExtensionAttachment; import org.apache.fop.area.OffDocumentItem; import org.apache.fop.area.PageSequence; import org.apache.fop.area.PageViewport; import org.apache.fop.area.RegionReference; import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Span; import org.apache.fop.area.Trait; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; import org.apache.fop.area.inline.Container; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.InlineBlockParent; import org.apache.fop.area.inline.InlineParent; import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.XMLHandler; import org.apache.fop.util.ColorUtil; import org.apache.fop.util.LanguageTags; import org.apache.fop.util.XMLUtil; /** * Renderer that renders areas to XML for debugging purposes. * This creates an xml that contains the information of the area * tree. It does not output any state or derived information. * The output can be used to build a new area tree which can be * rendered to any renderer. */ public class XMLRenderer extends AbstractXMLRenderer { /** * Area Tree (AT) version, used to express an @version attribute * in the root element of the AT document, the initial value of which * is set to '2.0' to signify that something preceded it (but didn't * happen to be marked as such), and that this version is not necessarily * backwards compatible with the unmarked (<2.0) version. */ public static final String VERSION = "2.0"; /** XML MIME type */ public static final String XML_MIME_TYPE = MimeConstants.MIME_FOP_AREA_TREE; private boolean startedSequence; private boolean compactFormat; /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */ protected Renderer mimic; /** * @param userAgent the user agent that contains configuration details. This cannot be null. */ public XMLRenderer(FOUserAgent userAgent) { super(userAgent); context = new RendererContext(this, XML_MIME_TYPE); XMLHandler xmlHandler = new XMLXMLHandler(); userAgent.getXMLHandlerRegistry().addXMLHandler(xmlHandler); Boolean b = (Boolean)userAgent.getRendererOptions().get("compact-format"); if (b != null) { setCompactFormat(b); } } /** * Call this method to make the XMLRenderer mimic a different renderer by using its font * setup. This is useful when working with the intermediate format parser. * @param renderer the renderer to mimic */ public void mimicRenderer(Renderer renderer) { this.mimic = renderer; } /** {@inheritDoc} */ @Override public void setupFontInfo(FontInfo inFontInfo) throws FOPException { if (mimic != null) { mimic.setupFontInfo(inFontInfo); } else { super.setupFontInfo(inFontInfo); } } /** * Controls whether to create a more compact format which omit certain attributes. * @param compact true to activate the compact format */ public void setCompactFormat(boolean compact) { this.compactFormat = compact; } private boolean isDetailedFormat() { return !this.compactFormat; } /** * Adds the general Area attributes. * @param area Area to extract attributes from */ protected void addAreaAttributes(Area area) { addAttribute("ipd", area.getIPD()); addAttribute("bpd", area.getBPD()); maybeAddLevelAttribute(area); if (isDetailedFormat()) { if (area.getIPD() != 0) { addAttribute("ipda", area.getAllocIPD()); } if (area.getBPD() != 0) { addAttribute("bpda", area.getAllocBPD()); } addAttribute("bap", area.getBorderAndPaddingWidthStart() + " " + area.getBorderAndPaddingWidthEnd() + " " + area.getBorderAndPaddingWidthBefore() + " " + area.getBorderAndPaddingWidthAfter()); } } /** * Adds attributes from traits of an Area. * @param area Area to extract traits from */ protected void addTraitAttributes(Area area) { Map traitMap = area.getTraits(); if (traitMap != null) { for (Object o : traitMap.entrySet()) { Map.Entry traitEntry = (Map.Entry) o; Object key = traitEntry.getKey(); String name = Trait.getTraitName(key); Class clazz = Trait.getTraitClass(key); if ("break-before".equals(name) || "break-after".equals(name)) { continue; } Object value = traitEntry.getValue(); if (((Integer) key).intValue() == Trait.FONT) { FontTriplet triplet = (FontTriplet) value; addAttribute("font-name", triplet.getName()); addAttribute("font-style", triplet.getStyle()); addAttribute("font-weight", triplet.getWeight()); } else if (clazz.equals(InternalLink.class)) { InternalLink iLink = (InternalLink) value; addAttribute(name, iLink.xmlAttribute()); } else if (clazz.equals(Background.class)) { Background bkg = (Background) value; //TODO Remove the following line (makes changes in the test checks necessary) addAttribute(name, bkg.toString()); if (bkg.getColor() != null) { addAttribute("bkg-color", ColorUtil.colorToString(bkg.getColor())); } if (bkg.getURL() != null) { addAttribute("bkg-img", bkg.getURL()); String repString; int repeat = bkg.getRepeat(); switch (repeat) { case Constants.EN_REPEAT: repString = "repeat"; break; case Constants.EN_REPEATX: repString = "repeat-x"; break; case Constants.EN_REPEATY: repString = "repeat-y"; break; case Constants.EN_NOREPEAT: repString = "no-repeat"; break; default: throw new IllegalStateException( "Illegal value for repeat encountered: " + repeat); } addAttribute("bkg-repeat", repString); addAttribute("bkg-horz-offset", bkg.getHoriz()); addAttribute("bkg-vert-offset", bkg.getVertical()); } } else if (clazz.equals(Color.class)) { Color c = (Color) value; addAttribute(name, ColorUtil.colorToString(c)); } else if (((Integer) key).intValue() == Trait.START_INDENT || ((Integer) key).intValue() == Trait.END_INDENT) { if ((Integer) value != 0) { addAttribute(name, value.toString()); } } else { addAttribute(name, value.toString()); } } } transferForeignObjects(area); } private void transferForeignObjects(AreaTreeObject ato) { Map prefixes = new java.util.HashMap(); Iterator iter = ato.getForeignAttributes().entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); QName qname = (QName)entry.getKey(); prefixes.put(qname.getPrefix(), qname.getNamespaceURI()); addAttribute(qname, (String)entry.getValue()); } //Namespace declarations iter = prefixes.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); String qn = "xmlns:" + (String)entry.getKey(); atts.addAttribute("", (String)entry.getKey(), qn, CDATA, (String)entry.getValue()); } } /** {@inheritDoc} */ @Override public void processOffDocumentItem(OffDocumentItem oDI) { if (oDI instanceof BookmarkData) { renderBookmarkTree((BookmarkData) oDI); } else if (oDI instanceof DestinationData) { renderDestination((DestinationData) oDI); } else if (oDI instanceof OffDocumentExtensionAttachment) { ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment(); if (extensionAttachments == null) { extensionAttachments = new java.util.ArrayList(); } extensionAttachments.add(attachment); } else { String warn = "Ignoring OffDocumentItem: " + oDI; log.warn(warn); } } /** * Renders a BookmarkTree object * @param bookmarkRoot the BookmarkData object representing the top of the tree */ @Override protected void renderBookmarkTree(BookmarkData bookmarkRoot) { if (bookmarkRoot.getWhenToProcess() == OffDocumentItem.END_OF_DOC) { endPageSequence(); } /* If this kind of handling is also necessary for other renderers, then better add endPageSequence to the Renderer interface and call it explicitly from model.endDocument() */ startElement("bookmarkTree"); for (int i = 0; i < bookmarkRoot.getCount(); i++) { renderBookmarkItem(bookmarkRoot.getSubData(i)); } endElement("bookmarkTree"); } private void renderBookmarkItem(BookmarkData bm) { atts.clear(); addAttribute("title", bm.getBookmarkTitle()); addAttribute("show-children", String.valueOf(bm.showChildItems())); PageViewport pv = bm.getPageViewport(); String pvKey = pv == null ? null : pv.getKey(); addAttribute("internal-link", InternalLink.makeXMLAttribute(pvKey, bm.getIDRef())); startElement("bookmark", atts); for (int i = 0; i < bm.getCount(); i++) { renderBookmarkItem(bm.getSubData(i)); } endElement("bookmark"); } /** * Renders a DestinationData object (named destination) * @param destination the destination object */ protected void renderDestination(DestinationData destination) { if (destination.getWhenToProcess() == OffDocumentItem.END_OF_DOC) { endPageSequence(); } atts.clear(); PageViewport pv = destination.getPageViewport(); String pvKey = pv == null ? null : pv.getKey(); addAttribute("internal-link", InternalLink.makeXMLAttribute(pvKey, destination.getIDRef())); startElement("destination", atts); endElement("destination"); } /** {@inheritDoc} */ @Override public void startRenderer(OutputStream outputStream) throws IOException { log.debug("Rendering areas to Area Tree XML"); if (this.handler == null) { SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); try { TransformerHandler transformerHandler = factory.newTransformerHandler(); this.handler = transformerHandler; StreamResult res = new StreamResult(outputStream); transformerHandler.setResult(res); } catch (TransformerConfigurationException tce) { throw new RuntimeException(tce.getMessage()); } this.out = outputStream; } try { handler.startDocument(); } catch (SAXException saxe) { handleSAXException(saxe); } if (userAgent.getProducer() != null) { comment("Produced by " + userAgent.getProducer()); } atts.clear(); addAttribute("version", VERSION); startElement("areaTree", atts); } /** {@inheritDoc} */ @Override public void stopRenderer() throws IOException { endPageSequence(); endElement("areaTree"); try { handler.endDocument(); } catch (SAXException saxe) { handleSAXException(saxe); } if (this.out != null) { this.out.flush(); } log.debug("Written out Area Tree XML"); } /** {@inheritDoc} */ @Override public void renderPage(PageViewport page) throws IOException, FOPException { atts.clear(); addAttribute("bounds", page.getViewArea()); addAttribute("key", page.getKey()); addAttribute("nr", page.getPageNumber()); addAttribute("formatted-nr", page.getPageNumberString()); if (page.getSimplePageMasterName() != null) { addAttribute("simple-page-master-name", page.getSimplePageMasterName()); } if (page.isBlank()) { addAttribute("blank", "true"); } transferForeignObjects(page); startElement("pageViewport", atts); startElement("page"); handlePageExtensionAttachments(page); super.renderPage(page); endElement("page"); endElement("pageViewport"); } /** {@inheritDoc} */ @Override protected void handleExtensionAttachments(List attachments) { if (attachments != null && attachments.size() > 0) { startElement("extension-attachments"); for (Object attachment1 : attachments) { ExtensionAttachment attachment = (ExtensionAttachment) attachment1; if (attachment instanceof XMLizable) { try { ((XMLizable) attachment).toSAX(this.handler); } catch (SAXException e) { log.error("Error while serializing Extension Attachment", e); } } else { String warn = "Ignoring non-XMLizable ExtensionAttachment: " + attachment; log.warn(warn); } } endElement("extension-attachments"); } } /** {@inheritDoc} */ @Override public void startPageSequence(PageSequence pageSequence) { handleDocumentExtensionAttachments(); endPageSequence(); // move this before handleDocumentExtensionAttachments() ? startedSequence = true; atts.clear(); Locale locale = pageSequence.getLocale(); if (locale != null) { addAttribute(new QName(XMLConstants.XML_NS_URI, "xml:lang"), LanguageTags.toLanguageTag(locale)); } transferForeignObjects(pageSequence); startElement("pageSequence", atts); handleExtensionAttachments(pageSequence.getExtensionAttachments()); LineArea seqTitle = pageSequence.getTitle(); if (seqTitle != null) { startElement("title"); List children = seqTitle.getInlineAreas(); for (Object aChildren : children) { InlineArea inline = (InlineArea) aChildren; renderInlineArea(inline); } endElement("title"); } } /** * Tells the renderer to finish the current PageSequence */ public void endPageSequence() { if (startedSequence) { endElement("pageSequence"); } startedSequence = false; } /** * {@inheritDoc} */ @Override protected void renderRegionViewport(RegionViewport port) { if (port != null) { atts.clear(); addAreaAttributes(port); addTraitAttributes(port); addAttribute("rect", port.getViewArea()); if (port.hasClip()) { addAttribute("clipped", "true"); } startElement("regionViewport", atts); RegionReference region = port.getRegionReference(); atts.clear(); addAreaAttributes(region); addTraitAttributes(region); addAttribute("name", region.getRegionName()); addAttribute("ctm", region.getCTM().toString()); if (region.getRegionClass() == FO_REGION_BEFORE) { startElement("regionBefore", atts); renderRegion(region); endElement("regionBefore"); } else if (region.getRegionClass() == FO_REGION_START) { startElement("regionStart", atts); renderRegion(region); endElement("regionStart"); } else if (region.getRegionClass() == FO_REGION_BODY) { assert (region instanceof BodyRegion); BodyRegion body = (BodyRegion) region; if (body.getColumnCount() != 1) { addAttribute("columnGap", body.getColumnGap()); addAttribute("columnCount", body.getColumnCount()); } startElement("regionBody", atts); renderBodyRegion(body); endElement("regionBody"); } else if (region.getRegionClass() == FO_REGION_END) { startElement("regionEnd", atts); renderRegion(region); endElement("regionEnd"); } else if (region.getRegionClass() == FO_REGION_AFTER) { startElement("regionAfter", atts); renderRegion(region); endElement("regionAfter"); } endElement("regionViewport"); } } @Override protected void startVParea(CTM ctm, Rectangle clippingRect) { //only necessary for graphical output } /** {@inheritDoc} */ @Override protected void endVParea() { //only necessary for graphical output } /** {@inheritDoc} */ protected void startLayer(String layer) { //only necessary for graphical output } /** {@inheritDoc} */ protected void endLayer() { //only necessary for graphical output } /** * {@inheritDoc} * org.apache.fop.area.inline.InlineArea) */ @Override protected void renderInlineAreaBackAndBorders(InlineArea area) { //only necessary for graphical output } /** * {@inheritDoc} */ @Override protected void renderBeforeFloat(BeforeFloat bf) { startElement("beforeFloat"); super.renderBeforeFloat(bf); endElement("beforeFloat"); } /** * {@inheritDoc} */ @Override protected void renderFootnote(Footnote footnote) { atts.clear(); addAttribute("top-offset", footnote.getTop()); startElement("footnote", atts); super.renderFootnote(footnote); endElement("footnote"); } /** * {@inheritDoc} */ @Override protected void renderMainReference(MainReference mr) { atts.clear(); addAreaAttributes(mr); addTraitAttributes(mr); if (mr.getColumnCount() != 1) { addAttribute("columnGap", mr.getColumnGap()); } startElement("mainReference", atts); Span span = null; List spans = mr.getSpans(); for (Object span1 : spans) { span = (Span) span1; atts.clear(); if (span.getColumnCount() != 1) { addAttribute("columnCount", span.getColumnCount()); } addAreaAttributes(span); addTraitAttributes(span); startElement("span", atts); for (int c = 0; c < span.getColumnCount(); c++) { NormalFlow flow = span.getNormalFlow(c); renderFlow(flow); } endElement("span"); } endElement("mainReference"); } /** * {@inheritDoc} */ @Override protected void renderFlow(NormalFlow flow) { // the normal flow reference area contains stacked blocks atts.clear(); addAreaAttributes(flow); addTraitAttributes(flow); startElement("flow", atts); super.renderFlow(flow); endElement("flow"); } /** {@inheritDoc} */ @Override protected void renderReferenceArea(Block block) { handleBlockTraits(block); List children = block.getChildAreas(); if (children != null) { renderBlocks(block, children); } } /** {@inheritDoc} */ @Override protected void renderBlock(Block block) { atts.clear(); addAreaAttributes(block); addTraitAttributes(block); int positioning = block.getPositioning(); if (block instanceof BlockViewport) { BlockViewport bvp = (BlockViewport)block; boolean abspos = false; if (bvp.getPositioning() == Block.ABSOLUTE || bvp.getPositioning() == Block.FIXED) { abspos = true; } if (abspos) { addAttribute("left-position", bvp.getXOffset()); addAttribute("top-position", bvp.getYOffset()); } addAttribute("ctm", bvp.getCTM().toString()); if (bvp.hasClip()) { addAttribute("clipped", "true"); } } else { if (block.getXOffset() != 0) { addAttribute("left-offset", block.getXOffset()); } if (block.getYOffset() != 0) { addAttribute("top-offset", block.getYOffset()); } } switch (positioning) { case Block.RELATIVE: addAttribute("positioning", "relative"); break; case Block.ABSOLUTE: addAttribute("positioning", "absolute"); break; case Block.FIXED: addAttribute("positioning", "fixed"); break; default: //nop } startElement("block", atts); super.renderBlock(block); endElement("block"); } /** * {@inheritDoc} */ @Override protected void renderLineArea(LineArea line) { atts.clear(); addAreaAttributes(line); addTraitAttributes(line); startElement("lineArea", atts); super.renderLineArea(line); endElement("lineArea"); } /** * {@inheritDoc} */ @Override protected void renderInlineArea(InlineArea inlineArea) { atts.clear(); if (inlineArea.getClass() == InlineArea.class) { // Generic inline area. This is implemented to allow the 0x0 "dummy" // area generated by fo:wrapper to pass its id. addAreaAttributes(inlineArea); addTraitAttributes(inlineArea); startElement("inline", atts); endElement("inline"); } else { super.renderInlineArea(inlineArea); // calls specific renderers for Text, Space, Viewport, etc. etc. } } /** * {@inheritDoc} */ @Override protected void renderInlineViewport(InlineViewport viewport) { atts.clear(); addAreaAttributes(viewport); addTraitAttributes(viewport); addAttribute("offset", viewport.getBlockProgressionOffset()); addAttribute("pos", viewport.getContentPosition()); if (viewport.hasClip()) { addAttribute("clip", "true"); } startElement("viewport", atts); super.renderInlineViewport(viewport); endElement("viewport"); } /** * {@inheritDoc} */ @Override public void renderImage(Image image, Rectangle2D pos) { atts.clear(); addAreaAttributes(image); addTraitAttributes(image); addAttribute("url", image.getURL()); //addAttribute("pos", pos); startElement("image", atts); endElement("image"); } /** * {@inheritDoc} */ @Override public void renderContainer(Container cont) { startElement("container"); super.renderContainer(cont); endElement("container"); } /** * Renders an fo:foreing-object. * @param fo the foreign object * @param pos the position of the foreign object * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, Rectangle2D) */ @Override public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { atts.clear(); addAreaAttributes(fo); addTraitAttributes(fo); String ns = fo.getNameSpace(); addAttribute("ns", ns); startElement("foreignObject", atts); Document doc = fo.getDocument(); context.setProperty(XMLXMLHandler.HANDLER, handler); renderXML(context, doc, ns); endElement("foreignObject"); } /** * {@inheritDoc} */ @Override protected void renderInlineSpace(Space space) { atts.clear(); addAreaAttributes(space); addTraitAttributes(space); addAttribute("offset", space.getBlockProgressionOffset()); startElement("space", atts); endElement("space"); } /** * {@inheritDoc} */ @Override protected void renderText(TextArea text) { atts.clear(); if (text.getTextWordSpaceAdjust() != 0) { addAttribute("twsadjust", text.getTextWordSpaceAdjust()); } if (text.getTextLetterSpaceAdjust() != 0) { addAttribute("tlsadjust", text.getTextLetterSpaceAdjust()); } addAttribute("offset", text.getBlockProgressionOffset()); addAttribute("baseline", text.getBaselineOffset()); addAreaAttributes(text); addTraitAttributes(text); startElement("text", atts); super.renderText(text); endElement("text"); } /** * {@inheritDoc} */ @Override protected void renderWord(WordArea word) { atts.clear(); int offset = word.getBlockProgressionOffset(); if (offset != 0) { addAttribute("offset", offset); } int[] letterAdjust = word.getLetterAdjustArray(); if (letterAdjust != null) { StringBuffer sb = new StringBuffer(64); boolean nonZeroFound = false; for (int i = 0, c = letterAdjust.length; i < c; i++) { if (i > 0) { sb.append(' '); } sb.append(letterAdjust[i]); nonZeroFound |= (letterAdjust[i] != 0); } if (nonZeroFound) { addAttribute("letter-adjust", sb.toString()); } } maybeAddLevelAttribute(word); maybeAddPositionAdjustAttribute(word); String text = word.getWord(); maybeAddReversedAttribute(word, text); startElement("word", atts); characters(text); endElement("word"); super.renderWord(word); } /** * {@inheritDoc} */ @Override protected void renderSpace(SpaceArea space) { atts.clear(); int offset = space.getBlockProgressionOffset(); if (offset != 0) { addAttribute("offset", offset); } maybeAddLevelAttribute(space); if (!space.isAdjustable()) { addAttribute("adj", "false"); //default is true } startElement("space", atts); characters(space.getSpace()); endElement("space"); super.renderSpace(space); } /** * {@inheritDoc} */ @Override protected void renderInlineParent(InlineParent ip) { atts.clear(); addAreaAttributes(ip); addTraitAttributes(ip); addAttribute("offset", ip.getBlockProgressionOffset()); startElement("inlineparent", atts); super.renderInlineParent(ip); endElement("inlineparent"); } /** * {@inheritDoc} */ @Override protected void renderInlineBlockParent(InlineBlockParent ibp) { atts.clear(); addAreaAttributes(ibp); addTraitAttributes(ibp); addAttribute("offset", ibp.getBlockProgressionOffset()); startElement("inlineblockparent", atts); super.renderInlineBlockParent(ibp); endElement("inlineblockparent"); } /** * {@inheritDoc} */ @Override protected void renderLeader(Leader area) { atts.clear(); addAreaAttributes(area); addTraitAttributes(area); addAttribute("offset", area.getBlockProgressionOffset()); addAttribute("ruleStyle", area.getRuleStyleAsString()); addAttribute("ruleThickness", area.getRuleThickness()); startElement("leader", atts); endElement("leader"); super.renderLeader(area); } /** {@inheritDoc} */ public String getMimeType() { return XML_MIME_TYPE; } private void maybeAddLevelAttribute(Area a) { int level = a.getBidiLevel(); if (level >= 0) { addAttribute("level", level); } } private void maybeAddPositionAdjustAttribute(WordArea w) { int[][] adjustments = w.getGlyphPositionAdjustments(); if (adjustments != null) { addAttribute("position-adjust", XMLUtil.encodePositionAdjustments(adjustments)); } } private void maybeAddReversedAttribute(WordArea w, String text) { if (w.isReversed() && (text.length() > 1)) { addAttribute("reversed", "true"); } } }