| /* |
| * 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; |
| |
| // Java |
| import java.awt.Rectangle; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.w3c.dom.Document; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fop.ResourceEventProducer; |
| import org.apache.fop.apps.FOPException; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.area.Area; |
| import org.apache.fop.area.BeforeFloat; |
| import org.apache.fop.area.Block; |
| import org.apache.fop.area.BlockParent; |
| import org.apache.fop.area.BlockViewport; |
| import org.apache.fop.area.BodyRegion; |
| import org.apache.fop.area.CTM; |
| 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.OffDocumentItem; |
| import org.apache.fop.area.Page; |
| 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.inline.Container; |
| import org.apache.fop.area.inline.FilledArea; |
| 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.InlineBlock; |
| 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.flow.ChangeBar; |
| import org.apache.fop.fo.properties.Property; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.traits.BorderProps; |
| import org.apache.fop.traits.Direction; |
| import org.apache.fop.traits.Visibility; |
| |
| /** |
| * Abstract base class for all renderers. The Abstract renderer does all the |
| * top level processing of the area tree and adds some abstract methods to |
| * handle viewports. This keeps track of the current block and inline position. |
| */ |
| public abstract class AbstractRenderer |
| implements Renderer, Constants { |
| |
| /** logging instance */ |
| protected static final Log log = LogFactory.getLog("org.apache.fop.render"); |
| |
| /** |
| * user agent |
| */ |
| protected FOUserAgent userAgent; |
| |
| /** |
| * block progression position |
| */ |
| protected int currentBPPosition; |
| |
| /** |
| * inline progression position |
| */ |
| protected int currentIPPosition; |
| |
| /** |
| * the block progression position of the containing block used for |
| * absolutely positioned blocks |
| */ |
| protected int containingBPPosition; |
| |
| /** |
| * the inline progression position of the containing block used for |
| * absolutely positioned blocks |
| */ |
| protected int containingIPPosition; |
| |
| /** |
| * The "start edge" IP Position of the current column (for change bars) |
| */ |
| protected int columnStartIPPosition; |
| |
| /** |
| * The "end edge" IP Position of the current column (for change bars) |
| */ |
| protected int columnEndIPPosition; |
| |
| /** |
| * The "left" position of the current column (for change bars) |
| */ |
| protected int columnLeftIPPosition; |
| |
| /** |
| * The "right" position of the current column (for change bars) |
| */ |
| protected int columnRightIPPosition; |
| |
| /** |
| * The number of columns in the span (for change bars) |
| */ |
| protected int columnCount; |
| |
| /** |
| * The index number of the current column (for change bars) |
| */ |
| protected int columnIndex; |
| |
| /** |
| * The column width (for change bars) |
| */ |
| protected int columnWidth; |
| |
| /** |
| * The size of column gap (for change bars) |
| */ |
| protected int columnGap; |
| |
| /** |
| * The block progression direction (for change bars) |
| */ |
| protected Direction blockProgressionDirection; |
| |
| /** |
| * The inline progression direction (for change bars) |
| */ |
| protected Direction inlineProgressionDirection; |
| |
| /** |
| * Is binding on start edge of column? |
| */ |
| protected boolean bindingOnStartEdge; |
| |
| /** |
| * Is binding on end edge of column? |
| */ |
| protected boolean bindingOnEndEdge; |
| |
| /** |
| * The IP begin offset of coordinate 0 |
| */ |
| private int beginOffset; |
| |
| /** |
| * the currently active PageViewport |
| */ |
| protected PageViewport currentPageViewport; |
| |
| /* warned XML handlers */ |
| private Set warnedXMLHandlers; |
| |
| /* layers stack */ |
| private Stack<String> layers; |
| |
| /** {@inheritDoc} */ |
| public abstract void setupFontInfo(FontInfo fontInfo) throws FOPException; |
| |
| /** |
| * |
| * @param userAgent the user agent that contains configuration details. This cannot be null. |
| */ |
| public AbstractRenderer(FOUserAgent userAgent) { |
| this.userAgent = userAgent; |
| } |
| |
| /** {@inheritDoc} */ |
| public FOUserAgent getUserAgent() { |
| return userAgent; |
| } |
| |
| /** {@inheritDoc} */ |
| public void startRenderer(OutputStream outputStream) |
| throws IOException { |
| if (userAgent == null) { |
| throw new IllegalStateException("FOUserAgent has not been set on Renderer"); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void stopRenderer() |
| throws IOException { } |
| |
| /** |
| * Check if this renderer supports out of order rendering. If this renderer |
| * supports out of order rendering then it means that the pages that are |
| * not ready will be prepared and a future page will be rendered. |
| * |
| * @return True if the renderer supports out of order rendering |
| */ |
| public boolean supportsOutOfOrder() { |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| public void setDocumentLocale(Locale locale) { |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void processOffDocumentItem(OffDocumentItem odi) { } |
| |
| /** {@inheritDoc} */ |
| public Graphics2DAdapter getGraphics2DAdapter() { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| public ImageAdapter getImageAdapter() { |
| return null; |
| } |
| |
| /** @return the current PageViewport or null, if none is active */ |
| protected PageViewport getCurrentPageViewport() { |
| return this.currentPageViewport; |
| } |
| |
| /** {@inheritDoc} */ |
| public void preparePage(PageViewport page) { } |
| |
| /** |
| * Utility method to convert a page sequence title to a string. Some |
| * renderers may only be able to use a string title. A title is a sequence |
| * of inline areas that this method attempts to convert to an equivalent |
| * string. |
| * |
| * @param title The Title to convert |
| * @return An expanded string representing the title |
| */ |
| protected String convertTitleToString(LineArea title) { |
| List children = title.getInlineAreas(); |
| String str = convertToString(children); |
| return str.trim(); |
| } |
| |
| private String convertToString(List children) { |
| StringBuffer sb = new StringBuffer(); |
| for (Object aChildren : children) { |
| InlineArea inline = (InlineArea) aChildren; |
| //if (inline instanceof Character) { |
| // sb.append(((Character) inline).getChar()); |
| /*} else*/ |
| if (inline instanceof TextArea) { |
| sb.append(((TextArea) inline).getText()); |
| } else if (inline instanceof InlineParent) { |
| sb.append(convertToString( |
| ((InlineParent) inline).getChildAreas())); |
| } else { |
| sb.append(" "); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @deprecated |
| */ |
| public void startPageSequence(LineArea seqTitle) { |
| //do nothing |
| } |
| |
| /** {@inheritDoc} */ |
| public void startPageSequence(PageSequence pageSequence) { |
| // do nothing |
| } |
| |
| // normally this would be overriden to create a page in the |
| // output |
| /** {@inheritDoc} */ |
| public void renderPage(PageViewport page) |
| throws IOException, FOPException { |
| |
| this.currentPageViewport = page; |
| try { |
| Page p = page.getPage(); |
| renderPageAreas(p); |
| } finally { |
| this.currentPageViewport = null; |
| } |
| } |
| |
| /** |
| * Renders page areas. |
| * |
| * @param page The page whos page areas are to be rendered |
| */ |
| protected void renderPageAreas(Page page) { |
| /* Spec does not appear to specify whether fo:region-body should |
| appear above or below side regions in cases of overlap. FOP |
| decision is to have fo:region-body on top, hence it is rendered |
| last here. */ |
| RegionViewport viewport; |
| viewport = page.getRegionViewport(FO_REGION_BEFORE); |
| if (viewport != null) { |
| renderRegionViewport(viewport); |
| } |
| viewport = page.getRegionViewport(FO_REGION_START); |
| if (viewport != null) { |
| renderRegionViewport(viewport); |
| } |
| viewport = page.getRegionViewport(FO_REGION_BODY); |
| if (viewport != null) { |
| renderRegionViewport(viewport); |
| } |
| viewport = page.getRegionViewport(FO_REGION_END); |
| if (viewport != null) { |
| renderRegionViewport(viewport); |
| } |
| viewport = page.getRegionViewport(FO_REGION_AFTER); |
| if (viewport != null) { |
| renderRegionViewport(viewport); |
| } |
| } |
| |
| /** |
| * Renders a region viewport. <p> |
| * |
| * The region may clip the area and it establishes a position from where |
| * the region is placed.</p> |
| * |
| * @param port The region viewport to be rendered |
| */ |
| protected void renderRegionViewport(RegionViewport port) { |
| // The CTM will transform coordinates relative to |
| // this region-reference area into page coords, so |
| // set origin for the region to 0,0. |
| currentBPPosition = 0; |
| currentIPPosition = 0; |
| |
| RegionReference regionReference = port.getRegionReference(); |
| handleRegionTraits(port); |
| |
| // shouldn't the viewport have the CTM |
| startVParea(regionReference.getCTM(), port.getClipRectangle()); |
| // do after starting viewport area |
| if (regionReference.getRegionClass() == FO_REGION_BODY) { |
| assert (regionReference instanceof BodyRegion); |
| renderBodyRegion((BodyRegion) regionReference); |
| } else { |
| renderRegion(regionReference); |
| } |
| endVParea(); |
| } |
| |
| /** |
| * Establishes a new viewport area. |
| * |
| * @param ctm the coordinate transformation matrix to use |
| * @param clippingRect the clipping rectangle if the viewport should be clipping, |
| * null if no clipping is performed. |
| */ |
| protected abstract void startVParea(CTM ctm, Rectangle clippingRect); |
| |
| /** |
| * Signals exit from a viewport area. Subclasses can restore transformation matrices |
| * valid before the viewport area was started. |
| */ |
| protected abstract void endVParea(); |
| |
| /** |
| * Handle the traits for a region |
| * This is used to draw the traits for the given page region. |
| * (See Sect. 6.4.1.2 of XSL-FO spec.) |
| * @param rv the RegionViewport whose region is to be drawn |
| */ |
| protected void handleRegionTraits(RegionViewport rv) { |
| // draw border and background |
| } |
| |
| /** |
| * Renders a region reference area. |
| * |
| * @param region The region reference area |
| */ |
| protected void renderRegion(RegionReference region) { |
| renderBlocks(null, region.getBlocks()); |
| } |
| |
| /** |
| * Renders a body region area. |
| * |
| * @param region The body region |
| */ |
| protected void renderBodyRegion(BodyRegion region) { |
| BeforeFloat bf = region.getBeforeFloat(); |
| if (bf != null) { |
| renderBeforeFloat(bf); |
| } |
| MainReference mr = region.getMainReference(); |
| if (mr != null) { |
| renderMainReference(mr); |
| } |
| Footnote foot = region.getFootnote(); |
| if (foot != null) { |
| renderFootnote(foot); |
| } |
| } |
| |
| /** |
| * Renders a before float area. |
| * |
| * @param bf The before float area |
| */ |
| protected void renderBeforeFloat(BeforeFloat bf) { |
| List blocks = bf.getChildAreas(); |
| if (blocks != null) { |
| renderBlocks(null, blocks); |
| Block sep = bf.getSeparator(); |
| if (sep != null) { |
| renderBlock(sep); |
| } |
| } |
| } |
| |
| /** |
| * Renders a footnote |
| * |
| * @param footnote The footnote |
| */ |
| protected void renderFootnote(Footnote footnote) { |
| currentBPPosition += footnote.getTop(); |
| List blocks = footnote.getChildAreas(); |
| if (blocks != null) { |
| Block sep = footnote.getSeparator(); |
| if (sep != null) { |
| renderBlock(sep); |
| } |
| renderBlocks(null, blocks); |
| } |
| } |
| |
| /** |
| * Renders the main reference area. |
| * <p> |
| * The main reference area contains a list of spans that are |
| * stacked on the page. |
| * The spans contain a list of normal flow reference areas |
| * that are positioned into columns. |
| * </p> |
| * |
| * @param mainReference The main reference area |
| */ |
| protected void renderMainReference(MainReference mainReference) { |
| Span span = null; |
| List spans = mainReference.getSpans(); |
| int saveBPPos = currentBPPosition; |
| int saveIPPos = currentIPPosition; |
| int saveSpanBPPos = saveBPPos; |
| |
| for (Object span1 : spans) { |
| span = (Span) span1; |
| |
| columnCount = span.getColumnCount(); |
| columnGap = span.getColumnGap(); |
| columnWidth = span.getColumnWidth(); |
| |
| blockProgressionDirection = (Direction) span.getTrait(Trait.BLOCK_PROGRESSION_DIRECTION); |
| inlineProgressionDirection = (Direction) span.getTrait(Trait.INLINE_PROGRESSION_DIRECTION); |
| |
| int level = span.getBidiLevel(); |
| if (level < 0) { |
| level = 0; |
| } |
| if ((level & 1) == 1) { |
| currentIPPosition += span.getIPD(); |
| currentIPPosition += columnGap; |
| } |
| |
| for (columnIndex = 0; columnIndex < columnCount; columnIndex++) { |
| |
| NormalFlow flow = span.getNormalFlow(columnIndex); |
| |
| boolean isLeftToRight = (inlineProgressionDirection == null) |
| || (inlineProgressionDirection.getEnumValue() == Constants.EN_LR); |
| |
| if (flow != null) { |
| |
| // if direction is right to left, then end is left edge, |
| // else end is right edge (for top-bottom/bottom-top block |
| // progression directions) |
| |
| // binding edge is on left edge for odd pages and |
| // on right edge for even pages |
| |
| int pageIndex = currentPageViewport.getPageIndex(); |
| |
| bindingOnStartEdge = false; |
| bindingOnEndEdge = false; |
| |
| if (isLeftToRight) { |
| |
| columnStartIPPosition = 0; |
| columnEndIPPosition = columnWidth; |
| columnLeftIPPosition = 0; |
| columnRightIPPosition = columnWidth; |
| |
| if (blockProgressionDirection == null || blockProgressionDirection.isVertical()) { |
| if (pageIndex % 2 == 0) { |
| bindingOnStartEdge = true; |
| } else { |
| bindingOnEndEdge = true; |
| } |
| } |
| |
| } else { |
| |
| columnStartIPPosition = columnWidth; |
| columnEndIPPosition = 0; |
| columnLeftIPPosition = 0; |
| columnRightIPPosition = columnWidth; |
| |
| if (blockProgressionDirection == null || blockProgressionDirection.isVertical()) { |
| if (pageIndex % 2 == 0) { |
| bindingOnEndEdge = true; |
| } else { |
| bindingOnStartEdge = true; |
| } |
| } |
| } |
| |
| currentBPPosition = saveSpanBPPos; |
| if ((level & 1) == 1) { |
| currentIPPosition -= flow.getIPD(); |
| currentIPPosition -= columnGap; |
| } |
| renderFlow(flow); |
| if ((level & 1) == 0) { |
| currentIPPosition += flow.getIPD(); |
| currentIPPosition += columnGap; |
| } |
| } |
| } |
| currentIPPosition = saveIPPos; |
| currentBPPosition = saveSpanBPPos + span.getHeight(); |
| saveSpanBPPos = currentBPPosition; |
| } |
| currentBPPosition = saveBPPos; |
| } |
| |
| /** |
| * Renders a flow reference area. |
| * |
| * @param flow The flow reference area |
| */ |
| protected void renderFlow(NormalFlow flow) { |
| // the normal flow reference area contains stacked blocks |
| List blocks = flow.getChildAreas(); |
| if (blocks != null) { |
| renderBlocks(null, blocks); |
| } |
| } |
| |
| /** |
| * Handle block traits. |
| * This method is called when the correct ip and bp posiiton is |
| * set. This should be overridden to draw border and background |
| * traits for the block area. |
| * |
| * @param block the block area |
| */ |
| protected void handleBlockTraits(Block block) { |
| // draw border and background |
| } |
| |
| /** |
| * Renders a block viewport. |
| * |
| * @param bv The block viewport |
| * @param children The children to render within the block viewport |
| */ |
| protected void renderBlockViewport(BlockViewport bv, List children) { |
| boolean inNewLayer = false; |
| if (maybeStartLayer(bv)) { |
| inNewLayer = true; |
| } |
| // clip and position viewport if necessary |
| if (bv.getPositioning() == Block.ABSOLUTE) { |
| // save positions |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| Rectangle clippingRect = null; |
| if (bv.hasClip()) { |
| clippingRect = new Rectangle(saveIP, saveBP, bv.getIPD(), bv.getBPD()); |
| } |
| |
| CTM ctm = bv.getCTM(); |
| currentIPPosition = 0; |
| currentBPPosition = 0; |
| |
| startVParea(ctm, clippingRect); |
| handleBlockTraits(bv); |
| renderBlocks(bv, children); |
| endVParea(); |
| |
| // clip if necessary |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } else { |
| // save position and offset |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| handleBlockTraits(bv); |
| renderBlocks(bv, children); |
| |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP + bv.getAllocBPD(); |
| } |
| maybeEndLayer(bv, inNewLayer); |
| } |
| |
| /** |
| * Renders a block area that represents a reference area. The reference area establishes |
| * a new coordinate system. |
| * @param block the block area |
| */ |
| protected abstract void renderReferenceArea(Block block); |
| |
| /** |
| * Renders a list of block areas. |
| * |
| * @param parent the parent block if the parent is a block, otherwise |
| * a null value. |
| * @param blocks The block areas |
| */ |
| protected void renderBlocks(Block parent, List blocks) { |
| int saveIP = currentIPPosition; |
| |
| // Calculate the position of the content rectangle. |
| if (parent != null && !parent.getTraitAsBoolean(Trait.IS_VIEWPORT_AREA)) { |
| currentBPPosition += parent.getBorderAndPaddingWidthBefore(); |
| } |
| |
| // the position of the containing block is used for |
| // absolutely positioned areas |
| int contBP = currentBPPosition; |
| int contIP = currentIPPosition; |
| containingBPPosition = currentBPPosition; |
| containingIPPosition = currentIPPosition; |
| |
| for (Object obj : blocks) { |
| if (obj instanceof Block) { |
| currentIPPosition = contIP; |
| containingBPPosition = contBP; |
| containingIPPosition = contIP; |
| renderBlock((Block) obj); |
| containingBPPosition = contBP; |
| containingIPPosition = contIP; |
| } else if (obj instanceof LineArea) { |
| // a line area is rendered from the top left position |
| // of the line, each inline object is offset from there |
| LineArea line = (LineArea) obj; |
| if (parent != null) { |
| int level = parent.getBidiLevel(); |
| if ((level == -1) || ((level & 1) == 0)) { |
| currentIPPosition += parent.getStartIndent(); |
| } else { |
| currentIPPosition += parent.getEndIndent(); |
| } |
| } |
| renderLineArea(line); |
| currentBPPosition += line.getAllocBPD(); |
| } |
| currentIPPosition = saveIP; |
| } |
| } |
| |
| /** |
| * Renders a block area. |
| * |
| * @param block The block area |
| */ |
| protected void renderBlock(Block block) { |
| assert block != null; |
| List<ChangeBar> changeBarList = block.getChangeBarList(); |
| |
| if (changeBarList != null && !changeBarList.isEmpty()) { |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| drawChangeBars(block, changeBarList); |
| |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } |
| |
| List children = block.getChildAreas(); |
| boolean inNewLayer = false; |
| if (maybeStartLayer(block)) { |
| inNewLayer = true; |
| } |
| if (block instanceof BlockViewport) { |
| if (children != null) { |
| renderBlockViewport((BlockViewport) block, children); |
| } else { |
| handleBlockTraits(block); |
| // simply move position |
| currentBPPosition += block.getAllocBPD(); |
| } |
| } else if (block.getTraitAsBoolean(Trait.IS_REFERENCE_AREA)) { |
| renderReferenceArea(block); |
| } else { |
| // save position and offset |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| currentIPPosition += block.getXOffset(); |
| currentBPPosition += block.getYOffset(); |
| currentBPPosition += block.getSpaceBefore(); |
| |
| handleBlockTraits(block); |
| |
| if (children != null && block.getTrait(Trait.VISIBILITY) != Visibility.HIDDEN) |
| { |
| renderBlocks(block, children); |
| } |
| |
| if (block.getPositioning() == Block.ABSOLUTE) { |
| // absolute blocks do not effect the layout |
| currentBPPosition = saveBP; |
| } else { |
| // stacked and relative blocks effect stacking |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP + block.getAllocBPD(); |
| } |
| } |
| maybeEndLayer(block, inNewLayer); |
| } |
| |
| /** |
| * Renders an inline block area. |
| * |
| * @param inlineBlock The inline block area |
| */ |
| protected void renderInlineBlock(InlineBlock inlineBlock) { |
| renderBlock(inlineBlock.getBlock()); |
| } |
| |
| /** |
| * Establish new optional content group layer. |
| * |
| * @param layer name of layer |
| */ |
| protected abstract void startLayer(String layer); |
| |
| /** |
| * Finish current optional content group layer. |
| */ |
| protected abstract void endLayer(); |
| |
| protected boolean maybeStartLayer(Area area) { |
| String layer = (String) area.getTrait(Trait.LAYER); |
| if (layer != null) { |
| if (layers == null) { |
| layers = new Stack<String>(); |
| } |
| if (layers.empty() || !layers.peek().equals(layer)) { |
| layers.push(layer); |
| startLayer(layer); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected void maybeEndLayer(Area area, boolean inNewLayer) { |
| if (inNewLayer) { |
| assert layers != null; |
| assert !layers.empty(); |
| String layer = (String) area.getTrait(Trait.LAYER); |
| assert layer != null; |
| assert layers.peek().equals(layer); |
| endLayer(); |
| layers.pop(); |
| } |
| } |
| |
| /** |
| * Renders a line area. <p> |
| * |
| * A line area may have grouped styling for its children such as underline, |
| * background.</p> |
| * |
| * @param line The line area |
| */ |
| protected void renderLineArea(LineArea line) { |
| List children = line.getInlineAreas(); |
| int saveBP = currentBPPosition; |
| currentBPPosition += line.getSpaceBefore(); |
| int bl = line.getBidiLevel(); |
| if (bl >= 0) { |
| if ((bl & 1) == 0) { |
| currentIPPosition += line.getStartIndent(); |
| } else { |
| currentIPPosition += line.getEndIndent(); |
| } |
| } else { |
| currentIPPosition += line.getStartIndent(); |
| } |
| for (Object aChildren : children) { |
| InlineArea inline = (InlineArea) aChildren; |
| renderInlineArea(inline); |
| } |
| currentBPPosition = saveBP; |
| } |
| |
| /** |
| * Render the given InlineArea. |
| * @param inlineArea inline area text to render |
| */ |
| protected void renderInlineArea(InlineArea inlineArea) { |
| List<ChangeBar> changeBarList = inlineArea.getChangeBarList(); |
| |
| if (changeBarList != null && !changeBarList.isEmpty()) { |
| drawChangeBars(inlineArea, changeBarList); |
| } |
| if (inlineArea instanceof TextArea) { |
| renderText((TextArea) inlineArea); |
| //} else if (inlineArea instanceof Character) { |
| //renderCharacter((Character) inlineArea); |
| } else if (inlineArea instanceof WordArea) { |
| renderWord((WordArea) inlineArea); |
| } else if (inlineArea instanceof SpaceArea) { |
| renderSpace((SpaceArea) inlineArea); |
| } else if (inlineArea instanceof InlineBlock) { |
| renderInlineBlock((InlineBlock) inlineArea); |
| } else if (inlineArea instanceof InlineParent) { |
| renderInlineParent((InlineParent) inlineArea); |
| } else if (inlineArea instanceof InlineBlockParent) { |
| renderInlineBlockParent((InlineBlockParent) inlineArea); |
| } else if (inlineArea instanceof Space) { |
| renderInlineSpace((Space) inlineArea); |
| } else if (inlineArea instanceof InlineViewport) { |
| renderInlineViewport((InlineViewport) inlineArea); |
| } else if (inlineArea instanceof Leader) { |
| renderLeader((Leader) inlineArea); |
| } |
| } |
| |
| /** |
| * Common method to render the background and borders for any inline area. |
| * The all borders and padding are drawn outside the specified area. |
| * @param area the inline area for which the background, border and padding is to be |
| * rendered |
| */ |
| protected abstract void renderInlineAreaBackAndBorders(InlineArea area); |
| |
| /** |
| * Render the given Space. |
| * @param space the space to render |
| */ |
| protected void renderInlineSpace(Space space) { |
| renderInlineAreaBackAndBorders(space); |
| // an inline space moves the inline progression position |
| // for the current block by the width or height of the space |
| // it may also have styling (only on this object) that needs |
| // handling |
| currentIPPosition += space.getAllocIPD(); |
| } |
| |
| /** |
| * Render the given Leader. |
| * @param area the leader to render |
| */ |
| protected void renderLeader(Leader area) { |
| currentIPPosition += area.getAllocIPD(); |
| } |
| |
| /** |
| * Render the given TextArea. |
| * @param text the text to render |
| */ |
| protected void renderText(TextArea text) { |
| List children = text.getChildAreas(); |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| List<ChangeBar> changeBarList = text.getChangeBarList(); |
| |
| if (changeBarList != null && !changeBarList.isEmpty()) { |
| drawChangeBars(text, changeBarList); |
| |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } |
| |
| for (Object aChildren : children) { |
| InlineArea inline = (InlineArea) aChildren; |
| renderInlineArea(inline); |
| } |
| currentIPPosition = saveIP + text.getAllocIPD(); |
| } |
| |
| /** |
| * Render the given WordArea. |
| * @param word the word to render |
| */ |
| protected void renderWord(WordArea word) { |
| currentIPPosition += word.getAllocIPD(); |
| } |
| |
| /** |
| * Render the given SpaceArea. |
| * @param space the space to render |
| */ |
| protected void renderSpace(SpaceArea space) { |
| currentIPPosition += space.getAllocIPD(); |
| } |
| |
| /** |
| * Render the given InlineParent. |
| * @param ip the inline parent to render |
| */ |
| protected void renderInlineParent(InlineParent ip) { |
| boolean inNewLayer = false; |
| if (maybeStartLayer(ip)) { |
| inNewLayer = true; |
| } |
| int level = ip.getBidiLevel(); |
| List children = ip.getChildAreas(); |
| renderInlineAreaBackAndBorders(ip); |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| // if inline parent is a filled area (generated by Leader), and if |
| // it is right-to-left, then adjust starting ip position in order to |
| // align children to starting (right) edge of filled area |
| int ipAdjust; |
| if ((ip instanceof FilledArea) && ((level & 1) != 0)) { |
| int ipdChildren = 0; |
| for (Object aChildren : children) { |
| InlineArea inline = (InlineArea) aChildren; |
| ipdChildren += inline.getAllocIPD(); |
| } |
| ipAdjust = ip.getAllocIPD() - ipdChildren; |
| } else { |
| ipAdjust = 0; |
| } |
| // perform inline position adjustments |
| if ((level == -1) || ((level & 1) == 0)) { |
| currentIPPosition += ip.getBorderAndPaddingWidthStart(); |
| } else { |
| currentIPPosition += ip.getBorderAndPaddingWidthEnd(); |
| if (ipAdjust > 0) { |
| currentIPPosition += ipAdjust; |
| } |
| } |
| currentBPPosition += ip.getBlockProgressionOffset(); |
| // render children inlines |
| for (Object aChildren : children) { |
| InlineArea inline = (InlineArea) aChildren; |
| renderInlineArea(inline); |
| } |
| currentIPPosition = saveIP + ip.getAllocIPD(); |
| currentBPPosition = saveBP; |
| maybeEndLayer(ip, inNewLayer); |
| } |
| |
| /** |
| * Render the given InlineBlockParent. |
| * @param ibp the inline block parent to render |
| */ |
| protected void renderInlineBlockParent(InlineBlockParent ibp) { |
| int level = ibp.getBidiLevel(); |
| renderInlineAreaBackAndBorders(ibp); |
| if ((level == -1) || ((level & 1) == 0)) { |
| currentIPPosition += ibp.getBorderAndPaddingWidthStart(); |
| } else { |
| currentIPPosition += ibp.getBorderAndPaddingWidthEnd(); |
| } |
| // For inline content the BP position is updated by the enclosing line area |
| int saveBP = currentBPPosition; |
| currentBPPosition += ibp.getBlockProgressionOffset(); |
| renderBlock(ibp.getChildArea()); |
| currentBPPosition = saveBP; |
| } |
| |
| /** |
| * Render the given Viewport. |
| * @param viewport the viewport to render |
| */ |
| protected void renderInlineViewport(InlineViewport viewport) { |
| Area content = viewport.getContent(); |
| int saveBP = currentBPPosition; |
| currentBPPosition += viewport.getBlockProgressionOffset(); |
| Rectangle2D contpos = viewport.getContentPosition(); |
| if (content instanceof Image) { |
| renderImage((Image) content, contpos); |
| } else if (content instanceof Container) { |
| renderContainer((Container) content); |
| } else if (content instanceof ForeignObject) { |
| renderForeignObject((ForeignObject) content, contpos); |
| } else if (content instanceof InlineBlockParent) { |
| renderInlineBlockParent((InlineBlockParent) content); |
| } |
| currentIPPosition += viewport.getAllocIPD(); |
| currentBPPosition = saveBP; |
| } |
| |
| /** |
| * Renders an image area. |
| * |
| * @param image The image |
| * @param pos The target position of the image |
| * (todo) Make renderImage() protected |
| */ |
| public void renderImage(Image image, Rectangle2D pos) { |
| List<ChangeBar> changeBarList = image.getChangeBarList(); |
| |
| if (changeBarList != null && !changeBarList.isEmpty()) { |
| drawChangeBars(image, changeBarList); |
| } |
| // Default: do nothing. |
| // Some renderers (ex. Text) don't support images. |
| } |
| |
| /** |
| * Tells the renderer to render an inline container. |
| * @param cont The inline container area |
| */ |
| protected void renderContainer(Container cont) { |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| List blocks = cont.getBlocks(); |
| renderBlocks(null, blocks); |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } |
| |
| /** |
| * Renders a foreign object area. |
| * |
| * @param fo The foreign object area |
| * @param pos The target position of the foreign object |
| * (todo) Make renderForeignObject() protected |
| */ |
| protected void renderForeignObject(ForeignObject fo, Rectangle2D pos) { |
| List<ChangeBar> changeBarList = fo.getChangeBarList(); |
| |
| if (changeBarList != null && !changeBarList.isEmpty()) { |
| drawChangeBars(fo, changeBarList); |
| } |
| |
| // Default: do nothing. |
| // Some renderers (ex. Text) don't support foreign objects. |
| } |
| |
| /** |
| * Render the xml document with the given xml namespace. |
| * The Render Context is by the handle to render into the current |
| * rendering target. |
| * @param ctx rendering context |
| * @param doc DOM Document containing the source document |
| * @param namespace Namespace URI of the document |
| */ |
| public void renderXML(RendererContext ctx, Document doc, |
| String namespace) { |
| XMLHandler handler = userAgent.getXMLHandlerRegistry().getXMLHandler( |
| this, namespace); |
| if (handler != null) { |
| try { |
| XMLHandlerConfigurator configurator |
| = new XMLHandlerConfigurator(userAgent); |
| configurator.configure(ctx, namespace); |
| handler.handleXML(ctx, doc, namespace); |
| } catch (Exception e) { |
| // could not handle document |
| ResourceEventProducer eventProducer |
| = ResourceEventProducer.Provider.get( |
| ctx.getUserAgent().getEventBroadcaster()); |
| eventProducer.foreignXMLProcessingError(this, doc, namespace, e); |
| } |
| } else { |
| if (warnedXMLHandlers == null) { |
| warnedXMLHandlers = new java.util.HashSet(); |
| } |
| if (!warnedXMLHandlers.contains(namespace)) { |
| // no handler found for document |
| warnedXMLHandlers.add(namespace); |
| ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( |
| ctx.getUserAgent().getEventBroadcaster()); |
| eventProducer.foreignXMLNoHandler(this, doc, namespace); |
| } |
| } |
| } |
| |
| /** |
| * Converts a millipoint-based transformation matrix to points. |
| * @param at a millipoint-based transformation matrix |
| * @return a point-based transformation matrix |
| */ |
| protected AffineTransform mptToPt(AffineTransform at) { |
| double[] matrix = new double[6]; |
| at.getMatrix(matrix); |
| //Convert to points |
| matrix[4] = matrix[4] / 1000; |
| matrix[5] = matrix[5] / 1000; |
| return new AffineTransform(matrix); |
| } |
| |
| /** |
| * Converts a point-based transformation matrix to millipoints. |
| * @param at a point-based transformation matrix |
| * @return a millipoint-based transformation matrix |
| */ |
| protected AffineTransform ptToMpt(AffineTransform at) { |
| double[] matrix = new double[6]; |
| at.getMatrix(matrix); |
| //Convert to millipoints |
| //Math.round() because things like this can happen: 65.6 * 1000 = 65.599999999999999 |
| //which is bad for testing |
| matrix[4] = Math.round(matrix[4] * 1000); |
| matrix[5] = Math.round(matrix[5] * 1000); |
| return new AffineTransform(matrix); |
| } |
| |
| /** |
| * Draws all change bars associated with an area. |
| * |
| * @param area The area to draw change bars for |
| * @param changeBarList The list of change bars affecting the area |
| */ |
| protected void drawChangeBars(Area area, List<ChangeBar> changeBarList) { |
| |
| if (area.getTraitAsBoolean(Trait.IS_REFERENCE_AREA)) { |
| return; |
| } |
| |
| Block changeBarArea; |
| |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| int currentColumnStartIP = columnStartIPPosition; |
| int currentColumnEndIP = columnEndIPPosition; |
| int currentColumnLeftIP = columnLeftIPPosition; |
| int currentColumnRightIP = columnRightIPPosition; |
| |
| for (ChangeBar changeBar : changeBarList) { |
| |
| boolean isLeftToRight = (inlineProgressionDirection == null) |
| || (inlineProgressionDirection.getEnumValue() == Constants.EN_LR); |
| |
| changeBarArea = new Block(); |
| |
| // currentIPPosition is reset to zero so from now on all multicolumn |
| // dimensions has to be calculated relatively to the given column |
| currentIPPosition = 0; |
| currentBPPosition = saveBP; |
| |
| int changeBarWidth = changeBar.getWidth().getValue(); |
| int changeBarOffset = changeBar.getOffset().getValue(); |
| |
| if (isLeftToRight) { |
| currentColumnStartIP = columnStartIPPosition - changeBarWidth; |
| currentColumnLeftIP = columnLeftIPPosition - changeBarWidth; |
| } else { |
| currentColumnEndIP = columnEndIPPosition - changeBarWidth; |
| currentColumnLeftIP = columnLeftIPPosition - changeBarWidth; |
| } |
| |
| // xOffset by default is negative width for change bars placed on the |
| // start edge (overriden if placement is at the end edge) |
| int xOffset = currentColumnStartIP; |
| |
| // xScale is for adding or subtracting the offset of the change bar |
| // depending on placing the bar towards or away from the edge it is |
| // bound to |
| int xScale = -1; |
| |
| // determines currentIPPosition based on placement |
| switch (changeBar.getPlacement()) { |
| case EN_START: |
| xOffset = currentColumnStartIP; |
| xScale = -1; |
| break; |
| case EN_END: |
| xOffset = currentColumnEndIP; |
| xScale = 1; |
| break; |
| case EN_LEFT: |
| xOffset = currentColumnLeftIP; |
| xScale = (isLeftToRight) ? -1 : 1; |
| break; |
| case EN_RIGHT: |
| xOffset = currentColumnRightIP; |
| xScale = (isLeftToRight) ? 1 : -1; |
| break; |
| case EN_INSIDE: |
| if (bindingOnStartEdge) { |
| xOffset = currentColumnStartIP; |
| xScale = -1; |
| } else if (bindingOnEndEdge) { |
| xOffset = currentColumnEndIP; |
| xScale = 1; |
| } else { |
| xOffset = currentColumnStartIP; |
| xScale = -1; |
| } |
| break; |
| case EN_OUTSIDE: |
| if (bindingOnStartEdge) { |
| xOffset = columnEndIPPosition; |
| xScale = 1; |
| } else if (bindingOnEndEdge) { |
| xOffset = columnStartIPPosition; |
| xScale = -1; |
| } else { |
| xOffset = columnStartIPPosition; |
| xScale = -1; |
| } |
| break; |
| case EN_ALTERNATE: |
| if (columnCount == 2) { |
| if (columnIndex == 0) { |
| xOffset = columnStartIPPosition; |
| xScale = -1; |
| } else { |
| xOffset = columnEndIPPosition; |
| xScale = 1; |
| } |
| } else { |
| if (bindingOnStartEdge) { |
| xOffset = columnEndIPPosition; |
| xScale = 1; |
| } else if (bindingOnEndEdge) { |
| xOffset = columnStartIPPosition; |
| xScale = -1; |
| } else { |
| xOffset = columnStartIPPosition; |
| xScale = -1; |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (isLeftToRight) { |
| xOffset += xScale * changeBarOffset; |
| } else { |
| xOffset -= xScale * changeBarOffset; |
| } |
| |
| xOffset += getBeginOffset(); |
| |
| // Change bar area has 0 ipd, class xsl-absolute, no margin or padding |
| changeBarArea.setAreaClass(Area.CLASS_ABSOLUTE); |
| changeBarArea.setIPD(0); |
| |
| BorderProps props = BorderProps.makeRectangular( |
| changeBar.getStyle(), changeBarWidth, changeBar.getColor(), |
| BorderProps.Mode.SEPARATE); |
| |
| changeBarArea.addTrait(Trait.BORDER_START, props); |
| changeBarArea.addTrait(Trait.BORDER_END, props); |
| |
| changeBarArea.setXOffset(xOffset); |
| |
| int areaHeight = area.getAllocBPD(); |
| |
| if (area instanceof BlockParent) { |
| changeBarArea.setBPD(areaHeight); |
| changeBarArea.setYOffset(((BlockParent) area).getYOffset()); |
| renderBlock(changeBarArea); |
| |
| } else { |
| if (areaHeight > 0) { |
| Property p = changeBar.getLineHeight().getOptimum(DummyPercentBaseContext.getInstance()); |
| int lineHeight = p.getLength().getValue(); |
| changeBarArea.setBPD(lineHeight); |
| changeBarArea.setYOffset(areaHeight - lineHeight); |
| } |
| renderInlineBlock(new InlineBlock(changeBarArea)); |
| } |
| |
| // restore position on page |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } |
| } |
| |
| /** |
| * Returns the begin offset of the inline begin (changes by reference area |
| * transforms). |
| * |
| * @return the offset from current coordinate system 0 that the IP begin is |
| * at |
| */ |
| protected int getBeginOffset() { |
| return beginOffset; |
| } |
| |
| /** |
| * Sets the begin offset for inline progression begin (changes by reference |
| * area tranforms). |
| * |
| * @param offset the new offset from IPP 0 that true IP start is at |
| */ |
| protected void setBeginOffset(int offset) { |
| beginOffset = offset; |
| } |
| } |