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)