org.xhtmlrenderer.render.PageBox Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flying-saucer-core Show documentation
Show all versions of flying-saucer-core Show documentation
Flying Saucer is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code as well as Java2D output.
/*
* {{{ header & license
* Copyright (c) 2005 Wisconsin Court System
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package org.xhtmlrenderer.render;
import org.w3c.dom.Element;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.xhtmlrenderer.css.constants.CSSName;
import org.xhtmlrenderer.css.constants.IdentValue;
import org.xhtmlrenderer.css.constants.MarginBoxName;
import org.xhtmlrenderer.css.newmatch.PageInfo;
import org.xhtmlrenderer.css.parser.FSFunction;
import org.xhtmlrenderer.css.parser.PropertyValue;
import org.xhtmlrenderer.css.sheet.PropertyDeclaration;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.css.style.CssContext;
import org.xhtmlrenderer.css.style.derived.LengthValue;
import org.xhtmlrenderer.css.style.derived.RectPropertySet;
import org.xhtmlrenderer.layout.BoxBuilder;
import org.xhtmlrenderer.layout.Layer;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.newtable.TableBox;
import java.awt.*;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Locale;
public class PageBox {
private static final MarginArea[] MARGIN_AREA_DEFS = {
new TopLeftCorner(),
new TopMarginArea(),
new TopRightCorner(),
new LeftMarginArea(),
new RightMarginArea(),
new BottomLeftCorner(),
new BottomMarginArea(),
new BottomRightCorner(),
};
private static final int LEADING_TRAILING_SPLIT = 5;
private CalculatedStyle _style;
private int _top;
private int _bottom;
private int _paintingTop;
private int _paintingBottom;
private int _pageNo;
private int _outerPageWidth;
private PageDimensions _pageDimensions;
private PageInfo _pageInfo;
private final MarginAreaContainer[] _marginAreas = new MarginAreaContainer[MARGIN_AREA_DEFS.length];
private Element _metadata;
public int getWidth(CssContext cssCtx) {
resolvePageDimensions(cssCtx);
return _pageDimensions.getWidth();
}
public int getHeight(CssContext cssCtx) {
resolvePageDimensions(cssCtx);
return _pageDimensions.getHeight();
}
private void resolvePageDimensions(CssContext cssCtx) {
if (_pageDimensions == null) {
CalculatedStyle style = getStyle();
int width;
int height;
if (style.isLength(CSSName.FS_PAGE_WIDTH)) {
width = (int)style.getFloatPropertyProportionalTo(
CSSName.FS_PAGE_WIDTH, 0, cssCtx);
} else {
width = resolveAutoPageWidth(cssCtx);
}
if (style.isLength(CSSName.FS_PAGE_HEIGHT)) {
height = (int)style.getFloatPropertyProportionalTo(
CSSName.FS_PAGE_HEIGHT, 0, cssCtx);
} else {
height = resolveAutoPageHeight(cssCtx);
}
if (style.isIdent(CSSName.FS_PAGE_ORIENTATION, IdentValue.LANDSCAPE)) {
int temp;
temp = width;
width = height;
height = temp;
}
PageDimensions dim = new PageDimensions();
dim.setWidth(width);
dim.setHeight(height);
_pageDimensions = dim;
}
}
private boolean isUseLetterSize() {
Locale l = Locale.getDefault();
String county = l.getCountry();
// Per http://en.wikipedia.org/wiki/Paper_size, letter paper is
// a de facto standard in Canada (although the government uses
// its own standard) and Mexico (even though it is officially an ISO
// country)
return county.equals("US") || county.equals("CA") || county.equals("MX");
}
private int resolveAutoPageWidth(CssContext cssCtx) {
if (isUseLetterSize()) {
return (int)LengthValue.calcFloatProportionalValue(
getStyle(),
CSSName.FS_PAGE_WIDTH,
"8.5in",
8.5f,
CSSPrimitiveValue.CSS_IN,
0,
cssCtx);
} else {
return (int)LengthValue.calcFloatProportionalValue(
getStyle(),
CSSName.FS_PAGE_WIDTH,
"210mm",
210.0f,
CSSPrimitiveValue.CSS_MM,
0,
cssCtx);
}
}
private int resolveAutoPageHeight(CssContext cssCtx) {
if (isUseLetterSize()) {
return (int)LengthValue.calcFloatProportionalValue(
getStyle(),
CSSName.FS_PAGE_HEIGHT,
"11in",
11.0f,
CSSPrimitiveValue.CSS_IN,
0,
cssCtx);
} else {
return (int)LengthValue.calcFloatProportionalValue(
getStyle(),
CSSName.FS_PAGE_HEIGHT,
"297mm",
297.0f,
CSSPrimitiveValue.CSS_MM,
0,
cssCtx);
}
}
public int getContentHeight(CssContext cssCtx) {
int retval = getHeight(cssCtx) - getMarginBorderPadding(cssCtx, CalculatedStyle.TOP)
- getMarginBorderPadding(cssCtx, CalculatedStyle.BOTTOM);
if (retval <= 0) {
throw new IllegalArgumentException(
"The content height cannot be zero or less. Check your document margin definition.");
}
return retval;
}
public int getContentWidth(CssContext cssCtx) {
int retval = getWidth(cssCtx) - getMarginBorderPadding(cssCtx, CalculatedStyle.LEFT)
- getMarginBorderPadding(cssCtx, CalculatedStyle.RIGHT);
if (retval <= 0) {
throw new IllegalArgumentException(
"The content width cannot be zero or less. Check your document margin definition.");
}
return retval;
}
public CalculatedStyle getStyle() {
return _style;
}
public void setStyle(CalculatedStyle style) {
_style = style;
}
public int getBottom() {
return _bottom;
}
public int getTop() {
return _top;
}
public void setTopAndBottom(CssContext cssCtx, int top) {
_top = top;
_bottom = top + getContentHeight(cssCtx);
}
public int getPaintingBottom() {
return _paintingBottom;
}
public void setPaintingBottom(int paintingBottom) {
_paintingBottom = paintingBottom;
}
public int getPaintingTop() {
return _paintingTop;
}
public void setPaintingTop(int paintingTop) {
_paintingTop = paintingTop;
}
public Rectangle getScreenPaintingBounds(CssContext cssCtx, int additionalClearance) {
return new Rectangle(
additionalClearance, getPaintingTop(),
getWidth(cssCtx), getPaintingBottom()-getPaintingTop());
}
public Rectangle getPrintPaintingBounds(CssContext cssCtx) {
return new Rectangle(
0, 0,
getWidth(cssCtx), getHeight(cssCtx));
}
public Rectangle getPagedViewClippingBounds(CssContext cssCtx, int additionalClearance) {
return new Rectangle(
additionalClearance + getMarginBorderPadding(cssCtx, CalculatedStyle.LEFT),
getPaintingTop() + getMarginBorderPadding(cssCtx, CalculatedStyle.TOP),
getContentWidth(cssCtx),
getContentHeight(cssCtx));
}
public Rectangle getPrintClippingBounds(CssContext cssCtx) {
Rectangle result = new Rectangle(
getMarginBorderPadding(cssCtx, CalculatedStyle.LEFT),
getMarginBorderPadding(cssCtx, CalculatedStyle.TOP),
getContentWidth(cssCtx),
getContentHeight(cssCtx));
result.height -= 1;
return result;
}
public RectPropertySet getMargin(CssContext cssCtx) {
return getStyle().getMarginRect(_outerPageWidth, cssCtx);
}
private Rectangle getBorderEdge(int left, int top, CssContext cssCtx) {
RectPropertySet margin = getMargin(cssCtx);
return new Rectangle(left + (int) margin.left(),
top + (int) margin.top(),
getWidth(cssCtx) - (int) margin.left() - (int) margin.right(),
getHeight(cssCtx) - (int) margin.top() - (int) margin.bottom());
}
public void paintBorder(RenderingContext c, int additionalClearance, short mode) {
int top = 0;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = getPaintingTop();
}
c.getOutputDevice().paintBorder(c,
getStyle(),
getBorderEdge(additionalClearance, top, c),
BorderPainter.ALL);
}
public void paintBackground(RenderingContext c, int additionalClearance, short mode) {
Rectangle bounds;
if (mode == Layer.PAGED_MODE_SCREEN) {
bounds = getScreenPaintingBounds(c, additionalClearance);
} else {
bounds = getPrintPaintingBounds(c);
}
c.getOutputDevice().paintBackground(c, getStyle(), bounds, bounds, getStyle().getBorder(c));
}
public void paintMarginAreas(RenderingContext c, int additionalClearance, short mode) {
for (int i = 0; i < MARGIN_AREA_DEFS.length; i++) {
MarginAreaContainer container = _marginAreas[i];
if (container != null) {
TableBox table = _marginAreas[i].getTable();
Point p = container.getArea().getPaintingPosition(
c, this, additionalClearance, mode);
c.getOutputDevice().translate(p.x, p.y);
table.getLayer().paint(c);
c.getOutputDevice().translate(-p.x, -p.y);
}
}
}
public int getPageNo() {
return _pageNo;
}
public void setPageNo(int pageNo) {
_pageNo = pageNo;
}
public int getOuterPageWidth() {
return _outerPageWidth;
}
public void setOuterPageWidth(int containingBlockWidth) {
_outerPageWidth = containingBlockWidth;
}
public int getMarginBorderPadding(CssContext cssCtx, int which) {
return getStyle().getMarginBorderPadding(
cssCtx, getOuterPageWidth(), which);
}
public PageInfo getPageInfo() {
return _pageInfo;
}
public void setPageInfo(PageInfo pageInfo) {
_pageInfo = pageInfo;
}
public Element getMetadata() {
return _metadata;
}
public void layout(LayoutContext c) {
c.setPage(this);
retrievePageMetadata(c);
layoutMarginAreas(c);
}
// HACK Would much prefer to do this in ITextRenderer or ITextOutputDevice
// but given the existing API, this is about the only place it can be done
private void retrievePageMetadata(LayoutContext c) {
List props = getPageInfo().getXMPPropertyList();
if (props != null && !props.isEmpty()) {
for (PropertyDeclaration decl : props) {
if (decl.getCSSName() == CSSName.CONTENT) {
PropertyValue value = (PropertyValue) decl.getValue();
List values = value.getValues();
if (values.size() == 1) {
PropertyValue funcVal = values.get(0);
if (funcVal.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION) {
FSFunction func = funcVal.getFunction();
if (BoxBuilder.isElementFunction(func)) {
BlockBox metadata = BoxBuilder.getRunningBlock(c, funcVal);
if (metadata != null) {
_metadata = metadata.getElement();
}
}
}
}
break;
}
}
}
}
private void layoutMarginAreas(LayoutContext c) {
RectPropertySet margin = getMargin(c);
for (int i = 0; i < MARGIN_AREA_DEFS.length; i++) {
MarginArea area = MARGIN_AREA_DEFS[i];
Dimension dim = area.getLayoutDimension(c, this, margin);
TableBox table = BoxBuilder.createMarginTable(
c, _pageInfo,
area.getMarginBoxNames(),
(int)dim.getHeight(),
area.getDirection());
if (table != null) {
table.setContainingBlock(new MarginBox(new Rectangle((int)dim.getWidth(), (int)dim.getHeight())));
try {
c.setNoPageBreak(1);
c.reInit(false);
c.pushLayer(table);
c.getRootLayer().addPage(c);
table.layout(c);
c.popLayer();
} finally {
c.setNoPageBreak(0);
}
_marginAreas[i] = new MarginAreaContainer(area, table);
}
}
}
public boolean isLeftPage() {
return _pageNo % 2 != 0;
}
public boolean isRightPage() {
return _pageNo % 2 == 0;
}
public void exportLeadingText(RenderingContext c, Writer writer) throws IOException {
for (int i = 0; i < LEADING_TRAILING_SPLIT; i++) {
MarginAreaContainer container = _marginAreas[i];
if (container != null) {
container.getTable().exportText(c, writer);
}
}
}
public void exportTrailingText(RenderingContext c, Writer writer) throws IOException {
for (int i = LEADING_TRAILING_SPLIT; i < _marginAreas.length; i++) {
MarginAreaContainer container = _marginAreas[i];
if (container != null) {
container.getTable().exportText(c, writer);
}
}
}
private static final class PageDimensions {
private int _width;
private int _height;
public int getHeight() {
return _height;
}
public void setHeight(int height) {
_height = height;
}
public int getWidth() {
return _width;
}
public void setWidth(int width) {
_width = width;
}
}
private static class MarginAreaContainer {
private final MarginArea _area;
private final TableBox _table;
private MarginAreaContainer(MarginArea area, TableBox table) {
_area = area;
_table = table;
}
private MarginArea getArea() {
return _area;
}
private TableBox getTable() {
return _table;
}
}
private abstract static class MarginArea {
private final MarginBoxName[] _marginBoxNames;
private TableBox _table;
public abstract Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin);
public abstract Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode);
private MarginArea(MarginBoxName marginBoxName) {
_marginBoxNames = new MarginBoxName[] { marginBoxName };
}
private MarginArea(MarginBoxName[] marginBoxNames) {
_marginBoxNames = marginBoxNames;
}
public TableBox getTable() {
return _table;
}
public void setTable(TableBox table) {
_table = table;
}
public MarginBoxName[] getMarginBoxNames() {
return _marginBoxNames;
}
public int getDirection() {
return BoxBuilder.MARGIN_BOX_HORIZONTAL;
}
}
private static class TopLeftCorner extends MarginArea {
private TopLeftCorner() {
super(MarginBoxName.TOP_LEFT_CORNER);
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.left(), (int)margin.top());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingTop();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = 0;
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(additionalClearance, top);
}
}
private static class TopRightCorner extends MarginArea {
private TopRightCorner() {
super(MarginBoxName.TOP_RIGHT_CORNER);
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.right(), (int)margin.top());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int left = additionalClearance + page.getWidth(c) - (int)page.getMargin(c).right();
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingTop();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = 0;
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(left, top);
}
}
private static class BottomRightCorner extends MarginArea {
private BottomRightCorner() {
super(MarginBoxName.BOTTOM_RIGHT_CORNER);
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.right(), (int)margin.bottom());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int left = additionalClearance + page.getWidth(c) - (int)page.getMargin(c).right();
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingBottom() - (int)page.getMargin(c).bottom();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = page.getHeight(c) - (int)page.getMargin(c).bottom();
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(left, top);
}
}
private static class BottomLeftCorner extends MarginArea {
private BottomLeftCorner() {
super(MarginBoxName.BOTTOM_LEFT_CORNER);
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.left(), (int)margin.bottom());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingBottom() - (int)page.getMargin(c).bottom();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = page.getHeight(c) - (int)page.getMargin(c).bottom();
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(additionalClearance, top);
}
}
private static class LeftMarginArea extends MarginArea {
private LeftMarginArea() {
super(new MarginBoxName[] {
MarginBoxName.LEFT_TOP,
MarginBoxName.LEFT_MIDDLE,
MarginBoxName.LEFT_BOTTOM });
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.left(), page.getContentHeight(c));
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingTop() + (int)page.getMargin(c).top();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = (int)page.getMargin(c).top();
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(additionalClearance, top);
}
public int getDirection() {
return BoxBuilder.MARGIN_BOX_VERTICAL;
}
}
private static class RightMarginArea extends MarginArea {
private RightMarginArea() {
super(new MarginBoxName[] {
MarginBoxName.RIGHT_TOP,
MarginBoxName.RIGHT_MIDDLE,
MarginBoxName.RIGHT_BOTTOM });
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension((int)margin.left(), page.getContentHeight(c));
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int left = additionalClearance + page.getWidth(c) - (int)page.getMargin(c).right();
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingTop() + (int)page.getMargin(c).top();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = (int)page.getMargin(c).top();
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(left, top);
}
public int getDirection() {
return BoxBuilder.MARGIN_BOX_VERTICAL;
}
}
private static class TopMarginArea extends MarginArea {
private TopMarginArea() {
super(new MarginBoxName[] {
MarginBoxName.TOP_LEFT,
MarginBoxName.TOP_CENTER,
MarginBoxName.TOP_RIGHT });
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension(page.getContentWidth(c), (int)margin.top());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int left = additionalClearance + (int)page.getMargin(c).left();
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingTop();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = 0;
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(left, top);
}
}
private static class BottomMarginArea extends MarginArea {
private BottomMarginArea() {
super(new MarginBoxName[] {
MarginBoxName.BOTTOM_LEFT,
MarginBoxName.BOTTOM_CENTER,
MarginBoxName.BOTTOM_RIGHT });
}
@Override
public Dimension getLayoutDimension(CssContext c, PageBox page, RectPropertySet margin) {
return new Dimension(page.getContentWidth(c), (int)margin.bottom());
}
@Override
public Point getPaintingPosition(
RenderingContext c, PageBox page, int additionalClearance, short mode) {
int left = additionalClearance + (int)page.getMargin(c).left();
int top;
if (mode == Layer.PAGED_MODE_SCREEN) {
top = page.getPaintingBottom() - (int)page.getMargin(c).bottom();
} else if (mode == Layer.PAGED_MODE_PRINT) {
top = page.getHeight(c) - (int)page.getMargin(c).bottom();
} else {
throw new IllegalArgumentException("Illegal mode");
}
return new Point(left, top);
}
}
}