Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaced element improved sizing support #372

Merged
merged 14 commits into from
Jul 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## CHANGELOG

### head - 0.0.1-RC22-SNAPSHOT
+ [#372](https://github.com/danfickle/openhtmltopdf/pull/372) Much improved sizing support for `img`, `svg` and `math` elements.
+ [#344](https://github.com/danfickle/openhtmltopdf/issues/344) Use PDFs in `img` tag: `<img src="some.pdf" page="1" alt="Some alt text" />`.

### 0.0.1-RC21 (2019-June-29)
+ [#361](https://github.com/danfickle/openhtmltopdf/issues/361) The SVG renderer now uses Batik in a more secure mode (no scripts, no external resource requests) by default. If you need the old behavior that allowed external resource requests and possibly scripts, please see the new BatikSVGDrawer constructor (only for trusted SVGs). Thanks @krabbenpuler.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ from ````/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/testcases/Testc
## CHANGELOG

### head - 0.0.1-RC22-SNAPSHOT
+ [#372](https://github.com/danfickle/openhtmltopdf/pull/372) Much improved sizing support for `img`, `svg` and `math` elements.
+ [#344](https://github.com/danfickle/openhtmltopdf/issues/344) Use PDFs in `img` tag: `<img src="some.pdf" page="1" alt="Some alt text" />`.

### 0.0.1-RC21 (2019-June-29)
+ [#361](https://github.com/danfickle/openhtmltopdf/issues/361) The SVG renderer now uses Batik in a more secure mode (no scripts, no external resource requests) by default. If you need the old behavior that allowed external resource requests and possibly scripts, please see the new BatikSVGDrawer constructor (only for trusted SVGs). Thanks @krabbenpuler.
Expand Down
170 changes: 141 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,130 @@ 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);

// Since the interface doesn't allow us to pass min-width/height
// we implement it here.
int minWidth = getCSSMinWidth(c);
int minHeight = getCSSMinHeight(c);

if (minWidth > cssWidth &&
minWidth > 0) {
cssWidth = minWidth;
}

if (minHeight > cssHeight &&
minHeight > 0) {
cssHeight = minHeight;
}

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) || cssWidth > getCSSMaxWidth(c)) ?
getCSSMaxWidth(c) : cssWidth;
cssWidth = cssWidth >= 0 && getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ?
getCSSMinWidth(c) : cssWidth;

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

if (getStyle().isBorderBox()) {
BorderPropertySet border = getBorder(c);
RectPropertySet padding = getPadding(c);
cssWidth = cssWidth < 0 ? cssWidth : (int) Math.max(0, cssWidth - border.width() - padding.width());
cssHeight = cssHeight < 0 ? cssHeight : (int) Math.max(0, cssHeight - border.height() - padding.height());
}

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;
}

setContentWidth(nw);
setHeight(nh);
}

public void calcDimensions(LayoutContext c) {
calcDimensions(c, getCSSWidth(c));
}
Expand All @@ -728,12 +851,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 +890,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 +990,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 +1699,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 +1755,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.openhtmltopdf.simple.extend;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;

public class ReplacedElementScaleHelper {
/**
* Creates a scale <code>AffineTransform</code> to scale a given replaced element to the desired size.
* @param dotsPerPixel
* @param contentBounds the desired size
* @param width the intrinsic width
* @param height the intrinsic height
* @return AffineTransform or null if not available.
*/
public static AffineTransform createScaleTransform(double dotsPerPixel, Rectangle contentBounds, float width, float height) {
double intrinsicWidth = width;
double intrinsicHeight = height;

double desiredWidth = (contentBounds.getWidth() / dotsPerPixel);
double desiredHeight = (contentBounds.getHeight() / dotsPerPixel);

AffineTransform scale = null;

if (width == 0 || height == 0) {
// Do nothing...
}
else if (desiredWidth > intrinsicWidth ||
desiredHeight > intrinsicHeight) {

double rw = desiredWidth / width;
double rh = desiredHeight / height;

double factor = Math.min(rw, rh);
scale = AffineTransform.getScaleInstance(factor, factor);
} else if (desiredWidth < intrinsicWidth &&
desiredHeight < intrinsicHeight) {
double rw = desiredWidth / width;
double rh = desiredHeight / height;

double factor = Math.max(rw, rh);
scale = AffineTransform.getScaleInstance(factor, factor);
}

return scale;
}

public static AffineTransform inverseOrNull(AffineTransform in) {
if (in == null) {
return null;
}
try {
return in.createInverse();
} catch (NoninvertibleTransformException e) {
return null;
}
}
}
Loading