diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java
index 2fa7ea6a5..af6b1c0f4 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java
@@ -29,6 +29,7 @@
import com.openhtmltopdf.css.parser.CSSParser;
import com.openhtmltopdf.css.parser.PropertyValue;
import com.openhtmltopdf.css.parser.property.BackgroundPropertyBuilder;
+import com.openhtmltopdf.css.parser.property.PrimitiveBackgroundPropertyBuilders;
import com.openhtmltopdf.css.parser.property.BorderPropertyBuilders;
import com.openhtmltopdf.css.parser.property.BorderSpacingPropertyBuilder;
import com.openhtmltopdf.css.parser.property.ContentPropertyBuilder;
@@ -146,7 +147,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"transparent",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundColor()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundColor()
);
/**
@@ -158,7 +159,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"none",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundImage()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundImage()
);
/**
@@ -170,7 +171,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"repeat",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundRepeat()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundRepeat()
);
/**
@@ -182,7 +183,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"scroll",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundAttachment()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundAttachment()
);
/**
@@ -194,7 +195,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"0% 0%",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundPosition()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundPosition()
);
public final static CSSName BACKGROUND_SIZE =
@@ -203,7 +204,7 @@ public final class CSSName implements Comparable {
PRIMITIVE,
"auto auto",
NOT_INHERITED,
- new PrimitivePropertyBuilders.BackgroundSize()
+ new PrimitiveBackgroundPropertyBuilders.BackgroundSize()
);
/**
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/AbstractPropertyBuilder.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/AbstractPropertyBuilder.java
index 88fcc6d4b..86dd25428 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/AbstractPropertyBuilder.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/AbstractPropertyBuilder.java
@@ -196,6 +196,10 @@ protected void checkInheritAllowed(CSSPrimitiveValue value, boolean inheritAllow
}
}
+ protected void checkForbidInherit(CSSPrimitiveValue value) {
+ checkInheritAllowed(value, false);
+ }
+
protected List checkInheritAll(CSSName[] all, List values, int origin, boolean important, boolean inheritAllowed) {
if (values.size() == 1) {
CSSPrimitiveValue value = values.get(0);
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/BackgroundPropertyBuilder.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/BackgroundPropertyBuilder.java
index 702b7c5b3..95735dd38 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/BackgroundPropertyBuilder.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/BackgroundPropertyBuilder.java
@@ -20,6 +20,8 @@
package com.openhtmltopdf.css.parser.property;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import com.openhtmltopdf.css.constants.CSSName;
@@ -29,7 +31,10 @@
import com.openhtmltopdf.css.parser.FSRGBColor;
import com.openhtmltopdf.css.parser.PropertyValue;
import com.openhtmltopdf.css.sheet.PropertyDeclaration;
+import com.openhtmltopdf.util.WebDoc;
+import com.openhtmltopdf.util.WebDocLocations;
+@WebDoc(WebDocLocations.CSS_BACKGROUND_PROPERTIES)
public class BackgroundPropertyBuilder extends AbstractPropertyBuilder {
// [<'background-color'> || <'background-image'> || <'background-repeat'> ||
// <'background-attachment'> || <'background-position'>] | inherit
@@ -93,18 +98,18 @@ public List buildDeclarations(
}
backgroundRepeat = new PropertyDeclaration(
- CSSName.BACKGROUND_REPEAT, value, important, origin);
+ CSSName.BACKGROUND_REPEAT, new PropertyValue(Collections.singletonList(value)), important, origin);
}
if (PrimitivePropertyBuilders.BACKGROUND_ATTACHMENTS.get(ident.FS_ID)) {
if (backgroundAttachment != null) {
throw new CSSParseException("A background-attachment value cannot be set twice", -1);
}
-
+
backgroundAttachment = new PropertyDeclaration(
- CSSName.BACKGROUND_ATTACHMENT, value, important, origin);
+ CSSName.BACKGROUND_ATTACHMENT, new PropertyValue(Collections.singletonList(value)), important, origin);
}
-
+
if (ident == IdentValue.TRANSPARENT) {
if (backgroundColor != null) {
throw new CSSParseException("A background-color value cannot be set twice", -1);
@@ -118,11 +123,13 @@ public List buildDeclarations(
if (backgroundImage != null) {
throw new CSSParseException("A background-image value cannot be set twice", -1);
}
-
+
+ List bgImages = Collections.singletonList(value);
+
backgroundImage = new PropertyDeclaration(
- CSSName.BACKGROUND_IMAGE, value, important, origin);
+ CSSName.BACKGROUND_IMAGE, new PropertyValue(bgImages), important, origin);
}
-
+
if (PrimitivePropertyBuilders.BACKGROUND_POSITIONS.get(ident.FS_ID)) {
processingBackgroundPosition = true;
}
@@ -137,9 +144,11 @@ public List buildDeclarations(
if (backgroundImage != null) {
throw new CSSParseException("A background-image value cannot be set twice", -1);
}
-
+
+ List bgImages = Collections.singletonList(value);
+
backgroundImage = new PropertyDeclaration(
- CSSName.BACKGROUND_IMAGE, value, important, origin);
+ CSSName.BACKGROUND_IMAGE, new PropertyValue(bgImages), important, origin);
}
if (processingBackgroundPosition || isLength(value) || type == CSSPrimitiveValue.CSS_PERCENTAGE) {
@@ -167,23 +176,24 @@ public List buildDeclarations(
backgroundColor = new PropertyDeclaration(
CSSName.BACKGROUND_COLOR, new PropertyValue(IdentValue.TRANSPARENT), important, origin);
}
-
+
if (backgroundImage == null) {
+ List bgImages = Collections.singletonList(new PropertyValue(IdentValue.NONE));
+
backgroundImage = new PropertyDeclaration(
- CSSName.BACKGROUND_IMAGE, new PropertyValue(IdentValue.NONE), important, origin);
+ CSSName.BACKGROUND_IMAGE, new PropertyValue(bgImages), important, origin);
}
-
+
if (backgroundRepeat == null) {
backgroundRepeat = new PropertyDeclaration(
- CSSName.BACKGROUND_REPEAT, new PropertyValue(IdentValue.REPEAT), important, origin);
+ CSSName.BACKGROUND_REPEAT, new PropertyValue(Collections.singletonList(new PropertyValue(IdentValue.REPEAT))), important, origin);
}
-
+
if (backgroundAttachment == null) {
backgroundAttachment = new PropertyDeclaration(
- CSSName.BACKGROUND_ATTACHMENT, new PropertyValue(IdentValue.SCROLL), important, origin);
-
+ CSSName.BACKGROUND_ATTACHMENT, new PropertyValue(Collections.singletonList(new PropertyValue(IdentValue.SCROLL))), important, origin);
}
-
+
if (backgroundPosition == null) {
List v = new ArrayList<>(2);
v.add(new PropertyValue(CSSPrimitiveValue.CSS_PERCENTAGE, 0.0f, "0%"));
@@ -191,14 +201,9 @@ public List buildDeclarations(
backgroundPosition = new PropertyDeclaration(
CSSName.BACKGROUND_POSITION, new PropertyValue(v), important, origin);
}
-
- result = new ArrayList<>(5);
- result.add(backgroundColor);
- result.add(backgroundImage);
- result.add(backgroundRepeat);
- result.add(backgroundAttachment);
- result.add(backgroundPosition);
-
- return result;
+
+ return Arrays.asList(
+ backgroundColor, backgroundImage, backgroundRepeat,
+ backgroundAttachment, backgroundPosition);
}
}
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitiveBackgroundPropertyBuilders.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitiveBackgroundPropertyBuilders.java
new file mode 100644
index 000000000..aac40791c
--- /dev/null
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitiveBackgroundPropertyBuilders.java
@@ -0,0 +1,326 @@
+package com.openhtmltopdf.css.parser.property;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import com.openhtmltopdf.css.constants.CSSName;
+import com.openhtmltopdf.css.constants.IdentValue;
+import com.openhtmltopdf.css.parser.CSSParseException;
+import com.openhtmltopdf.css.parser.CSSPrimitiveValue;
+import com.openhtmltopdf.css.parser.CSSValue;
+import com.openhtmltopdf.css.parser.PropertyValue;
+import com.openhtmltopdf.css.parser.Token;
+import com.openhtmltopdf.css.parser.property.PrimitivePropertyBuilders.GenericColor;
+import com.openhtmltopdf.css.sheet.PropertyDeclaration;
+import com.openhtmltopdf.util.WebDoc;
+import com.openhtmltopdf.util.WebDocLocations;
+
+@WebDoc(WebDocLocations.CSS_BACKGROUND_PROPERTIES)
+public class PrimitiveBackgroundPropertyBuilders {
+ private static BitSet setOf(IdentValue... val) {
+ return PrimitivePropertyBuilders.setFor(val);
+ }
+
+ private abstract static class MultipleBackgroundValueBuilder extends AbstractPropertyBuilder {
+ protected abstract List processValue(CSSName cssName, PropertyValue value);
+
+ protected List processValues(CSSName cssName, PropertyValue val1, PropertyValue val2) {
+ return Arrays.asList(val1, val2);
+ }
+
+ protected boolean allowsTwoValueItems() {
+ return false;
+ }
+
+ @Override
+ public List buildDeclarations(
+ CSSName cssName, List values, int origin,
+ boolean important, boolean inheritAllowed) {
+
+ checkValueCount(cssName, 1, Integer.MAX_VALUE, values.size());
+
+ List res;
+
+ if (values.size() == 1) {
+ PropertyValue val = values.get(0);
+ checkInheritAllowed(val, inheritAllowed);
+
+ if (val.getCssValueType() != CSSValue.CSS_INHERIT) {
+ res = processValue(cssName, val);
+ } else {
+ return Collections.singletonList(
+ new PropertyDeclaration(cssName, val, important, origin));
+ }
+ } else {
+ res = new ArrayList<>(values.size());
+
+ for (int i = 0; i < values.size(); i++) {
+ boolean atEnd = i == values.size() - 1;
+ boolean beforeComma = !atEnd && values.get(i + 1).getOperator() == Token.TK_COMMA;
+
+ PropertyValue val1 = values.get(i);
+ PropertyValue val2 = !atEnd && !beforeComma ? values.get(i + 1) : null;
+
+ checkForbidInherit(val1);
+
+ if (val2 == null) {
+ res.addAll(processValue(cssName, val1));
+ } else if (!allowsTwoValueItems()) {
+ checkValueCount(cssName, 1, 2);
+ } else {
+ checkForbidInherit(val2);
+ res.addAll(processValues(cssName, val1, val2));
+ i++;
+ }
+ }
+ }
+
+ return Collections.singletonList(
+ new PropertyDeclaration(cssName, new PropertyValue(res), important, origin));
+ }
+ }
+
+ public static class BackgroundImage extends MultipleBackgroundValueBuilder {
+ @Override
+ protected List processValue(CSSName cssName, PropertyValue value) {
+ if (value.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION &&
+ Objects.equals(value.getFunction().getName(), "linear-gradient")) {
+ // TODO: Validation of linear-gradient args.
+ return Collections.singletonList(value);
+ } else {
+ checkIdentOrURIType(cssName, value);
+
+ if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ IdentValue ident = checkIdent(cssName, value);
+ checkValidity(cssName, setOf(IdentValue.NONE), ident);
+ }
+
+ return Collections.singletonList(value);
+ }
+ }
+ }
+
+ public static class BackgroundColor extends GenericColor {
+ }
+
+ public static class BackgroundSize extends MultipleBackgroundValueBuilder {
+ private static final BitSet ALL_ALLOWED = setOf(
+ IdentValue.AUTO, IdentValue.CONTAIN, IdentValue.COVER
+ );
+
+ @Override
+ protected List processValue(CSSName cssName, PropertyValue first) {
+ checkIdentLengthOrPercentType(cssName, first);
+
+ if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ IdentValue firstIdent = checkIdent(cssName, first);
+ checkValidity(cssName, ALL_ALLOWED, firstIdent);
+
+ assert firstIdent == IdentValue.AUTO ||
+ firstIdent == IdentValue.COVER ||
+ firstIdent == IdentValue.CONTAIN;
+
+ // Items are expected to always return a pair so just repeat the ident.
+ return Arrays.asList(first, first);
+ } else {
+ assert isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE;
+
+ return Arrays.asList(first, new PropertyValue(IdentValue.AUTO));
+ }
+ }
+
+ @Override
+ protected List processValues(CSSName cssName, PropertyValue first, PropertyValue second) {
+ checkIdentLengthOrPercentType(cssName, first);
+ checkIdentLengthOrPercentType(cssName, second);
+
+ if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ IdentValue firstIdent = checkIdent(cssName, first);
+ if (firstIdent != IdentValue.AUTO) {
+ throw new CSSParseException("The only ident value allowed here is 'auto'", -1);
+ }
+ } else if (first.getFloatValue() < 0.0f) {
+ throw new CSSParseException(cssName + " values cannot be negative", -1);
+ }
+
+ if (second.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ IdentValue secondIdent = checkIdent(cssName, second);
+ if (secondIdent != IdentValue.AUTO) {
+ throw new CSSParseException("The only ident value allowed here is 'auto'", -1);
+ }
+ } else if (second.getFloatValue() < 0.0f) {
+ throw new CSSParseException(cssName + " values cannot be negative", -1);
+ }
+
+ return Arrays.asList(first, second);
+ }
+
+ @Override
+ protected boolean allowsTwoValueItems() {
+ return true;
+ }
+ }
+
+ public static class BackgroundPosition extends MultipleBackgroundValueBuilder {
+ @Override
+ protected List processValue(CSSName cssName, PropertyValue first) {
+ checkIdentLengthOrPercentType(cssName, first);
+
+ if (isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE) {
+ return Arrays.asList(first, createValueForIdent(IdentValue.CENTER));
+ }
+
+ assert first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT;
+
+ IdentValue firstIdent = checkIdent(cssName, first);
+ checkValidity(cssName, getAllowed(), firstIdent);
+
+ if (firstIdent == IdentValue.TOP ||
+ firstIdent == IdentValue.BOTTOM) {
+ return Arrays.asList(
+ createValueForIdent(IdentValue.CENTER),
+ createValueForIdent(firstIdent));
+ } else {
+ assert firstIdent == IdentValue.CENTER ||
+ firstIdent == IdentValue.LEFT ||
+ firstIdent == IdentValue.RIGHT;
+
+ return Arrays.asList(
+ createValueForIdent(firstIdent),
+ createValueForIdent(IdentValue.CENTER));
+ }
+ }
+
+ @Override
+ protected List processValues(CSSName cssName, PropertyValue first, PropertyValue second) {
+ checkIdentLengthOrPercentType(cssName, first);
+ checkIdentLengthOrPercentType(cssName, second);
+
+ IdentValue firstIdent = null;
+ if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ firstIdent = checkIdent(cssName, first);
+ checkValidity(cssName, getAllowed(), firstIdent);
+ }
+
+ IdentValue secondIdent = null;
+ if (second.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
+ secondIdent = checkIdent(cssName, second);
+ checkValidity(cssName, getAllowed(), secondIdent);
+ }
+
+ if (firstIdent == null && secondIdent == null) {
+ assert isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE;
+ assert isLength(second) || second.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE;
+
+ return Arrays.asList(first, second);
+ } else if (firstIdent != null && secondIdent != null) {
+ if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM ||
+ secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) {
+ // CSS Standard allows to swap ident order.
+ IdentValue temp = firstIdent;
+ firstIdent = secondIdent;
+ secondIdent = temp;
+ }
+
+ // Check that we don't have "left left" or "bottom top"
+ checkIdentPosition(cssName, firstIdent, secondIdent);
+
+ assert firstIdent == IdentValue.CENTER ||
+ firstIdent == IdentValue.LEFT ||
+ firstIdent == IdentValue.RIGHT;
+
+ assert secondIdent == IdentValue.CENTER ||
+ secondIdent == IdentValue.TOP ||
+ secondIdent == IdentValue.BOTTOM;
+
+ return Arrays.asList(
+ createValueForIdent(firstIdent),
+ createValueForIdent(secondIdent));
+ } else {
+ // Check that we don't have "70% left" or "bottom 40%"
+ checkIdentPosition(cssName, firstIdent, secondIdent);
+
+ if (firstIdent == null) {
+ assert isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE;
+ assert secondIdent != null;
+
+ return Arrays.asList(first, createValueForIdent(secondIdent));
+ } else {
+ assert firstIdent != null;
+ assert isLength(second) || second.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE;
+
+ return Arrays.asList(createValueForIdent(firstIdent), second);
+ }
+ }
+ }
+
+ @Override
+ protected boolean allowsTwoValueItems() {
+ return true;
+ }
+
+ private void checkIdentPosition(CSSName cssName, IdentValue firstIdent, IdentValue secondIdent) {
+ if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM ||
+ secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) {
+ throw new CSSParseException("Invalid combination of keywords in " + cssName, -1);
+ }
+ }
+
+ private float getPercentForIdent(IdentValue ident) {
+ float percent;
+
+ if (ident == IdentValue.CENTER) {
+ percent = 50.f;
+ } else if (ident == IdentValue.BOTTOM || ident == IdentValue.RIGHT) {
+ percent = 100.0f;
+ } else {
+ assert ident == IdentValue.TOP || ident == IdentValue.LEFT;
+ percent = 0.0f;
+ }
+
+ return percent;
+ }
+
+ private PropertyValue createValueForIdent(IdentValue ident) {
+ float percent = getPercentForIdent(ident);
+ return new PropertyValue(
+ CSSPrimitiveValue.CSS_PERCENTAGE, percent, percent + "%");
+ }
+
+ private BitSet getAllowed() {
+ return PrimitivePropertyBuilders.BACKGROUND_POSITIONS;
+ }
+ }
+
+ private abstract static class MultipleIdentValue extends MultipleBackgroundValueBuilder {
+ @Override
+ protected List processValue(CSSName cssName, PropertyValue value) {
+ checkIdentType(cssName, value);
+ IdentValue ident = checkIdent(cssName, value);
+
+ checkValidity(cssName, getAllowed(), ident);
+
+ return Collections.singletonList(value);
+ }
+
+ protected abstract BitSet getAllowed();
+ }
+
+ public static class BackgroundRepeat extends MultipleIdentValue {
+ @Override
+ protected BitSet getAllowed() {
+ return PrimitivePropertyBuilders.BACKGROUND_REPEATS;
+ }
+ }
+
+ public static class BackgroundAttachment extends MultipleIdentValue {
+ @Override
+ protected BitSet getAllowed() {
+ return PrimitivePropertyBuilders.BACKGROUND_ATTACHMENTS;
+ }
+ }
+}
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java
index 4569acc58..9d2272322 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java
@@ -86,7 +86,8 @@ public class PrimitivePropertyBuilders {
// scroll | fixed | inherit
public static final BitSet BACKGROUND_ATTACHMENTS = setFor(
- new IdentValue[] { IdentValue.SCROLL, IdentValue.FIXED });
+ new IdentValue[] { IdentValue.SCROLL
+ /*, IdentValue.FIXED - removed broken support for fixed in PR#650 by @danfickle */ });
// left | right | top | bottom | center
public static final BitSet BACKGROUND_POSITIONS = setFor(
@@ -111,7 +112,7 @@ public class PrimitivePropertyBuilders {
public static final PropertyBuilder MARGIN = new LengthLikeWithAuto();
public static final PropertyBuilder PADDING = new NonNegativeLengthLike();
- private static BitSet setFor(IdentValue[] values) {
+ static BitSet setFor(IdentValue[] values) {
BitSet result = new BitSet(IdentValue.getIdentCount());
for (int i = 0; i < values.length; i++) {
IdentValue ident = values[i];
@@ -120,7 +121,7 @@ private static BitSet setFor(IdentValue[] values) {
return result;
}
- private static abstract class SingleIdent extends AbstractPropertyBuilder {
+ static abstract class SingleIdent extends AbstractPropertyBuilder {
protected abstract BitSet getAllowed();
@Override
@@ -142,7 +143,7 @@ public List buildDeclarations(
}
}
- private static class GenericColor extends AbstractPropertyBuilder {
+ static class GenericColor extends AbstractPropertyBuilder {
private static final BitSet ALLOWED = setFor(
new IdentValue[] { IdentValue.TRANSPARENT });
@@ -495,7 +496,7 @@ protected BitSet getAllowed() {
}
}
- private static class GenericURIWithNone extends AbstractPropertyBuilder {
+ static class GenericURIWithNone extends AbstractPropertyBuilder {
// | none | inherit
private static final BitSet ALLOWED = setFor(new IdentValue[] { IdentValue.NONE });
@@ -518,250 +519,6 @@ public List buildDeclarations(
}
}
- public static class BackgroundAttachment extends SingleIdent {
- @Override
- protected BitSet getAllowed() {
- return BACKGROUND_ATTACHMENTS;
- }
- }
-
- public static class BackgroundColor extends GenericColor {
- }
-
- public static class BackgroundImage extends GenericURIWithNone {
- @Override
- public List buildDeclarations(
- CSSName cssName, List values, int origin,
- boolean important, boolean inheritAllowed) {
-
- checkValueCount(cssName, 1, values.size());
- PropertyValue value = values.get(0);
-
- if (value.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION &&
- Objects.equals(value.getFunction().getName(), "linear-gradient")) {
- // TODO: Validation of linear-gradient args.
- return Collections.singletonList(
- new PropertyDeclaration(cssName, value, important, origin));
- } else {
- return super.buildDeclarations(cssName, values, origin, important, inheritAllowed);
- }
- }
-
- }
-
- public static class BackgroundSize extends AbstractPropertyBuilder {
- private static final BitSet ALL_ALLOWED = setFor(new IdentValue[] {
- IdentValue.AUTO, IdentValue.CONTAIN, IdentValue.COVER
- });
-
- @Override
- public List buildDeclarations(CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) {
- checkValueCount(cssName, 1, 2, values.size());
-
- PropertyValue first = values.get(0);
- PropertyValue second = null;
- if (values.size() == 2) {
- second = values.get(1);
- }
-
- checkInheritAllowed(first, inheritAllowed);
- if (values.size() == 1 &&
- first.getCssValueType() == CSSValue.CSS_INHERIT) {
- return Collections.singletonList(
- new PropertyDeclaration(cssName, first, important, origin));
- }
-
- if (second != null) {
- checkInheritAllowed(second, false);
- }
-
- checkIdentLengthOrPercentType(cssName, first);
- if (second == null) {
- if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
- IdentValue firstIdent = checkIdent(cssName, first);
- checkValidity(cssName, ALL_ALLOWED, firstIdent);
-
- if (firstIdent == IdentValue.CONTAIN || firstIdent == IdentValue.COVER) {
- return Collections.singletonList(
- new PropertyDeclaration(cssName, first, important, origin));
- } else {
- return createTwoValueResponse(CSSName.BACKGROUND_SIZE, first, first, origin, important);
- }
- } else {
- return createTwoValueResponse(CSSName.BACKGROUND_SIZE, first, new PropertyValue(IdentValue.AUTO), origin, important);
- }
- } else {
- checkIdentLengthOrPercentType(cssName, second);
-
- if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
- IdentValue firstIdent = checkIdent(cssName, first);
- if (firstIdent != IdentValue.AUTO) {
- throw new CSSParseException("The only ident value allowed here is 'auto'", -1);
- }
- } else if (first.getFloatValue() < 0.0f) {
- throw new CSSParseException(cssName + " values cannot be negative", -1);
- }
-
- if (second.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
- IdentValue secondIdent = checkIdent(cssName, second);
- if (secondIdent != IdentValue.AUTO) {
- throw new CSSParseException("The only ident value allowed here is 'auto'", -1);
- }
- } else if (second.getFloatValue() < 0.0f) {
- throw new CSSParseException(cssName + " values cannot be negative", -1);
- }
-
- return createTwoValueResponse(CSSName.BACKGROUND_SIZE, first, second, origin, important);
- }
- }
-
- }
-
- public static class BackgroundPosition extends AbstractPropertyBuilder {
- @Override
- public List buildDeclarations(
- CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) {
- checkValueCount(cssName, 1, 2, values.size());
-
- PropertyValue first = values.get(0);
- PropertyValue second = null;
- if (values.size() == 2) {
- second = values.get(1);
- }
-
- checkInheritAllowed(first, inheritAllowed);
- if (values.size() == 1 &&
- first.getCssValueType() == CSSValue.CSS_INHERIT) {
- return Collections.singletonList(
- new PropertyDeclaration(cssName, first, important, origin));
- }
-
- if (second != null) {
- checkInheritAllowed(second, false);
- }
-
- checkIdentLengthOrPercentType(cssName, first);
- if (second == null) {
- if (isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE) {
- List responseValues = new ArrayList<>(2);
- responseValues.add(first);
- responseValues.add(new PropertyValue(
- CSSPrimitiveValue.CSS_PERCENTAGE, 50.0f, "50%"));
- return Collections.singletonList(new PropertyDeclaration(
- CSSName.BACKGROUND_POSITION,
- new PropertyValue(responseValues), important, origin));
- }
- } else {
- checkIdentLengthOrPercentType(cssName, second);
- }
-
-
- IdentValue firstIdent = null;
- if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
- firstIdent = checkIdent(cssName, first);
- checkValidity(cssName, getAllowed(), firstIdent);
- }
-
- IdentValue secondIdent = null;
- if (second == null) {
- secondIdent = IdentValue.CENTER;
- } else if (second.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
- secondIdent = checkIdent(cssName, second);
- checkValidity(cssName, getAllowed(), secondIdent);
- }
-
- if (firstIdent == null && secondIdent == null) {
- return Collections.singletonList(new PropertyDeclaration(
- CSSName.BACKGROUND_POSITION, new PropertyValue(values), important, origin));
- } else if (firstIdent != null && secondIdent != null) {
- if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM ||
- secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) {
- IdentValue temp = firstIdent;
- firstIdent = secondIdent;
- secondIdent = temp;
- }
-
- checkIdentPosition(cssName, firstIdent, secondIdent);
-
- return createTwoPercentValueResponse(
- getPercentForIdent(firstIdent),
- getPercentForIdent(secondIdent),
- important,
- origin);
- } else {
- checkIdentPosition(cssName, firstIdent, secondIdent);
-
- List responseValues = new ArrayList<>(2);
-
- if (firstIdent == null) {
- responseValues.add(first);
- responseValues.add(createValueForIdent(secondIdent));
- } else {
- responseValues.add(createValueForIdent(firstIdent));
- responseValues.add(second);
- }
-
- return Collections.singletonList(new PropertyDeclaration(
- CSSName.BACKGROUND_POSITION,
- new PropertyValue(responseValues), important, origin));
- }
- }
-
- private void checkIdentPosition(CSSName cssName, IdentValue firstIdent, IdentValue secondIdent) {
- if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM ||
- secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) {
- throw new CSSParseException("Invalid combination of keywords in " + cssName, -1);
- }
- }
-
- private float getPercentForIdent(IdentValue ident) {
- float percent = 0.0f;
-
- if (ident == IdentValue.CENTER) {
- percent = 50.f;
- } else if (ident == IdentValue.BOTTOM || ident == IdentValue.RIGHT) {
- percent = 100.0f;
- }
-
- return percent;
- }
-
- private PropertyValue createValueForIdent(IdentValue ident) {
- float percent = getPercentForIdent(ident);
- return new PropertyValue(
- CSSPrimitiveValue.CSS_PERCENTAGE, percent, percent + "%");
- }
-
- private List createTwoPercentValueResponse(
- float percent1, float percent2, boolean important, int origin) {
- PropertyValue value1 = new PropertyValue(
- CSSPrimitiveValue.CSS_PERCENTAGE, percent1, percent1 + "%");
- PropertyValue value2 = new PropertyValue(
- CSSPrimitiveValue.CSS_PERCENTAGE, percent2, percent2 + "%");
-
- List values = new ArrayList<>(2);
- values.add(value1);
- values.add(value2);
-
- PropertyDeclaration result = new PropertyDeclaration(
- CSSName.BACKGROUND_POSITION,
- new PropertyValue(values), important, origin);
-
- return Collections.singletonList(result);
- }
-
- private BitSet getAllowed() {
- return BACKGROUND_POSITIONS;
- }
- }
-
- public static class BackgroundRepeat extends SingleIdent {
- @Override
- protected BitSet getAllowed() {
- return BACKGROUND_REPEATS;
- }
- }
-
public static class BorderCollapse extends SingleIdent {
// collapse | separate | inherit
private static final BitSet ALLOWED = setFor(
@@ -1768,7 +1525,7 @@ protected BitSet getAllowed() {
}
- private static List createTwoValueResponse(CSSName cssName, PropertyValue value1, PropertyValue value2,
+ static List createTwoValueResponse(CSSName cssName, PropertyValue value1, PropertyValue value2,
int origin, boolean important) {
List values = new ArrayList<>(2);
values.add(value1);
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/BackgroundSize.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/BackgroundSize.java
index befb5b918..999cb4b60 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/BackgroundSize.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/BackgroundSize.java
@@ -19,22 +19,12 @@
*/
package com.openhtmltopdf.css.style;
+import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.parser.PropertyValue;
public class BackgroundSize {
- private boolean _contain;
- private boolean _cover;
- private boolean _bothAuto;
-
- private PropertyValue _width;
- private PropertyValue _height;
-
-
- public BackgroundSize(boolean contain, boolean cover, boolean bothAuto) {
- _contain = contain;
- _cover = cover;
- _bothAuto = bothAuto;
- }
+ private final PropertyValue _width;
+ private final PropertyValue _height;
public BackgroundSize(PropertyValue width, PropertyValue height) {
_width = width;
@@ -42,15 +32,16 @@ public BackgroundSize(PropertyValue width, PropertyValue height) {
}
public boolean isContain() {
- return _contain;
+ return _width.getIdentValue() == IdentValue.CONTAIN;
}
public boolean isCover() {
- return _cover;
+ return _width.getIdentValue() == IdentValue.COVER;
}
public boolean isBothAuto() {
- return _bothAuto;
+ return _width.getIdentValue() == IdentValue.AUTO &&
+ _height.getIdentValue() == IdentValue.AUTO;
}
public PropertyValue getWidth() {
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java
index 95008aa16..51ea6fb22 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java
@@ -21,6 +21,9 @@
package com.openhtmltopdf.css.style;
import java.awt.Cursor;
+import java.lang.annotation.Documented;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
@@ -53,6 +56,8 @@
import com.openhtmltopdf.render.FSFontMetrics;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.util.LogMessageId;
+import com.openhtmltopdf.util.WebDoc;
+import com.openhtmltopdf.util.WebDocLocations;
import com.openhtmltopdf.util.XRLog;
import com.openhtmltopdf.util.XRRuntimeException;
@@ -311,47 +316,6 @@ public FSColor getBackgroundColor() {
}
}
- public BackgroundSize getBackgroundSize() {
- if (_backgroundSize == null) {
- _backgroundSize = createBackgroundSize();
- }
-
- return _backgroundSize;
- }
-
- private BackgroundSize createBackgroundSize() {
- FSDerivedValue value = valueByName(CSSName.BACKGROUND_SIZE);
- if (value instanceof IdentValue) {
- IdentValue ident = (IdentValue)value;
- if (ident == IdentValue.COVER) {
- return new BackgroundSize(false, true, false);
- } else if (ident == IdentValue.CONTAIN) {
- return new BackgroundSize(true, false, false);
- }
- } else {
- ListValue valueList = (ListValue)value;
- List values = valueList.getValues();
- boolean firstAuto = values.get(0).getIdentValue() == IdentValue.AUTO;
- boolean secondAuto = values.get(1).getIdentValue() == IdentValue.AUTO;
-
- if (firstAuto && secondAuto) {
- return new BackgroundSize(false, false, true);
- } else {
- return new BackgroundSize(values.get(0), values.get(1));
- }
- }
-
- throw new RuntimeException("internal error");
- }
-
- public BackgroundPosition getBackgroundPosition() {
- ListValue result = (ListValue) valueByName(CSSName.BACKGROUND_POSITION);
- List values = result.getValues();
-
- return new BackgroundPosition(
- values.get(0), values.get(1));
- }
-
public List getCounterReset() {
FSDerivedValue value = valueByName(CSSName.COUNTER_RESET);
@@ -797,16 +761,8 @@ public boolean isCleared() {
return ! isIdent(CSSName.CLEAR, IdentValue.NONE);
}
- public IdentValue getBackgroundRepeat() {
- return getIdent(CSSName.BACKGROUND_REPEAT);
- }
-
- public IdentValue getBackgroundAttachment() {
- return getIdent(CSSName.BACKGROUND_ATTACHMENT);
- }
-
- public boolean isFixedBackground() {
- return getIdent(CSSName.BACKGROUND_ATTACHMENT) == IdentValue.FIXED;
+ public IdentValue getBackgroundRepeat(PropertyValue value) {
+ return value.getIdentValue();
}
public boolean isInline() {
@@ -1017,14 +973,12 @@ public boolean isOverflowVisible() {
return valueByName(CSSName.OVERFLOW) == IdentValue.VISIBLE;
}
- public boolean isHorizontalBackgroundRepeat() {
- IdentValue value = getIdent(CSSName.BACKGROUND_REPEAT);
- return value == IdentValue.REPEAT_X || value == IdentValue.REPEAT;
+ public boolean isHorizontalBackgroundRepeat(PropertyValue value) {
+ return value.getIdentValue() == IdentValue.REPEAT_X || value.getIdentValue() == IdentValue.REPEAT;
}
- public boolean isVerticalBackgroundRepeat() {
- IdentValue value = getIdent(CSSName.BACKGROUND_REPEAT);
- return value == IdentValue.REPEAT_Y || value == IdentValue.REPEAT;
+ public boolean isVerticalBackgroundRepeat(PropertyValue value) {
+ return value.getIdentValue() == IdentValue.REPEAT_Y || value.getIdentValue() == IdentValue.REPEAT;
}
public boolean isTopAuto() {
@@ -1232,11 +1186,6 @@ public boolean isShowEmptyCells() {
return isCollapseBorders() || isIdent(CSSName.EMPTY_CELLS, IdentValue.SHOW);
}
- public boolean isHasBackground() {
- return ! (isIdent(CSSName.BACKGROUND_COLOR, IdentValue.TRANSPARENT) &&
- isIdent(CSSName.BACKGROUND_IMAGE, IdentValue.NONE));
- }
-
public List getTextDecorations() {
FSDerivedValue value = valueByName(CSSName.TEXT_DECORATION);
if (value == IdentValue.NONE) {
@@ -1399,20 +1348,102 @@ public static int getCSSMaxHeight(CssContext c, Box box) {
}
}
- public boolean isLinearGradient() {
- FSDerivedValue value = valueByName(CSSName.BACKGROUND_IMAGE);
- return value instanceof FunctionValue &&
- Objects.equals(((FunctionValue) value).getFunction().getName(), "linear-gradient");
+ public boolean isHasBackground() {
+ return !isIdent(CSSName.BACKGROUND_COLOR, IdentValue.TRANSPARENT) ||
+ isHasBackgroundImage();
+ }
+
+ public boolean isHasBackgroundImage() {
+ List backgroundImages = ((ListValue) valueByName(CSSName.BACKGROUND_IMAGE)).getValues();
+
+ if (backgroundImages.size() == 1) {
+ return backgroundImages.get(0).getIdentValue() != IdentValue.NONE;
+ } else {
+ return backgroundImages.stream().anyMatch(val -> val.getIdentValue() != IdentValue.NONE);
+ }
+ }
+
+ public enum BackgroundImageType {
+ URI, GRADIENT, NONE;
+ }
+
+ public static class BackgroundContainer {
+ public BackgroundImageType type;
+ public PropertyValue imageGradientOrNone;
+
+ public BackgroundPosition backgroundPosition;
+ public BackgroundSize backgroundSize;
+ public PropertyValue backgroundRepeat;
}
- public FSLinearGradient getLinearGradient(CssContext cssContext, int boxWidth, int boxHeight) {
- if (!isLinearGradient()) {
+ public boolean isLinearGradient(PropertyValue value) {
+ return value.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION &&
+ Objects.equals(value.getFunction().getName(), "linear-gradient");
+ }
+
+ public FSLinearGradient getLinearGradient(
+ PropertyValue value, CssContext cssContext, int boxWidth, int boxHeight) {
+
+ if (!isLinearGradient(value)) {
return null;
}
- FunctionValue value = (FunctionValue) valueByName(CSSName.BACKGROUND_IMAGE);
return new FSLinearGradient(this, value.getFunction(), boxWidth, boxHeight, cssContext);
}
+
+ /**
+ * Gets the values of the background properties and combines in a list
+ * of BackgroundContainer values.
+ */
+ @WebDoc(WebDocLocations.CSS_BACKGROUND_PROPERTIES)
+ public List getBackgroundImages() {
+ List images = ((ListValue) valueByName(CSSName.BACKGROUND_IMAGE)).getValues();
+ List positions = ((ListValue) valueByName(CSSName.BACKGROUND_POSITION)).getValues();
+ List repeats = ((ListValue) valueByName(CSSName.BACKGROUND_REPEAT)).getValues();
+ List sizes = ((ListValue) valueByName(CSSName.BACKGROUND_SIZE)).getValues();
+
+ assert positions.size() % 2 == 0;
+ assert sizes.size() % 2 == 0;
+
+ List posPairs = new ArrayList<>(positions.size() / 2);
+ for (int i = 0; i < positions.size(); i += 2) {
+ posPairs.add(new BackgroundPosition(positions.get(i), positions.get(i + 1)));
+ }
+
+ List sizePairs = new ArrayList<>(sizes.size() / 2);
+ for (int i = 0; i < sizes.size(); i += 2) {
+ sizePairs.add(new BackgroundSize(sizes.get(i), sizes.get(i + 1)));
+ }
+
+ List backgrounds = new ArrayList<>(images.size());
+
+ for (int i = 0; i < images.size(); i++) {
+ BackgroundContainer bg = new BackgroundContainer();
+ PropertyValue img = images.get(i);
+
+ if (isLinearGradient(img)) {
+ bg.type = BackgroundImageType.GRADIENT;
+ } else if (img.getIdentValue() == IdentValue.NONE) {
+ bg.type = BackgroundImageType.NONE;
+ } else {
+ bg.type = BackgroundImageType.URI;
+ }
+
+ bg.imageGradientOrNone = img;
+
+ // If less background-position values are provided than images,
+ // they must repeat.
+ bg.backgroundPosition = posPairs.get(i % posPairs.size());
+ bg.backgroundSize = sizePairs.get(i % sizePairs.size());
+ bg.backgroundRepeat = repeats.get(i % repeats.size());
+
+ backgrounds.add(bg);
+ }
+
+ // Pre-reverse the images, from back to front.
+ Collections.reverse(backgrounds);
+ return backgrounds;
+ }
}
/*
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/Layer.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/Layer.java
index 923b7e501..d495ae841 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/Layer.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/Layer.java
@@ -869,26 +869,6 @@ private PaintingInfo calcPaintingDimension(LayoutContext c) {
return result;
}
- @Deprecated // Not used.
- private boolean containsFixedLayer() {
- for (Layer child : getChildren()) {
- if (child.getMaster().getStyle().isFixed() || child.containsFixedLayer()) {
- return true;
- }
- }
- return false;
- }
-
- @Deprecated
- public boolean containsFixedContent() {
- return _fixedBackground || containsFixedLayer();
- }
-
- @Deprecated // We not longer support fixed background.
- public void setFixedBackground(boolean b) {
- _fixedBackground = b;
- }
-
/**
* The resulting list should not be modified.
*/
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/AbstractOutputDevice.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/AbstractOutputDevice.java
index d5e328e1d..fef62765b 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/AbstractOutputDevice.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/AbstractOutputDevice.java
@@ -30,6 +30,8 @@
import com.openhtmltopdf.css.style.BackgroundPosition;
import com.openhtmltopdf.css.style.BackgroundSize;
import com.openhtmltopdf.css.style.CalculatedStyle;
+import com.openhtmltopdf.css.style.CalculatedStyle.BackgroundContainer;
+import com.openhtmltopdf.css.style.CalculatedStyle.BackgroundImageType;
import com.openhtmltopdf.css.style.CssContext;
import com.openhtmltopdf.css.style.derived.BorderPropertySet;
import com.openhtmltopdf.css.style.derived.FSLinearGradient;
@@ -43,6 +45,8 @@
import java.awt.*;
import java.awt.geom.Area;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
@@ -198,15 +202,17 @@ public void paintBorder(RenderingContext c, CalculatedStyle style, Rectangle edg
BorderPainter.paint(edge, sides, style.getBorder(c), c, 0, true);
}
- private FSImage getBackgroundImage(RenderingContext c, CalculatedStyle style) {
- if (! style.isIdent(CSSName.BACKGROUND_IMAGE, IdentValue.NONE)) {
- String uri = style.getStringProperty(CSSName.BACKGROUND_IMAGE);
+ private FSImage getBackgroundImage(PropertyValue bgImage, RenderingContext c) {
+ if (bgImage.getIdentValue() != IdentValue.NONE) {
+ String uri = bgImage.getStringValue();
+
try {
return c.getUac().getImageResource(uri).getImage();
} catch (Exception ex) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_FAILED_TO_LOAD_BACKGROUND_IMAGE_AT_URI, uri, ex);
}
}
+
return null;
}
@@ -236,27 +242,13 @@ private void paintBackground0(
return;
}
- FSColor backgroundColor = style.getBackgroundColor();
- FSImage backgroundImage = null;
- FSLinearGradient backgroundLinearGradient = null;
-
- if (style.isLinearGradient()) {
- backgroundLinearGradient = style.getLinearGradient(c, (int) (bgImageContainer.width - border.width()), (int) (bgImageContainer.height - border.height()));
- } else {
- backgroundImage = getBackgroundImage(c, style);
+ if (!style.isHasBackground()) {
+ return;
}
- // If the image width or height is zero, then there's nothing to draw.
- // Also prevents infinte loop when trying to tile an image with zero size.
- if (backgroundImage == null || backgroundImage.getHeight() == 0 || backgroundImage.getWidth() == 0) {
- backgroundImage = null;
- }
+ FSColor backgroundColor = style.getBackgroundColor();
+ List bgImages = style.getBackgroundImages();
- if ( (backgroundColor == null || backgroundColor == FSRGBColor.TRANSPARENT) &&
- backgroundImage == null && backgroundLinearGradient == null) {
- return;
- }
-
Shape borderBoundsShape = BorderPainter.generateBorderBounds(backgroundBounds, border, true);
// FIXME for issue 396 - generating an Area for a shape with curves is very very slow and
@@ -265,7 +257,7 @@ private void paintBackground0(
Area borderBounds = border.hasBorderRadius() && c.isFastRenderer() ? null : new Area(borderBoundsShape);
Shape oldclip = null;
-
+
if (!c.isFastRenderer()) {
oldclip = getClip();
if(oldclip != null) {
@@ -273,7 +265,7 @@ private void paintBackground0(
borderBounds.intersect(new Area(oldclip));
}
setClip(borderBounds);
- } else if (backgroundImage != null || backgroundLinearGradient != null) {
+ } else if (style.isHasBackgroundImage()) {
pushClip(borderBounds != null ? borderBounds : borderBoundsShape);
}
@@ -282,80 +274,116 @@ private void paintBackground0(
fill(borderBounds != null ? borderBounds : borderBoundsShape);
}
- if (backgroundImage != null || backgroundLinearGradient != null) {
- Rectangle localBGImageContainer = bgImageContainer;
- if (style.isFixedBackground()) {
- localBGImageContainer = c.getViewportRectangle();
- }
+ for (BackgroundContainer bgImage : bgImages) {
+ if (bgImage.type == BackgroundImageType.GRADIENT) {
+ FSLinearGradient backgroundLinearGradient =
+ style.getLinearGradient(bgImage.imageGradientOrNone, c, (int) (bgImageContainer.width - border.width()), (int) (bgImageContainer.height - border.height()));
- int xoff = localBGImageContainer.x;
- int yoff = localBGImageContainer.y;
+ if (backgroundLinearGradient != null) {
+ Dimension xyoff = calcInitialXYOff(bgImage, bgImageContainer, border, style, c);
- if (border != null) {
- xoff += (int)border.left();
- yoff += (int)border.top();
- }
-
- if (backgroundImage != null) {
- scaleBackgroundImage(c, style, localBGImageContainer, backgroundImage);
-
- float imageWidth = backgroundImage.getWidth();
- float imageHeight = backgroundImage.getHeight();
+ int xoff = xyoff.width;
+ int yoff = xyoff.height;
- BackgroundPosition position = style.getBackgroundPosition();
- xoff += calcOffset(
- c, style, position.getHorizontal(), localBGImageContainer.width, imageWidth);
- yoff += calcOffset(
- c, style, position.getVertical(), localBGImageContainer.height, imageHeight);
+ drawLinearGradient(backgroundLinearGradient, new Rectangle(xoff, yoff, bgImageContainer.width, bgImageContainer.height));
+ }
+ } else if (bgImage.type == BackgroundImageType.NONE) {
+ // Do nothing...
+ } else {
+ assert bgImage.type == BackgroundImageType.URI;
- boolean hrepeat = style.isHorizontalBackgroundRepeat();
- boolean vrepeat = style.isVerticalBackgroundRepeat();
+ FSImage backgroundImage = getBackgroundImage(bgImage.imageGradientOrNone, c);
- if (! hrepeat && ! vrepeat) {
- Rectangle imageBounds = new Rectangle(xoff, yoff, (int)imageWidth, (int)imageHeight);
- if (imageBounds.intersects(backgroundBounds)) {
- drawImage(backgroundImage, xoff, yoff, style.isImageRenderingInterpolate());
+ // If the image width or height is zero, then there's nothing to draw.
+ // Also prevents infinte loop when trying to tile an image with zero size.
+ if (backgroundImage != null && backgroundImage.getHeight() != 0 && backgroundImage.getWidth() != 0) {
+ drawBgImage(c, style, backgroundBounds, bgImageContainer, border, backgroundImage, bgImage);
}
- } else if (hrepeat && vrepeat) {
- paintTiles(
- backgroundImage,
- adjustTo(backgroundBounds.x, xoff, (int)imageWidth),
- adjustTo(backgroundBounds.y, yoff, (int)imageHeight),
- backgroundBounds.x + backgroundBounds.width,
- backgroundBounds.y + backgroundBounds.height, style.isImageRenderingInterpolate());
- } else if (hrepeat) {
- xoff = adjustTo(backgroundBounds.x, xoff, (int)imageWidth);
- Rectangle imageBounds = new Rectangle(xoff, yoff, (int)imageWidth, (int)imageHeight);
- if (imageBounds.intersects(backgroundBounds)) {
- paintHorizontalBand(
- backgroundImage,
- xoff,
- yoff,
- backgroundBounds.x + backgroundBounds.width, style.isImageRenderingInterpolate());
- }
- } else if (vrepeat) {
- yoff = adjustTo(backgroundBounds.y, yoff, (int)imageHeight);
- Rectangle imageBounds = new Rectangle(xoff, yoff, (int)imageWidth, (int)imageHeight);
- if (imageBounds.intersects(backgroundBounds)) {
- paintVerticalBand(
- backgroundImage,
- xoff,
- yoff,
- backgroundBounds.y + backgroundBounds.height, style.isImageRenderingInterpolate());
- }
- } // End background image painting.
- } else if (backgroundLinearGradient != null) {
- drawLinearGradient(backgroundLinearGradient, new Rectangle(xoff, yoff, bgImageContainer.width, bgImageContainer.height));
}
}
-
+
if (!c.isFastRenderer()) {
setClip(oldclip);
- } else if (backgroundImage != null || backgroundLinearGradient != null) {
+ } else if (style.isHasBackgroundImage()) {
popClip();
}
}
+ private Dimension calcInitialXYOff(
+ BackgroundContainer bgImage,
+ Rectangle bgImageContainer,
+ BorderPropertySet border,
+ CalculatedStyle style,
+ RenderingContext c) {
+
+ Rectangle localBGImageContainer = bgImageContainer;
+
+ int xoff = localBGImageContainer.x;
+ int yoff = localBGImageContainer.y;
+
+ if (border != null) {
+ xoff += (int) border.left();
+ yoff += (int) border.top();
+ }
+
+ return new Dimension(xoff, yoff);
+ }
+
+ private void drawBgImage(
+ RenderingContext c,
+ CalculatedStyle style,
+ Rectangle backgroundBounds,
+ Rectangle bgImageContainer,
+ BorderPropertySet border,
+ FSImage backgroundImage,
+ BackgroundContainer bgImage) {
+
+ Dimension xyoff = calcInitialXYOff(bgImage, bgImageContainer, border, style, c);
+
+ int xoff = xyoff.width;
+ int yoff = xyoff.height;
+
+ Rectangle localBGImageContainer = bgImageContainer;
+
+ scaleBackgroundImage(c, style, localBGImageContainer, backgroundImage, bgImage);
+
+ float imageWidth = backgroundImage.getWidth();
+ float imageHeight = backgroundImage.getHeight();
+
+ BackgroundPosition position = bgImage.backgroundPosition;
+
+ xoff += calcOffset(c, style, position.getHorizontal(), localBGImageContainer.width, imageWidth);
+ yoff += calcOffset(c, style, position.getVertical(), localBGImageContainer.height, imageHeight);
+
+ boolean hrepeat = style.isHorizontalBackgroundRepeat(bgImage.backgroundRepeat);
+ boolean vrepeat = style.isVerticalBackgroundRepeat(bgImage.backgroundRepeat);
+
+ if (!hrepeat && !vrepeat) {
+ Rectangle imageBounds = new Rectangle(xoff, yoff, (int) imageWidth, (int) imageHeight);
+ if (imageBounds.intersects(backgroundBounds)) {
+ drawImage(backgroundImage, xoff, yoff, style.isImageRenderingInterpolate());
+ }
+ } else if (hrepeat && vrepeat) {
+ paintTiles(backgroundImage, adjustTo(backgroundBounds.x, xoff, (int) imageWidth),
+ adjustTo(backgroundBounds.y, yoff, (int) imageHeight), backgroundBounds.x + backgroundBounds.width,
+ backgroundBounds.y + backgroundBounds.height, style.isImageRenderingInterpolate());
+ } else if (hrepeat) {
+ xoff = adjustTo(backgroundBounds.x, xoff, (int) imageWidth);
+ Rectangle imageBounds = new Rectangle(xoff, yoff, (int) imageWidth, (int) imageHeight);
+ if (imageBounds.intersects(backgroundBounds)) {
+ paintHorizontalBand(backgroundImage, xoff, yoff, backgroundBounds.x + backgroundBounds.width,
+ style.isImageRenderingInterpolate());
+ }
+ } else if (vrepeat) {
+ yoff = adjustTo(backgroundBounds.y, yoff, (int) imageHeight);
+ Rectangle imageBounds = new Rectangle(xoff, yoff, (int) imageWidth, (int) imageHeight);
+ if (imageBounds.intersects(backgroundBounds)) {
+ paintVerticalBand(backgroundImage, xoff, yoff, backgroundBounds.y + backgroundBounds.height,
+ style.isImageRenderingInterpolate());
+ }
+ }
+ }
+
private int adjustTo(int target, int current, int imageDim) {
int result = current;
if (result > target) {
@@ -416,8 +444,8 @@ private int calcOffset(CssContext c, CalculatedStyle style, PropertyValue value,
}
}
- private void scaleBackgroundImage(CssContext c, CalculatedStyle style, Rectangle backgroundContainer, FSImage image) {
- BackgroundSize backgroundSize = style.getBackgroundSize();
+ private void scaleBackgroundImage(CssContext c, CalculatedStyle style, Rectangle backgroundContainer, FSImage image, BackgroundContainer bgImage) {
+ BackgroundSize backgroundSize = bgImage.backgroundSize;
if (! backgroundSize.isBothAuto()) {
if (backgroundSize.isCover() || backgroundSize.isContain()) {
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java
index 82db84601..87b034e47 100755
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java
@@ -999,10 +999,6 @@ public void layout(LayoutContext c, int contentStart) {
c.pushLayer(this);
}
- if (style.isFixedBackground()) {
- c.getRootLayer().setFixedBackground(true);
- }
-
calcClearance(c);
if (isRoot() || getStyle().establishesBFC() || isMarginAreaRoot()) {
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDoc.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDoc.java
new file mode 100644
index 000000000..cb092cb0c
--- /dev/null
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDoc.java
@@ -0,0 +1,25 @@
+package com.openhtmltopdf.util;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+
+/**
+ * Lets us specify a url where the type, method, etc
+ * is documented. Specified as an annotation so we can use
+ * IDE features to find instances and maybe tooling can use it too.
+ */
+@Retention(SOURCE)
+@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR })
+public @interface WebDoc {
+ /**
+ * A url where docs can be found.
+ */
+ public String value();
+}
diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDocLocations.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDocLocations.java
new file mode 100644
index 000000000..ab9a3ac38
--- /dev/null
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/WebDocLocations.java
@@ -0,0 +1,5 @@
+package com.openhtmltopdf.util;
+
+public class WebDocLocations {
+ public static final String CSS_BACKGROUND_PROPERTIES = "https://github.com/danfickle/openhtmltopdf/wiki/Big-CSS-reference#background-properties";
+}
diff --git a/openhtmltopdf-examples/src/main/resources/demos/images/cc0-cat.png b/openhtmltopdf-examples/src/main/resources/demos/images/cc0-cat.png
new file mode 100644
index 000000000..968f2ca3a
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/demos/images/cc0-cat.png differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-advanced.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-advanced.pdf
new file mode 100644
index 000000000..799829dc6
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-advanced.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-page-box.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-page-box.pdf
new file mode 100644
index 000000000..4342b7917
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images-page-box.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images.pdf
new file mode 100644
index 000000000..619dba0f1
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-649-multiple-bg-images.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-advanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-advanced.html
new file mode 100644
index 000000000..0685c7ec9
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-advanced.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-page-box.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-page-box.html
new file mode 100644
index 000000000..5ac26a55e
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images-page-box.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images.html
new file mode 100644
index 000000000..da063ddc7
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-649-multiple-bg-images.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
index ad6c7b8ad..6e3e75bde 100644
--- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
+++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
@@ -1375,6 +1375,30 @@ public void testIssue642TransformInline() throws IOException {
assertTrue(vt.runTest("issue-642-transform-inline"));
}
+ /**
+ * Tests that the background-image property allows multiple values.
+ */
+ @Test
+ public void testIssue649MultipleBgImages() throws IOException {
+ assertTrue(vt.runTest("issue-649-multiple-bg-images"));
+ }
+
+ /**
+ * Tests that the other background-* properties allow multiple values.
+ */
+ @Test
+ public void testIssue649MultipleBgImagesAdvanced() throws IOException {
+ assertTrue(vt.runTest("issue-649-multiple-bg-images-advanced"));
+ }
+
+ /**
+ * Tests that multiple background images can be used in a page at-rule.
+ */
+ @Test
+ public void testIssue649MultipleBgImagesPageBox() throws IOException {
+ assertTrue(vt.runTest("issue-649-multiple-bg-images-page-box"));
+ }
+
// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)