org.jhotdraw8.draw.figure.PageFigure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.jhotdraw8.draw Show documentation
Show all versions of org.jhotdraw8.draw Show documentation
JHotDraw8 Drawing Framework
The newest version!
/*
* @(#)PageFigure.java
* Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
*/
package org.jhotdraw8.draw.figure;
import javafx.collections.ObservableList;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeType;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import org.jhotdraw8.css.value.CssSize;
import org.jhotdraw8.css.value.DefaultUnitConverter;
import org.jhotdraw8.draw.css.value.CssDimension2D;
import org.jhotdraw8.draw.css.value.CssInsets;
import org.jhotdraw8.draw.css.value.CssPoint2D;
import org.jhotdraw8.draw.css.value.CssRectangle2D;
import org.jhotdraw8.draw.key.CssInsetsStyleableMapAccessor;
import org.jhotdraw8.draw.key.CssPoint2DStyleableMapAccessor;
import org.jhotdraw8.draw.key.CssRectangle2DStyleableMapAccessor;
import org.jhotdraw8.draw.key.CssSizeStyleableKey;
import org.jhotdraw8.draw.key.DoubleStyleableKey;
import org.jhotdraw8.draw.key.PaperSizeStyleableMapAccessor;
import org.jhotdraw8.draw.key.Point2DStyleableMapAccessor;
import org.jhotdraw8.draw.render.RenderContext;
import org.jhotdraw8.draw.render.RenderingIntent;
import org.jhotdraw8.geom.FXRectangles;
import org.jhotdraw8.geom.FXTransforms;
import org.jhotdraw8.icollection.VectorList;
import org.jhotdraw8.icollection.immutable.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import static java.lang.Double.max;
/**
* Defines a page layout for printing.
*
* @author Werner Randelshofer
*/
public class PageFigure extends AbstractCompositeFigure
implements Page, Grouping, TransformableFigure, ResizableFigure, HideableFigure, LockableFigure, StyleableFigure,
FillableFigure, StrokableFigure {
public static final CssSizeStyleableKey HEIGHT = RectangleFigure.HEIGHT;
/**
* The computed number of pages along the x-axis.
*/
public static final DoubleStyleableKey NUM_PAGES_X = new DoubleStyleableKey("num-pages-x", 1.0);
/** The computed number of pages along the y-axis. */
public static final DoubleStyleableKey NUM_PAGES_Y = new DoubleStyleableKey("num-pages-y", 1.0);
public static final Point2DStyleableMapAccessor NUM_PAGES_X_Y = new Point2DStyleableMapAccessor("num-pages", NUM_PAGES_X, NUM_PAGES_Y);
public static final CssSizeStyleableKey PAGE_INSETS_BOTTOM = new CssSizeStyleableKey("page-insets-bottom", CssSize.ZERO);
public static final CssSizeStyleableKey PAGE_INSETS_LEFT = new CssSizeStyleableKey("page-insets-left", CssSize.ZERO);
public static final CssSizeStyleableKey PAGE_INSETS_RIGHT = new CssSizeStyleableKey("page-insets-right", CssSize.ZERO);
public static final CssSizeStyleableKey PAGE_INSETS_TOP = new CssSizeStyleableKey("page-insets-top", CssSize.ZERO);
public static final CssInsetsStyleableMapAccessor PAGE_INSETS = new CssInsetsStyleableMapAccessor("page-insets", PAGE_INSETS_TOP, PAGE_INSETS_RIGHT, PAGE_INSETS_BOTTOM, PAGE_INSETS_LEFT);
public static final CssSizeStyleableKey PAGE_OVERLAP_X = new CssSizeStyleableKey("page-overlap-x", CssSize.ZERO);
public static final CssSizeStyleableKey PAGE_OVERLAP_Y = new CssSizeStyleableKey("page-overlap-y", CssSize.ZERO);
public static final CssPoint2DStyleableMapAccessor PAGE_OVERLAP = new CssPoint2DStyleableMapAccessor("page-overlap", PAGE_OVERLAP_X, PAGE_OVERLAP_Y);
public static final CssSizeStyleableKey PAPER_HEIGHT = new CssSizeStyleableKey("paper-size-height", CssSize.of(297.0, "mm"));
public static final CssSizeStyleableKey PAPER_WIDTH = new CssSizeStyleableKey("paper-size-width", CssSize.of(210.0, "mm"));
public static final PaperSizeStyleableMapAccessor PAPER_SIZE = new PaperSizeStyleableMapAccessor("paper-size", PAPER_WIDTH, PAPER_HEIGHT);
/**
* The CSS type selector for this object is {@value #TYPE_SELECTOR}.
*/
public static final String TYPE_SELECTOR = "Page";
public static final CssSizeStyleableKey WIDTH = RectangleFigure.WIDTH;
public static final CssSizeStyleableKey X = RectangleFigure.X;
public static final CssSizeStyleableKey Y = RectangleFigure.Y;
public static final CssRectangle2DStyleableMapAccessor BOUNDS = RectangleFigure.BOUNDS;
private static final Object CONTENT_BOUNDS_PROPERTY = new Object();
private static final Object PAGE_INSETS_PROPERTY = new Object();
private static final Object PAGE_BOUNDS_PROPERTY = new Object();
private static final Object CURRENT_PAGE_PROPERTY = new Object();
public PageFigure() {
}
private void addBounds(final List pbList, Bounds b) {
double x = b.getMinX();
double y = b.getMinY();
double w = b.getWidth();
double h = b.getHeight();
pbList.add(new MoveTo(x, y));
pbList.add(new LineTo(x + w, y));
pbList.add(new LineTo(x + w, y + h));
pbList.add(new LineTo(x, y + h));
pbList.add(new ClosePath());
}
private double computeContentAreaFactor() {
if (true) {
return 1;
}
String units = getNonNull(WIDTH).getUnits();
DefaultUnitConverter uc = DefaultUnitConverter.getInstance();
double contentWidth = getNonNull(WIDTH).getConvertedValue(uc, units);
double contentHeight = getNonNull(HEIGHT).getConvertedValue(uc, units);
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue(uc, units);
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double overX = overlap.getX().getConvertedValue(uc, units);
double overY = overlap.getY().getConvertedValue(uc, units);
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
int numPagesY = Math.max(1, getStyledNonNull(NUM_PAGES_Y).intValue());
double innerPageW = (getStyledNonNull(PAPER_WIDTH).getConvertedValue(uc, units) - insets.getLeft() - insets.getRight());
double innerPageH = (getStyledNonNull(PAPER_HEIGHT).getConvertedValue(uc, units) - insets.getTop() - insets.getBottom());
double totalInnerPageWidth = innerPageW * numPagesX - overX * max(0, numPagesX - 1);
double totalInnerPageHeight = innerPageH * numPagesY - overY * max(0, numPagesY - 1);
double contentRatio = contentWidth / contentHeight;
double innerPageRatio = totalInnerPageWidth / totalInnerPageHeight;
double contentAreaFactor;
if (contentRatio > innerPageRatio) {
contentAreaFactor = (contentWidth) / totalInnerPageWidth;
} else {
contentAreaFactor = (contentHeight) / totalInnerPageHeight;
}
return contentAreaFactor;
}
@Override
public Node createNode(RenderContext ctx) {
Group n = new Group();
n.setManaged(false);
n.setAutoSizeChildren(false);
Rectangle contentBoundsNode = new Rectangle();
contentBoundsNode.setFill(null);
contentBoundsNode.setStroke(Color.LIGHTGRAY);
contentBoundsNode.setStrokeType(StrokeType.INSIDE);
Path pageBoundsNode = new Path();
Path insetsBoundsNode = new Path();
insetsBoundsNode.setFill(null);
insetsBoundsNode.setStroke(Color.LIGHTGRAY);
insetsBoundsNode.setStrokeType(StrokeType.CENTERED);
insetsBoundsNode.getStrokeDashArray().setAll(5.0);
Group currentPageNode = new Group();
n.getChildren().addAll(pageBoundsNode, insetsBoundsNode, contentBoundsNode, currentPageNode);
n.getProperties().put(PAGE_BOUNDS_PROPERTY, pageBoundsNode);
n.getProperties().put(PAGE_INSETS_PROPERTY, insetsBoundsNode);
n.getProperties().put(CONTENT_BOUNDS_PROPERTY, contentBoundsNode);
n.getProperties().put(CURRENT_PAGE_PROPERTY, currentPageNode);
return n;
}
@Override
public Node createPageNode(int internalPageNumber) {
throw new UnsupportedOperationException("Not supported yet."); //To change body ofCollection generated methods, choose Tools | Templates.
}
@Override
public Bounds getLayoutBounds() {
return getCssLayoutBounds().getConvertedBoundsValue();
}
@Override
public CssRectangle2D getCssLayoutBounds() {
return new CssRectangle2D(getNonNull(X),
getNonNull(Y),
getNonNull(WIDTH),
getNonNull(HEIGHT));
}
@Override
public int getNumberOfSubPages() {
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
int numPagesY = Math.max(1, getStyledNonNull(NUM_PAGES_Y).intValue());
return numPagesX * numPagesY;
}
@Override
public Bounds getPageBounds(int internalPageNumber) {
double contentAreaFactor = computeContentAreaFactor();
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue();
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double overX = overlap.getX().getConvertedValue();
double overY = overlap.getY().getConvertedValue();
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
double pageX = getNonNull(X).getConvertedValue() - insets.getLeft() * contentAreaFactor;
double pageY = getNonNull(Y).getConvertedValue() - insets.getTop() * contentAreaFactor;
double pageW = getStyledNonNull(PAPER_WIDTH).getConvertedValue() * contentAreaFactor;
double pageH = getStyledNonNull(PAPER_HEIGHT).getConvertedValue() * contentAreaFactor;
double pageOverX = (overX + insets.getLeft() + insets.getRight()) * contentAreaFactor;
double pageOverY = (overY + insets.getTop() + insets.getBottom()) * contentAreaFactor;
int px = internalPageNumber % numPagesX;
int py = internalPageNumber / numPagesX;
double x = pageX + (pageW - pageOverX) * px;
double y = pageY + (pageH - pageOverY) * py;
return new BoundingBox(x, y, pageW, pageH);
}
private Bounds getContentBounds(int internalPageNumber) {
double contentAreaFactor = computeContentAreaFactor();
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue();
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double overX = overlap.getX().getConvertedValue();
double overY = overlap.getY().getConvertedValue();
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
double pageX = getNonNull(X).getConvertedValue();
double pageY = getNonNull(Y).getConvertedValue();
double pageW = getNonNull(PAPER_WIDTH).getConvertedValue() * contentAreaFactor;
double pageH = getNonNull(PAPER_HEIGHT).getConvertedValue() * contentAreaFactor;
double marginH = insets.getLeft() + insets.getRight();
double marginV = insets.getTop() + insets.getBottom();
double pageOverX = (overX + marginH) * contentAreaFactor;
double pageOverY = (overY + marginV) * contentAreaFactor;
int px = internalPageNumber % numPagesX;
int py = internalPageNumber / numPagesX;
double x = pageX + (pageW - pageOverX) * px;
double y = pageY + (pageH - pageOverY) * py;
return new BoundingBox(x, y, pageW - marginH * contentAreaFactor, pageH - marginV * contentAreaFactor);
}
@Override
public Shape getPageClip(int internalPageNumber) {
double contentAreaFactor = computeContentAreaFactor();
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue();
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double ox = overlap.getX().getConvertedValue();
double oy = overlap.getY().getConvertedValue();
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
double pageX = getNonNull(X).getConvertedValue() - insets.getLeft() * contentAreaFactor;
double pageY = getNonNull(Y).getConvertedValue() - insets.getTop() * contentAreaFactor;
double pageWidth = getNonNull(PAPER_WIDTH).getConvertedValue() * contentAreaFactor;
double pageHeight = getNonNull(PAPER_HEIGHT).getConvertedValue() * contentAreaFactor;
double pageOverlapX = (ox + insets.getLeft() + insets.getRight()) * contentAreaFactor;
double pageOverlapY = (oy + insets.getTop() + insets.getBottom()) * contentAreaFactor;
int px = internalPageNumber % numPagesX;
int py = internalPageNumber / numPagesX;
double x = pageX + (pageWidth - pageOverlapX) * px;
double y = pageY + (pageHeight - pageOverlapY) * py;
Bounds b = FXRectangles.intersection(getLayoutBounds(),
new BoundingBox(x + insets.getLeft() * contentAreaFactor, y + insets.getTop() * contentAreaFactor,
pageWidth - (insets.getLeft() + insets.getRight()) * contentAreaFactor,
pageHeight - (insets.getTop() + insets.getBottom()) * contentAreaFactor));
return new Rectangle(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
}
@Override
public CssDimension2D getPaperSize() {
return getStyled(PAPER_SIZE);
}
@Override
public Transform getPageTransform(int internalPageNumber) {
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
int numPagesY = Math.max(1, getStyledNonNull(NUM_PAGES_Y).intValue());
internalPageNumber = Math.max(0, Math.min(internalPageNumber, numPagesX * numPagesY));
int px = internalPageNumber % numPagesX;
int py = internalPageNumber / numPagesX;
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue();
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double overlapX = overlap.getX().getConvertedValue();
double overlapY = overlap.getY().getConvertedValue();
double contentAreaFactor = computeContentAreaFactor();
double pageX = getNonNull(X).getConvertedValue() - insets.getLeft() * contentAreaFactor;
double pageY = getNonNull(Y).getConvertedValue() - insets.getTop() * contentAreaFactor;
double pageWidth = getNonNull(PAPER_WIDTH).getConvertedValue() * contentAreaFactor;
double pageHeight = getNonNull(PAPER_HEIGHT).getConvertedValue() * contentAreaFactor;
double pageOverlapX = (overlapX + insets.getLeft() + insets.getRight()) * contentAreaFactor;
double pageOverlapY = (overlapY + insets.getTop() + insets.getBottom()) * contentAreaFactor;
double x = pageX + (pageWidth - pageOverlapX) * px;
double y = pageY + (pageHeight - pageOverlapY) * py;
return FXTransforms.concat(new Translate(x, y), new Scale(contentAreaFactor, contentAreaFactor));
}
private Translate getPageTranslate(int internalPageNumber) {
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
int numPagesY = Math.max(1, getStyledNonNull(NUM_PAGES_Y).intValue());
internalPageNumber = Math.max(0, Math.min(internalPageNumber, numPagesX * numPagesY));
int px = internalPageNumber % numPagesX;
int py = internalPageNumber / numPagesX;
Insets insets = getStyledNonNull(PAGE_INSETS).getConvertedValue();
CssPoint2D overlap = getStyledNonNull(PAGE_OVERLAP);
double overlapX = overlap.getX().getConvertedValue();
double overlapY = overlap.getY().getConvertedValue();
double contentAreaFactor = computeContentAreaFactor();
double pageX = getNonNull(X).getConvertedValue() - insets.getLeft() * contentAreaFactor;
double pageY = getNonNull(Y).getConvertedValue() - insets.getTop() * contentAreaFactor;
double pageWidth = getNonNull(PAPER_WIDTH).getConvertedValue() * contentAreaFactor;
double pageHeight = getNonNull(PAPER_HEIGHT).getConvertedValue() * contentAreaFactor;
double pageOverlapX = (overlapX + insets.getLeft() + insets.getRight()) * contentAreaFactor;
double pageOverlapY = (overlapY + insets.getTop() + insets.getBottom()) * contentAreaFactor;
double x = (pageWidth - pageOverlapX) * px;
double y = (pageHeight - pageOverlapY) * py;
return new Translate(x, y);
}
@Override
public String getTypeSelector() {
return TYPE_SELECTOR;
}
@Override
public boolean isLayoutable() {
return true;
}
@Override
public boolean isSuitableChild(Figure newChild) {
return true;
}
@Override
public void layout(RenderContext ctx) {
final CssSize width = get(WIDTH);
final CssSize height = get(HEIGHT);
final CssDimension2D paperSize = getPaperSize();
final CssInsets pageInsets = getStyled(PAGE_INSETS);
final CssPoint2D pageOverlap = get(PAGE_OVERLAP);
CssDimension2D innerPageSize = paperSize.subtract(
new CssDimension2D(pageInsets.getLeft().add(pageInsets.getRight()),
pageInsets.getTop().add(pageInsets.getBottom())));
final int numPagesX = 1 + Math.max(0, (int) Math.ceil(
width.subtract(innerPageSize.getWidth()).getConvertedValue()
/ (innerPageSize.getWidth().subtract(pageOverlap.getX())).getConvertedValue()));
final int numPagesY = 1 + Math.max(0, (int) Math.ceil(
height.subtract(innerPageSize.getHeight()).getConvertedValue()
/ (innerPageSize.getHeight().subtract(pageOverlap.getY())).getConvertedValue()));
set(NUM_PAGES_X, (double) numPagesX);
set(NUM_PAGES_Y, (double) numPagesY);
int currentPage = 0;
final Transform pageTransform = getPageTransform(currentPage);
ImmutableList transforms = VectorList.of();
if (!pageTransform.isIdentity()) {
transforms = transforms.add(pageTransform);
}
for (Figure child : getChildren()) {
child.set(TRANSFORMS, transforms);
}
}
@Override
public void reshapeInLocal(CssSize x, CssSize y, CssSize width, CssSize height) {
set(X, width.getValue() < 0 ? x.add(width) : x);
set(Y, height.getValue() < 0 ? y.add(height) : y);
set(WIDTH, width.abs());
set(HEIGHT, height.abs());
}
@Override
public void reshapeInLocal(Transform transform) {
Bounds newBounds = transform.transform(getLayoutBounds());
set(X, CssSize.of(newBounds.getMinX()));
set(Y, CssSize.of(newBounds.getMinY()));
set(WIDTH, CssSize.of(newBounds.getWidth()));
set(HEIGHT, CssSize.of(newBounds.getHeight()));
}
@Override
public void updateNode(RenderContext ctx, Node node) {
Group groupNode = (Group) node;
// We can't use #applyTransformableFigureProperties(node) because
// this will rotate around an unpredictable center!
node.getTransforms().setAll(getLocalToParent(true));
Rectangle contentBoundsNode = (Rectangle) groupNode.getProperties().get(CONTENT_BOUNDS_PROPERTY);
Path pageBoundsNode = (Path) groupNode.getProperties().get(PAGE_BOUNDS_PROPERTY);
Path pageInsetsNode = (Path) groupNode.getProperties().get(PAGE_INSETS_PROPERTY);
Group currentPageNode = (Group) groupNode.getProperties().get(CURRENT_PAGE_PROPERTY);
applyFillableFigureProperties(ctx, pageBoundsNode);
applyStrokableFigureProperties(ctx, pageBoundsNode);
if (ctx.get(RenderContext.RENDERING_INTENT) == RenderingIntent.EDITOR) {
applyHideableFigureProperties(ctx, node);
contentBoundsNode.setVisible(true);
pageBoundsNode.setVisible(true);
} else if (ctx.get(RenderContext.RENDER_PAGE) == this) {
applyHideableFigureProperties(ctx, node);
contentBoundsNode.setVisible(false);
pageBoundsNode.setVisible(false);
pageInsetsNode.setVisible(false);
} else {
node.setVisible(false);
}
double contentWidth = getNonNull(WIDTH).getConvertedValue();
double contentHeight = getNonNull(HEIGHT).getConvertedValue();
contentBoundsNode.setX(getNonNull(X).getConvertedValue());
contentBoundsNode.setY(getNonNull(Y).getConvertedValue());
contentBoundsNode.setWidth(contentWidth);
contentBoundsNode.setHeight(contentHeight);
int numPagesX = Math.max(1, getStyledNonNull(NUM_PAGES_X).intValue());
int numPagesY = Math.max(1, getStyledNonNull(NUM_PAGES_Y).intValue());
final int n = numPagesX * numPagesY;
final List pbList = new ArrayList<>(n * 4);
final List pmList = new ArrayList<>(n * 4);
for (int i = 0; i < n; i++) {
addBounds(pbList, getPageBounds(i));
addBounds(pmList, getContentBounds(i));
}
pageBoundsNode.getElements().setAll(pbList);
pageInsetsNode.getElements().setAll(pmList);
Integer currentPage = ctx.get(RenderContext.RENDER_PAGE_INTERNAL_NUMBER);
currentPageNode.getTransforms().setAll(getPageTranslate(currentPage == null ? 0 : currentPage));
List currentPageChildren = new ArrayList<>(getChildren().size() + 2);
for (Figure child : getChildren()) {
currentPageChildren.add(ctx.getNode(child));
}
ObservableList group = currentPageNode.getChildren();
if (!group.equals(currentPageChildren)) {
group.setAll(currentPageChildren);
}
}
}