org.apache.fop.render.AbstractRenderer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: AbstractRenderer.java 1835810 2018-07-13 10:29:57Z ssteiner $ */
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 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.
*
* The region may clip the area and it establishes a position from where
* the region is placed.
*
* @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.
*
* 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.
*
*
* @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 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();
}
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.
*
* A line area may have grouped styling for its children such as underline,
* background.
*
* @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 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 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 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 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 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy