Skip to content

Commit

Permalink
#350 #344 - Refactors sizing for replaced elements such as img and svg.
Browse files Browse the repository at this point in the history
This work moves sizing of replaced objects form externally in each replaced object implementation to internally in the core code. This should reduce duplication and mean all sizing properties are available on each replaced object type.

For example, now images have min-width and min-height as well as a border-box implementation.

This work is published initially in a new branch because while there are good tests for images we need to test the new sizing code for all the other replaced object options such as SVG, PDF, custom, etc.
  • Loading branch information
danfickle committed May 1, 2019
1 parent 957cd03 commit ab9a1f9
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 80 deletions.
151 changes: 122 additions & 29 deletions openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.w3c.dom.Element;

import com.openhtmltopdf.css.constants.CSSName;
Expand Down Expand Up @@ -703,6 +702,111 @@ private int calcEffPageRelativeWidth(LayoutContext c) {
}
}

/**
* Creates the replaced element as required. This method should be idempotent.
*/
private void createReplaced(LayoutContext c) {
ReplacedElement re = getReplacedElement();

if (re == null) {
int cssWidth = getCSSWidth(c);
int cssHeight = getCSSHeight(c);

re = c.getReplacedElementFactory().createReplacedElement(
c, this, c.getUac(), cssWidth, cssHeight);

if (re != null) {
setReplacedElement(re);
sizeReplacedElement(c, re);
}
}
}

/**
* Size a replaced element taking into account size properties including min/max,
* border-box/content-box and the natural size/aspect ratio of the replaced object.
*
* This method may be called multiple times so must be idempotent.
*/
private void sizeReplacedElement(LayoutContext c, ReplacedElement re) {
int cssWidth = getCSSWidth(c);
int cssHeight = getCSSHeight(c);

boolean haveExactDims = cssWidth >= 0 && cssHeight >= 0;

int intrinsicWidth = re.getIntrinsicWidth();
int intrinsicHeight = re.getIntrinsicHeight();

cssWidth = !getStyle().isMaxWidthNone() && intrinsicWidth > getCSSMaxWidth(c) ?
getCSSMaxWidth(c) : cssWidth;
cssWidth = getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ?
getCSSMinWidth(c) : cssWidth;

cssHeight = !getStyle().isMaxHeightNone() && intrinsicHeight > getCSSMaxHeight(c) ?
getCSSMaxHeight(c) : cssHeight;
cssHeight = getCSSMinHeight(c) > 0 && cssHeight < getCSSMinHeight(c) ?
getCSSMinHeight(c) : cssHeight;

int nw;
int nh;

if (cssWidth > 0 && cssHeight > 0) {
if (haveExactDims) {
// We only warp the aspect ratio if we have explicit width and height values.
nw = cssWidth;
nh = cssHeight;
} else if (intrinsicWidth > cssWidth || intrinsicHeight > cssHeight) {
// Too large, so reduce respecting the aspect ratio.
double rw = (double) intrinsicWidth / (double) cssWidth;
double rh = (double) intrinsicHeight / (double) cssHeight;

if (rw > rh) {
nw = cssWidth;
nh = intrinsicHeight;
} else {
nw = intrinsicWidth;
nh = cssHeight;
}
} else {
// Too small.
double rw = (double) intrinsicWidth / (double) cssWidth;
double rh = (double) intrinsicHeight / (double) cssHeight;

if (rw > rh) {
nw = cssWidth;
nh = ((int) (intrinsicHeight / rw));
} else {
nw = ((int) (intrinsicWidth / rh));
nh = cssHeight;
}
}
} else if (cssWidth > 0) {
// Explicit min/max/width with auto height so keep aspect ratio.
nw = cssWidth;
nh = ((int) (((double) cssWidth / (double) intrinsicWidth) * intrinsicHeight));
} else if (cssHeight > 0) {
// Explicit min/max/height with auto width.
nh = cssHeight;
nw = ((int) (((double) cssHeight / (double) intrinsicHeight) * intrinsicWidth));
} else if (cssWidth == 0 || cssHeight == 0) {
// Empty.
nw = cssWidth;
nh = cssHeight;
} else {
// Auto width and height so use the natural dimensions of the replaced object.
nw = intrinsicWidth;
nh = intrinsicHeight;
}

if (getStyle().isBorderBox()) {
setBorderBoxWidth(c, nw);
setBorderBoxHeight(c, nh);
} else {
setContentWidth(nw);
setHeight(nh);
}
}

public void calcDimensions(LayoutContext c) {
calcDimensions(c, getCSSWidth(c));
}
Expand All @@ -728,12 +832,18 @@ protected void calcDimensions(LayoutContext c, int cssWidth) {
setLeftMBP((int) margin.left() + (int) border.left() + (int) padding.left());
setRightMBP((int) padding.right() + (int) border.right() + (int) margin.right());

createReplaced(c);
if (isReplaced()) {
setDimensionsCalculated(true);
return;
}

if (c.isPrint() && getStyle().isDynamicAutoWidth()) {
setContentWidth(calcEffPageRelativeWidth(c));
} else {
setContentWidth((getContainingBlockWidth() - getLeftMBP() - getRightMBP()));
}

setHeight(0);

if (! isAnonymous() || (isFromCaptionedTable() && isFloated())) {
Expand Down Expand Up @@ -761,19 +871,10 @@ protected void calcDimensions(LayoutContext c, int cssWidth) {
}
}

//check if replaced
ReplacedElement re = getReplacedElement();
if (re == null) {
re = c.getReplacedElementFactory().createReplacedElement(
c, this, c.getUac(), cssWidth, cssHeight);
if (re != null){
re = fitReplacedElement(c, re);
}
}

if (re != null) {
setContentWidth(re.getIntrinsicWidth());
setHeight(re.getIntrinsicHeight());
setReplacedElement(re);

} else if (cssWidth == -1 && pinnedContentWidth == -1 &&
style.isCanBeShrunkToFit()) {
setNeedShrinkToFitCalculatation(true);
Expand Down Expand Up @@ -870,6 +971,7 @@ public void layout(LayoutContext c, int contentStart) {
c.getRootLayer().addPageSequence(this);
}

createReplaced(c);
calcDimensions(c);
calcShrinkToFitWidthIfNeeded(c);
collapseMargins(c);
Expand Down Expand Up @@ -1578,22 +1680,13 @@ public void calcMinMaxWidth(LayoutContext c) {

int width = getCSSWidth(c, true);

if (width == -1) {
if (getReplacedElement() != null) {
width = getReplacedElement().getIntrinsicWidth();
} else {
int height = getCSSHeight(c);
ReplacedElement re = c.getReplacedElementFactory().createReplacedElement(
c, this, c.getUac(), width, height);
if (re != null) {
re = fitReplacedElement(c, re);
setReplacedElement(re);
width = getReplacedElement().getIntrinsicWidth();
}
}
createReplaced(c);
if (isReplaced() && width == -1) {
// FIXME: We need to special case this for issue 313.
width = getContentWidth();
}

if (isReplaced() || (width != -1 && ! isFixedWidthAdvisoryOnly())) {
if (width != -1 && !isFixedWidthAdvisoryOnly()) {
_minWidth = _maxWidth =
(int) margin.left() + (int) border.left() + (int) padding.left() +
width +
Expand Down Expand Up @@ -1643,11 +1736,11 @@ public void calcMinMaxWidth(LayoutContext c) {
if (! isReplaced()) {
calcMinMaxCSSMinMaxWidth(c, margin, border, padding);
}

setMinMaxCalculated(true);
}
}

@Deprecated
private ReplacedElement fitReplacedElement(LayoutContext c,
ReplacedElement re)
{
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<html>
<head>
<style>
@page {
size: 200px 200px;
margin: 0;
}
body {
margin: 0;
}
</style>
</head>
<body>
<table>
<tr><td style="border: 1px solid green;"><img src="../../demos/images/flyingsaucer.png" style="max-width: 50px;" /></td></tr>
</table>

<table>
<tr><td style="border: 1px solid green;width: 150px;"><img src="../../demos/images/flyingsaucer.png" style="max-width: 30%;" /></td></tr>
</table>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<html>
<head>
<style>
@page {
size: 500px 1500px;
margin: 0;
}
body {
margin: 0;
}
img {
display: block;
border: 2px solid red;
}
</style>
</head>
<body>
<!-- width only -->
<img style="width: 200px;" src="../../demos/images/flyingsaucer.png" />

<!-- height only -->
<img style="height: 200px;" src="../../demos/images/flyingsaucer.png" />

<!-- min-width -->
<img style="width: 50px; min-width: 200px;" src="../../demos/images/flyingsaucer.png" />

<!-- min-height -->
<img style="height: 40px; min-height: 250px;" src="../../demos/images/flyingsaucer.png" />

<!-- max-width -->
<img style="width: 1000px; max-width: 70px;" src="../../demos/images/flyingsaucer.png" />

<!-- max-height -->
<img style="height: 1000px; max-height: 40px;" src="../../demos/images/flyingsaucer.png" />

<!-- border-box -->
<img style="padding: 50px; border: 10px solid red; box-sizing: border-box; width: 200px;" src="../../demos/images/flyingsaucer.png" />

<!-- content-box -->
<img style="padding: 50px; border: 10px solid red; box-sizing: content-box; width: 200px;" src="../../demos/images/flyingsaucer.png" />

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,15 @@ public void testReplacedImgInTableCell() throws IOException {
assertTrue(vt.runTest("replaced-img-in-table-cell"));
}

/**
* 1. Tests that an img (with percentage max-width) shows up in an absolute width table cell.
* 2. Tests that an img (with absolute max-width) shows up correctly sized in an auto width table cell.
*/
@Test
public void testReplacedImgInTableCell2() throws IOException {
assertTrue(vt.runTest("replaced-img-in-table-cell-2"));
}

/**
* Tests that a fixed position element correctly resizes to the sum of its child boxes
* using border-box sizing.
Expand Down Expand Up @@ -726,6 +735,15 @@ public void testReplacedImgDisplayBlock() throws IOException {
assertTrue(vt.runTest("replaced-img-display-block"));
}

/**
* Tests various sizing properties for replaced images including box-sizing,
* min/max, etc.
*/
@Test
public void testReplacedSizingImg() throws IOException {
assertTrue(vt.runTest("replaced-sizing-img"));
}

// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ public ReplacedElement createReplacedElement(LayoutContext context, BlockBox box
if (srcAttr != null && srcAttr.endsWith(".svg")) {
return new Java2DSVGReplacedElement(uac.getXMLResource(srcAttr).getDocument().getDocumentElement(), _svgImpl, cssWidth, cssHeight, box, context);
}
}
}else if (context.getNamespaceHandler().isImageElement(e)) {
return replaceImage(uac, context, e, cssWidth, cssHeight);
}

return null; // We no longer handle form controls.
/*
* Default: Just let the base class handle everything
*/
return super.createReplacedElement(context, box, uac, cssWidth, cssHeight);
//return super.createReplacedElement(context, box, uac, cssWidth, cssHeight);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,15 @@ public boolean isRequiresInteractivePaint() {
return false;
}

public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice,
BlockBox box) {
Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(),
box.getAbsY(), c);
@Override
public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, BlockBox box) {
Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), c);
ReplacedElement element = box.getReplacedElement();
outputDevice.drawImage(((PdfBoxImageElement) element).getImage(),

FSImage img = ((PdfBoxImageElement) element).getImage();
img.scale(contentBounds.width, contentBounds.height);

outputDevice.drawImage(img,
contentBounds.x, contentBounds.y, interpolate);
}

Expand Down
Loading

0 comments on commit ab9a1f9

Please sign in to comment.