diff options
author | Peter Hancock <phancock@apache.org> | 2012-10-24 10:20:17 +0000 |
---|---|---|
committer | Peter Hancock <phancock@apache.org> | 2012-10-24 10:20:17 +0000 |
commit | 7e09a8037fc79d14550c301ea039f9163a735b19 (patch) | |
tree | d9b85d984b258e3a7b1e4b882ed3c90f7c550a2b /src/java/org/apache/fop | |
parent | f17dbea7f8210e18ef5e9f103a684637ac36a916 (diff) | |
parent | 5de50e85027df695f0d3d9356f7f684ba464ed1d (diff) | |
download | xmlgraphics-fop-7e09a8037fc79d14550c301ea039f9163a735b19.tar.gz xmlgraphics-fop-7e09a8037fc79d14550c301ea039f9163a735b19.zip |
Added support for Rounded Corners (see Bugzilla 54041).
Contributions made by PH, JM and LB
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1401607 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop')
37 files changed, 3926 insertions, 1428 deletions
diff --git a/src/java/org/apache/fop/afp/AFPResourceManager.java b/src/java/org/apache/fop/afp/AFPResourceManager.java index 2e5f78043..99b28cafd 100644 --- a/src/java/org/apache/fop/afp/AFPResourceManager.java +++ b/src/java/org/apache/fop/afp/AFPResourceManager.java @@ -67,14 +67,9 @@ public class AFPResourceManager { /** Maintain a reference count of instream objects for referencing purposes */ private int instreamObjectCount = 0; - /** a mapping of resourceInfo --> include name */ - private final Map<AFPResourceInfo, String> includeNameMap - = new java.util.HashMap<AFPResourceInfo, String>(); - - /** a mapping of resourceInfo --> page segment name */ - private Map<AFPResourceInfo, String> pageSegmentMap - = new java.util.HashMap<AFPResourceInfo, String>(); - + /** Mapping of resourceInfo to AbstractCachedObject */ + private final Map<AFPResourceInfo, AbstractCachedObject> includeObjectCache + = new java.util.HashMap<AFPResourceInfo, AbstractCachedObject>(); private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults(); /** @@ -140,21 +135,7 @@ public class AFPResourceManager { public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException { AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo(); updateResourceInfoUri(resourceInfo); - - String objectName = includeNameMap.get(resourceInfo); - if (objectName != null) { - // an existing data resource so reference it by adding an include to the current page - includeObject(dataObjectInfo, objectName); - return true; - } - - objectName = pageSegmentMap.get(resourceInfo); - if (objectName != null) { - // an existing data resource so reference it by adding an include to the current page - includePageSegment(dataObjectInfo, objectName); - return true; - } - return false; + return includeCachedObject(resourceInfo, dataObjectInfo.getObjectAreaInfo()); } /** @@ -195,7 +176,7 @@ public class AFPResourceManager { useInclude &= resourceGroup != null; if (useInclude) { - boolean usePageSegment = dataObjectInfo.isCreatePageSegment(); + final boolean usePageSegment = dataObjectInfo.isCreatePageSegment(); // if it is to reside within a resource group at print-file or external level if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) { @@ -213,23 +194,108 @@ public class AFPResourceManager { // add data object into its resource group destination resourceGroup.addObject(namedObj); - - // create the include object - String objectName = namedObj.getName(); - if (usePageSegment) { - includePageSegment(dataObjectInfo, objectName); - pageSegmentMap.put(resourceInfo, objectName); - } else { - includeObject(dataObjectInfo, objectName); - // record mapping of resource info to data object resource name - includeNameMap.put(resourceInfo, objectName); - } + includeObject(namedObj, dataObjectInfo); } else { // not to be included so inline data object directly into the current page dataStream.getCurrentPage().addObject(namedObj); } } + private abstract class AbstractCachedObject { + protected String objectName; + protected AFPDataObjectInfo dataObjectInfo; + + public AbstractCachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) { + this.objectName = objectName; + this.dataObjectInfo = dataObjectInfo; + + + } + protected abstract void includeObject(); + } + + private class CachedPageSegment extends AbstractCachedObject { + + public CachedPageSegment(String objectName, AFPDataObjectInfo dataObjectInfo) { + super(objectName, dataObjectInfo); + } + + protected void includeObject() { + includePageSegment(dataObjectInfo, objectName); + } + + } + + private class CachedObject extends AbstractCachedObject { + + public CachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) { + super(objectName, dataObjectInfo); + } + + protected void includeObject() { + AFPResourceManager.this.includeObject(dataObjectInfo, objectName); + } + + } + + + private void includeObject(AbstractNamedAFPObject namedObj, AFPDataObjectInfo dataObjectInfo) { + + // create the include object + String objectName = namedObj.getName(); + + AbstractCachedObject cachedObject; + + if (dataObjectInfo.isCreatePageSegment()) { + cachedObject = new CachedPageSegment(objectName, dataObjectInfo); + } else { + cachedObject = new CachedObject(objectName, dataObjectInfo); + } + + cachedObject.includeObject(); + + includeObjectCache.put(dataObjectInfo.getResourceInfo(), cachedObject); + + //The data field of dataObjectInfo is not further required + // therefore we are safe to null the reference, saving memory + dataObjectInfo.setData(null); + + } + + /** + * TODO + * @param resourceInfo + * @return + */ + public boolean isObjectCached(AFPResourceInfo resourceInfo) { + return includeObjectCache.containsKey(resourceInfo); + } + + /** + * TODO + * @param resourceInfo + * @param areaInfo + * @return + */ + public boolean includeCachedObject(AFPResourceInfo resourceInfo, AFPObjectAreaInfo areaInfo) { + + String objectName; + + AbstractCachedObject cachedObject = (AbstractCachedObject)includeObjectCache.get(resourceInfo); + + if (cachedObject != null) { + if (areaInfo != null) { + cachedObject.dataObjectInfo.setObjectAreaInfo(areaInfo); + } + cachedObject.includeObject(); + + return true; + } else { + return false; + } + } + + private void updateResourceInfoUri(AFPResourceInfo resourceInfo) { String uri = resourceInfo.getUri(); if (uri == null) { @@ -316,8 +382,9 @@ public class AFPResourceManager { resourceInfo.setName(resourceName); resourceInfo.setUri(uri.toASCIIString()); - String objectName = includeNameMap.get(resourceInfo); - if (objectName == null) { + AbstractCachedObject cachedObject = (AbstractCachedObject) + includeObjectCache.get(resourceInfo); + if (cachedObject == null) { if (log.isDebugEnabled()) { log.debug("Adding included resource: " + resourceName); } @@ -330,8 +397,12 @@ public class AFPResourceManager { ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel); resourceGroup.addObject(resourceObject); + + //TODO what is the data object? + cachedObject = new CachedObject(resourceName, null); + // record mapping of resource info to data object resource name - includeNameMap.put(resourceInfo, resourceName); + includeObjectCache.put(resourceInfo, cachedObject); } else { //skip, already created } @@ -354,9 +425,8 @@ public class AFPResourceManager { resourceInfo.setName(resourceName); resourceInfo.setUri(uri.toASCIIString()); - String resource = includeNameMap.get(resourceInfo); - if (resource == null) { - + AbstractCachedObject cachedObject = (AbstractCachedObject) includeObjectCache.get(resourceInfo); + if (cachedObject == null) { ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel); //resourceObject delegates write commands to copyNamedResource() @@ -382,11 +452,9 @@ public class AFPResourceManager { @Override protected void writeEnd(OutputStream os) throws IOException { } }; - resourceGroup.addObject(resourceObject); - - includeNameMap.put(resourceInfo, resourceName); - + cachedObject = new CachedObject(resourceName, null); + includeObjectCache.put(resourceInfo, cachedObject); } } diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index 65320fd48..cd0d4becf 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -169,6 +169,7 @@ public final class Trait implements Serializable { /** shift direction trait */ public static final Integer SHIFT_DIRECTION = 42; + /** Maximum value used by trait keys */ public static final int MAX_TRAIT_KEY = 42; diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index 1f098fe05..7a83a12f8 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -778,18 +778,46 @@ public interface Constants { int PR_X_ALT_TEXT = 274; /** Property constant - FOP proprietary prototype (in XSL-FO 2.0 Requirements) */ int PR_X_XML_BASE = 275; + + /** Property constant FOP proprietary*/ + int PR_X_BORDER_BEFORE_RADIUS_START = 276; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_BEFORE_RADIUS_END = 277; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_AFTER_RADIUS_START = 278; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_AFTER_RADIUS_END = 279; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_START_RADIUS_BEFORE = 280; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_START_RADIUS_AFTER = 281; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_END_RADIUS_BEFORE = 282; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_END_RADIUS_AFTER = 283; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_RADIUS = 284; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_BEFORE_START_RADIUS = 285; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_BEFORE_END_RADIUS = 286; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_AFTER_START_RADIUS = 287; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_AFTER_END_RADIUS = 288; + /** * Property constant - FOP proprietary extension (see NumberConverter) used * to perform additional control over number conversion when generating page * numbers. */ - int PR_X_NUMBER_CONVERSION_FEATURES = 276; + int PR_X_NUMBER_CONVERSION_FEATURES = 289; /** Scope for table header */ - int PR_X_HEADER_COLUMN = 277; + int PR_X_HEADER_COLUMN = 290; /** Number of property constants defined */ - int PROPERTY_COUNT = 277; + int PROPERTY_COUNT = 290; // compound property constants diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index 6f77efc93..3ae81c62a 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -30,6 +30,7 @@ import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.properties.BackgroundPositionShorthand; import org.apache.fop.fo.properties.BorderSpacingShorthandParser; import org.apache.fop.fo.properties.BorderWidthPropertyMaker; +import org.apache.fop.fo.properties.BoxCornerPropShorthandParser; import org.apache.fop.fo.properties.BoxPropShorthandParser; import org.apache.fop.fo.properties.CharacterProperty; import org.apache.fop.fo.properties.ColorProperty; @@ -99,6 +100,7 @@ public final class FOPropertyMapping implements Constants { private PropertyMaker genericCondBorderWidth = null; private PropertyMaker genericBorderWidth = null; private PropertyMaker genericBorderStyle = null; + private PropertyMaker genericCondCornerRadius = null; private PropertyMaker genericBreak = null; private PropertyMaker genericSpace = null; @@ -203,6 +205,13 @@ public final class FOPropertyMapping implements Constants { genericBorderStyle.addEnum("outset", getEnumProperty(EN_OUTSET, "OUTSET")); genericBorderStyle.setDefault("none"); + // GenericCondCornerRadius + genericCondCornerRadius = new CondLengthProperty.Maker(0); + genericCondCornerRadius.useGeneric(genericCondLength); + genericCondCornerRadius.setInherited(false); + genericCondCornerRadius.getSubpropMaker(CP_LENGTH).setDefault("0pt"); + genericCondCornerRadius.setPercentBase(LengthBase.CONTAINING_BLOCK_HEIGHT); + // GenericBreak genericBreak = new EnumProperty.Maker(0); genericBreak.setInherited(false); @@ -2605,6 +2614,65 @@ public final class FOPropertyMapping implements Constants { m.setDefault(""); addPropertyMaker("fox:alt-text", m); + + // fox:border-*-radius-* + m = new CondLengthProperty.Maker(PR_X_BORDER_BEFORE_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_BEFORE_START_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-before-radius-start", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_BEFORE_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_BEFORE_END_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-before-radius-end", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_AFTER_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_AFTER_START_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-after-radius-start", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_AFTER_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_AFTER_END_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-after-radius-end", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_START_RADIUS_BEFORE); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_BEFORE_START_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-start-radius-before", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_START_RADIUS_AFTER); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_AFTER_START_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-start-radius-after", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_END_RADIUS_BEFORE); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_BEFORE_END_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-end-radius-before", m); + + m = new CondLengthProperty.Maker(PR_X_BORDER_END_RADIUS_AFTER); + m.useGeneric(genericCondCornerRadius); + m.addShorthand(generics[PR_X_BORDER_AFTER_END_RADIUS]); + m.addShorthand(generics[PR_X_BORDER_RADIUS]); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-end-radius-after", m); + + // provisional-label-separation m = new LengthProperty.Maker(PR_PROVISIONAL_LABEL_SEPARATION); m.setInherited(true); @@ -2730,14 +2798,14 @@ public final class FOPropertyMapping implements Constants { addPropertyMaker("border-right", m); // border-style - m = new ListProperty.Maker(PR_BORDER_STYLE); + m = new ListProperty.Maker(PR_BORDER_STYLE); m.setInherited(false); m.setDefault(""); m.setDatatypeParser(new BoxPropShorthandParser()); addPropertyMaker("border-style", m); // border-spacing - m = new ListProperty.Maker(PR_BORDER_SPACING); + m = new ListProperty.Maker(PR_BORDER_SPACING); m.setInherited(true); m.setDefault("0pt"); m.setDatatypeParser(new BorderSpacingShorthandParser()); @@ -2757,6 +2825,41 @@ public final class FOPropertyMapping implements Constants { m.setDatatypeParser(new BoxPropShorthandParser()); addPropertyMaker("border-width", m); + // fox:border-radius + m = new ListProperty.Maker(PR_X_BORDER_RADIUS); + m.setInherited(false); + m.setDatatypeParser(new BoxCornerPropShorthandParser()); + m.setPercentBase(LengthBase.CONTAINING_BLOCK_WIDTH); + addPropertyMaker("fox:border-radius", m); + + // fox:border-before-start-radius + m = new ListProperty.Maker(PR_X_BORDER_BEFORE_START_RADIUS); + m.setInherited(false); + m.setDatatypeParser(new BoxCornerPropShorthandParser()); + m.setPercentBase(LengthBase.CONTAINING_BLOCK_WIDTH); + addPropertyMaker("fox:border-before-start-radius", m); + + // fox:border-before-end-radius + m = new ListProperty.Maker(PR_X_BORDER_BEFORE_END_RADIUS); + m.setInherited(false); + m.setDatatypeParser(new BoxCornerPropShorthandParser()); + m.setPercentBase(LengthBase.CONTAINING_BLOCK_WIDTH); + addPropertyMaker("fox:border-before-end-radius", m); + + // fox:border-after-start-radius + m = new ListProperty.Maker(PR_X_BORDER_AFTER_START_RADIUS); + m.setInherited(false); + m.setDatatypeParser(new BoxCornerPropShorthandParser()); + m.setPercentBase(LengthBase.CONTAINING_BLOCK_WIDTH); + addPropertyMaker("fox:border-after-start-radius", m); + + // fox:border-after-end-radius + m = new ListProperty.Maker(PR_X_BORDER_AFTER_END_RADIUS); + m.setInherited(false); + m.setDatatypeParser(new BoxCornerPropShorthandParser()); + m.setPercentBase(LengthBase.CONTAINING_BLOCK_WIDTH); + addPropertyMaker("fox:border-after-end-radius", m); + // cue m = new ToBeImplementedProperty.Maker(PR_CUE); m.setInherited(false); diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index a040edf1a..09b47f02a 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -40,8 +40,7 @@ public class ExtensionElementMapping extends ElementMapping { /** The standard XML prefix for elements and attributes in this namespace. */ public static final String STANDARD_PREFIX = "fox"; - private static final Set<String> PROPERTY_ATTRIBUTES - = new java.util.HashSet<String>(); + private static final Set<String> PROPERTY_ATTRIBUTES = new java.util.HashSet<String>(); static { //These are FOP's standard extension properties (fox:*) @@ -53,6 +52,20 @@ public class ExtensionElementMapping extends ElementMapping { //These are FOP's extension properties for accessibility PROPERTY_ATTRIBUTES.add("alt-text"); PROPERTY_ATTRIBUTES.add("header"); + //fox:border-*-radius-* + PROPERTY_ATTRIBUTES.add("border-before-radius-start"); + PROPERTY_ATTRIBUTES.add("border-before-radius-end"); + PROPERTY_ATTRIBUTES.add("border-after-radius-start"); + PROPERTY_ATTRIBUTES.add("border-after-radius-end"); + PROPERTY_ATTRIBUTES.add("border-start-radius-before"); + PROPERTY_ATTRIBUTES.add("border-start-radius-after"); + PROPERTY_ATTRIBUTES.add("border-end-radius-before"); + PROPERTY_ATTRIBUTES.add("border-end-radius-after"); + PROPERTY_ATTRIBUTES.add("border-radius"); + PROPERTY_ATTRIBUTES.add("border-before-start-radius"); + PROPERTY_ATTRIBUTES.add("border-before-end-radius"); + PROPERTY_ATTRIBUTES.add("border-after-start-radius"); + PROPERTY_ATTRIBUTES.add("border-after-end-radius"); } /** diff --git a/src/java/org/apache/fop/fo/properties/BoxCornerPropShorthandParser.java b/src/java/org/apache/fop/fo/properties/BoxCornerPropShorthandParser.java new file mode 100644 index 000000000..06f198573 --- /dev/null +++ b/src/java/org/apache/fop/fo/properties/BoxCornerPropShorthandParser.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fo.properties; + +import org.apache.fop.fo.FOPropertyMapping; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.expr.PropertyException; + +/** + * Shorthand property parser for Box rounded corner properties + */ +public class BoxCornerPropShorthandParser extends GenericShorthandParser { + + /** + * Default constructor. + */ + public BoxCornerPropShorthandParser() { + } + + /** + * Stores 1 or 2 values of same type representing rounded corner radii. + * If 2 value are present the first is the corner radius in the IP direction, + * the second in the BP direction. + * {@inheritDoc} + */ + @Override + protected Property convertValueForProperty(int propId, + Property property, + PropertyMaker maker, + PropertyList propertyList) + throws PropertyException { + String name = FOPropertyMapping.getPropertyName(propId); + Property p = null; + int count = property.getList().size(); + + if (name.indexOf("border-start") > -1 || name.indexOf("border-end") > -1) { + p = getElement(property, 0); + } else if (name.indexOf("border-before") > -1 || name.indexOf("border-after") > -1) { + p = getElement(property, count > 1 ? 1 : 0); + } + + // if p not null, try to convert it to a value of the correct type + if (p != null) { + return maker.convertShorthandProperty(propertyList, p, null); + } + return p; + } + +} diff --git a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java index 85a17ccd6..61c7b95cd 100644 --- a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java +++ b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java @@ -38,7 +38,6 @@ import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.expr.PropertyException; -import org.apache.fop.util.CompareUtil; /** * Stores all common border and padding properties. @@ -98,6 +97,8 @@ public class CommonBorderPaddingBackground { /** the "end" edge */ public static final int END = 3; + + /** * Utility class to express border info. */ @@ -110,32 +111,40 @@ public class CommonBorderPaddingBackground { private int mStyle; // Enum for border style private Color mColor; // Border color private CondLengthProperty mWidth; + private CondLengthProperty radiusStart; + private CondLengthProperty radiusEnd; private int hash = -1; /** * Hidden constructor */ - private BorderInfo(int style, CondLengthProperty width, Color color) { + private BorderInfo(int style, CondLengthProperty width, Color color, + CondLengthProperty radiusStart, CondLengthProperty radiusEnd) { mStyle = style; mWidth = width; mColor = color; + this.radiusStart = radiusStart; + this.radiusEnd = radiusEnd; } /** - * Returns a BorderInfo instance corresponding to the given values + * Returns a BorderInfo instance corresponding to the given values. * * @param style the border-style * @param width the border-width * @param color the border-color + * @param radiusStart the start radius for rounded borders + * @param radiusEnd the end radius for rounded borders * @return a cached BorderInfo instance */ - public static BorderInfo getInstance(int style, CondLengthProperty width, Color color) { - return CACHE.fetch(new BorderInfo(style, width, color)); + public static BorderInfo getInstance(int style, CondLengthProperty width, Color color, + CondLengthProperty radiusStart, CondLengthProperty radiusEnd) { + return CACHE.fetch(new BorderInfo(style, width, color, radiusStart, radiusEnd)); } /** - * @return the border-style + * @return the border-style */ public int getStyle() { return this.mStyle; @@ -170,6 +179,20 @@ public class CommonBorderPaddingBackground { } } + /** + * @return the border-*-start-radius + */ + public CondLengthProperty getRadiusStart() { + return this.radiusStart; + } + + /** + * @return the border-*-end-radius + */ + public CondLengthProperty getRadiusEnd() { + return this.radiusEnd; + } + @Override public String toString() { StringBuffer sb = new StringBuffer("BorderInfo"); @@ -179,6 +202,10 @@ public class CommonBorderPaddingBackground { sb.append(mColor); sb.append(", "); sb.append(mWidth); + sb.append(", "); + sb.append(radiusStart); + sb.append(", "); + sb.append(radiusEnd); sb.append("}"); return sb.toString(); } @@ -191,10 +218,12 @@ public class CommonBorderPaddingBackground { if (!(obj instanceof BorderInfo)) { return false; } - BorderInfo other = (BorderInfo) obj; - return CompareUtil.equal(mColor, other.mColor) - && mStyle == other.mStyle - && CompareUtil.equal(mWidth, other.mWidth); + BorderInfo bi = (BorderInfo)obj; + return (this.mColor == bi.mColor + && this.mStyle == bi.mStyle + && this.mWidth == bi.mWidth + && this.radiusStart == bi.radiusStart + && this.radiusEnd == bi.radiusEnd); } @Override @@ -204,18 +233,24 @@ public class CommonBorderPaddingBackground { hash = 37 * hash + (mColor == null ? 0 : mColor.hashCode()); hash = 37 * hash + mStyle; hash = 37 * hash + (mWidth == null ? 0 : mWidth.hashCode()); + hash = 37 * hash + (radiusStart == null ? 0 : radiusStart.hashCode()); + hash = 37 * hash + (radiusEnd == null ? 0 : radiusEnd.hashCode()); this.hash = hash; } return this.hash; } } + + + /** * A border info with style "none". Used as a singleton, in the collapsing-border model, * for elements which don't specify any border on some of their sides. */ - private static final BorderInfo DEFAULT_BORDER_INFO - = BorderInfo.getInstance(Constants.EN_NONE, new ConditionalNullLength(), null); + private static final BorderInfo DEFAULT_BORDER_INFO = BorderInfo.getInstance( + Constants.EN_NONE, new ConditionalNullLength(), null, new ConditionalNullLength(), + new ConditionalNullLength()); /** * A conditional length of value 0. Returned by the @@ -292,8 +327,11 @@ public class CommonBorderPaddingBackground { backgroundAttachment = pList.get(Constants.PR_BACKGROUND_ATTACHMENT).getEnum(); + + + Color bc = pList.get(Constants.PR_BACKGROUND_COLOR).getColor( - pList.getFObj().getUserAgent()); + pList.getFObj().getUserAgent()); if (bc.getAlpha() == 0) { backgroundColor = null; } else { @@ -319,22 +357,30 @@ public class CommonBorderPaddingBackground { Constants.PR_BORDER_BEFORE_COLOR, Constants.PR_BORDER_BEFORE_STYLE, Constants.PR_BORDER_BEFORE_WIDTH, - Constants.PR_PADDING_BEFORE); + Constants.PR_PADDING_BEFORE, + Constants.PR_X_BORDER_BEFORE_RADIUS_START, + Constants.PR_X_BORDER_BEFORE_RADIUS_END); initBorderInfo(pList, AFTER, Constants.PR_BORDER_AFTER_COLOR, Constants.PR_BORDER_AFTER_STYLE, Constants.PR_BORDER_AFTER_WIDTH, - Constants.PR_PADDING_AFTER); + Constants.PR_PADDING_AFTER, + Constants.PR_X_BORDER_AFTER_RADIUS_START, + Constants.PR_X_BORDER_AFTER_RADIUS_END); initBorderInfo(pList, START, Constants.PR_BORDER_START_COLOR, Constants.PR_BORDER_START_STYLE, Constants.PR_BORDER_START_WIDTH, - Constants.PR_PADDING_START); + Constants.PR_PADDING_START, + Constants.PR_X_BORDER_START_RADIUS_BEFORE, + Constants.PR_X_BORDER_START_RADIUS_AFTER); initBorderInfo(pList, END, Constants.PR_BORDER_END_COLOR, Constants.PR_BORDER_END_STYLE, Constants.PR_BORDER_END_WIDTH, - Constants.PR_PADDING_END); + Constants.PR_PADDING_END, + Constants.PR_X_BORDER_END_RADIUS_BEFORE, + Constants.PR_X_BORDER_END_RADIUS_AFTER); } @@ -346,8 +392,7 @@ public class CommonBorderPaddingBackground { * @return a CommonBorderPaddingBackground instance (cached if possible) * @throws PropertyException in case of an error */ - public static CommonBorderPaddingBackground getInstance(PropertyList pList) - throws PropertyException { + public static CommonBorderPaddingBackground getInstance(PropertyList pList) throws PropertyException { CommonBorderPaddingBackground newInstance = new CommonBorderPaddingBackground(pList); CommonBorderPaddingBackground cachedInstance = null; /* if padding-* and background-position-* resolve to absolute lengths @@ -400,21 +445,23 @@ public class CommonBorderPaddingBackground { } private void initBorderInfo(PropertyList pList, int side, - int colorProp, int styleProp, int widthProp, int paddingProp) - throws PropertyException { + int colorProp, int styleProp, int widthProp, int paddingProp, + int radiusStartProp, int radiusEndProp) + throws PropertyException { padding[side] = pList.get(paddingProp).getCondLength(); // If style = none, force width to 0, don't get Color (spec 7.7.20) int style = pList.get(styleProp).getEnum(); - if (style != Constants.EN_NONE) { - FOUserAgent ua = pList.getFObj().getUserAgent(); - setBorderInfo(BorderInfo.getInstance(style, + FOUserAgent ua = pList.getFObj().getUserAgent(); + setBorderInfo(BorderInfo.getInstance(style, pList.get(widthProp).getCondLength(), - pList.get(colorProp).getColor(ua)), side); - } - + pList.get(colorProp).getColor(ua), + pList.get(radiusStartProp).getCondLength(), + pList.get(radiusEndProp).getCondLength()), side); } + + /** * Sets a border. * @param info the border information @@ -538,6 +585,40 @@ public class CommonBorderPaddingBackground { } /** + * Returns the border corner radius of the starting edge + * i.e. the edge either adjacent to the before or start border. + * @param side the border side + * @param discard indicates whether the .conditionality component should be + * considered (end of a reference-area) + * @param context the context for percentage calculations + * @return the border radius of the of the starting corner + */ + public int getBorderRadiusStart(int side, boolean discard, PercentBaseContext context) { + if (borderInfo[side] == null) { + return 0; + } else { + return borderInfo[side].radiusStart.getLengthValue(context); + } + } + + /** + * Returns the border corner radius of the ending edge + * i.e. the edge either adjacent to the after or end border + * @param side the border side + * @param discard indicates whether the .conditionality component should be + * considered (end of a reference-area) + * @param context the context for percentage calculations + * @return the border radius of the of the ending corner + */ + public int getBorderRadiusEnd(int side, boolean discard, PercentBaseContext context) { + if (borderInfo[side] == null) { + return 0; + } else { + return borderInfo[side].radiusEnd.getLengthValue(context); + } + } + + /** * The border-color for the given side * * @param side one of {@link #BEFORE}, {@link #AFTER}, {@link #START}, {@link #END} @@ -601,9 +682,9 @@ public class CommonBorderPaddingBackground { */ public int getIPPaddingAndBorder(boolean discard, PercentBaseContext context) { return getPaddingStart(discard, context) - + getPaddingEnd(discard, context) - + getBorderStartWidth(discard) - + getBorderEndWidth(discard); + + getPaddingEnd(discard, context) + + getBorderStartWidth(discard) + + getBorderEndWidth(discard); } /** @@ -615,18 +696,18 @@ public class CommonBorderPaddingBackground { */ public int getBPPaddingAndBorder(boolean discard, PercentBaseContext context) { return getPaddingBefore(discard, context) + getPaddingAfter(discard, context) - + getBorderBeforeWidth(discard) + getBorderAfterWidth(discard); + + getBorderBeforeWidth(discard) + getBorderAfterWidth(discard); } @Override public String toString() { return "CommonBordersAndPadding (Before, After, Start, End):\n" - + "Borders: (" + getBorderBeforeWidth(false) + ", " + getBorderAfterWidth(false) + ", " - + getBorderStartWidth(false) + ", " + getBorderEndWidth(false) + ")\n" - + "Border Colors: (" + getBorderColor(BEFORE) + ", " + getBorderColor(AFTER) + ", " - + getBorderColor(START) + ", " + getBorderColor(END) + ")\n" - + "Padding: (" + getPaddingBefore(false, null) + ", " + getPaddingAfter(false, null) - + ", " + getPaddingStart(false, null) + ", " + getPaddingEnd(false, null) + ")\n"; + + "Borders: (" + getBorderBeforeWidth(false) + ", " + getBorderAfterWidth(false) + ", " + + getBorderStartWidth(false) + ", " + getBorderEndWidth(false) + ")\n" + + "Border Colors: (" + getBorderColor(BEFORE) + ", " + getBorderColor(AFTER) + ", " + + getBorderColor(START) + ", " + getBorderColor(END) + ")\n" + + "Padding: (" + getPaddingBefore(false, null) + ", " + getPaddingAfter(false, null) + + ", " + getPaddingStart(false, null) + ", " + getPaddingEnd(false, null) + ")\n"; } /** @@ -734,19 +815,19 @@ public class CommonBorderPaddingBackground { if (this == obj) { return true; } - if (!(obj instanceof CommonBorderPaddingBackground)) { + if (obj instanceof CommonBorderPaddingBackground) { + CommonBorderPaddingBackground cbpb = (CommonBorderPaddingBackground)obj; + return (this.backgroundAttachment == cbpb.backgroundAttachment + && this.backgroundColor == cbpb.backgroundColor + && this.backgroundImage.equals(cbpb.backgroundImage) + && this.backgroundPositionHorizontal == cbpb.backgroundPositionHorizontal + && this.backgroundPositionVertical == cbpb.backgroundPositionVertical + && this.backgroundRepeat == cbpb.backgroundRepeat + && Arrays.equals(borderInfo, cbpb.borderInfo) + && Arrays.equals(padding, cbpb.padding)); + } else { return false; } - - CommonBorderPaddingBackground other = (CommonBorderPaddingBackground) obj; - return backgroundAttachment == other.backgroundAttachment - && CompareUtil.equal(backgroundColor, other.backgroundColor) - && CompareUtil.equal(backgroundImage, other.backgroundImage) - && CompareUtil.equal(backgroundPositionHorizontal, backgroundPositionHorizontal) - && CompareUtil.equal(backgroundPositionVertical, other.backgroundPositionVertical) - && backgroundRepeat == other.backgroundRepeat - && Arrays.equals(borderInfo, other.borderInfo) - && Arrays.equals(padding, other.padding); } @Override diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java index 0ae499478..c4aac1d8c 100644 --- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java +++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java @@ -80,19 +80,19 @@ public final class TraitSetter { addBorderTrait(area, bpProps, isNotFirst, CommonBorderPaddingBackground.START, - BorderProps.SEPARATE, Trait.BORDER_START); + BorderProps.Mode.SEPARATE, Trait.BORDER_START, context); addBorderTrait(area, bpProps, isNotLast, CommonBorderPaddingBackground.END, - BorderProps.SEPARATE, Trait.BORDER_END); + BorderProps.Mode.SEPARATE, Trait.BORDER_END, context); addBorderTrait(area, bpProps, false, CommonBorderPaddingBackground.BEFORE, - BorderProps.SEPARATE, Trait.BORDER_BEFORE); + BorderProps.Mode.SEPARATE, Trait.BORDER_BEFORE, context); addBorderTrait(area, bpProps, false, CommonBorderPaddingBackground.AFTER, - BorderProps.SEPARATE, Trait.BORDER_AFTER); + BorderProps.Mode.SEPARATE, Trait.BORDER_AFTER, context); } /* @@ -104,14 +104,16 @@ public final class TraitSetter { */ private static void addBorderTrait(Area area, CommonBorderPaddingBackground bpProps, - boolean discard, int side, int mode, - Integer trait) { - int borderWidth = bpProps.getBorderWidth(side, discard); - if (borderWidth > 0) { - area.addTrait(trait, - new BorderProps(bpProps.getBorderStyle(side), - borderWidth, bpProps.getBorderColor(side), - mode)); + + boolean discard, int side, BorderProps.Mode mode, + Integer traitCode, PercentBaseContext context) { + int width = bpProps.getBorderWidth(side, discard); + int radiusStart = bpProps.getBorderRadiusStart(side, discard, context); + int radiusEnd = bpProps.getBorderRadiusEnd(side, discard, context); + if (width > 0 || radiusStart > 0 || radiusEnd > 0) { + area.addTrait(traitCode, new BorderProps(bpProps.getBorderStyle(side), width, radiusStart, + radiusEnd, bpProps.getBorderColor(side), mode)); + } } @@ -126,19 +128,19 @@ public final class TraitSetter { */ public static void addBorders(Area area, CommonBorderPaddingBackground borderProps, PercentBaseContext context) { - BorderProps bps = getBorderProps(borderProps, CommonBorderPaddingBackground.BEFORE); + BorderProps bps = getBorderProps(borderProps, CommonBorderPaddingBackground.BEFORE, context); if (bps != null) { area.addTrait(Trait.BORDER_BEFORE, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.AFTER); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.AFTER, context); if (bps != null) { area.addTrait(Trait.BORDER_AFTER, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.START); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.START, context); if (bps != null) { area.addTrait(Trait.BORDER_START, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.END); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.END, context); if (bps != null) { area.addTrait(Trait.BORDER_END, bps); } @@ -163,22 +165,23 @@ public final class TraitSetter { boolean discardBefore, boolean discardAfter, boolean discardStart, boolean discardEnd, PercentBaseContext context) { - BorderProps bps = getBorderProps(borderProps, CommonBorderPaddingBackground.BEFORE); + BorderProps bps = getBorderProps(borderProps, CommonBorderPaddingBackground.BEFORE, context); if (bps != null && !discardBefore) { area.addTrait(Trait.BORDER_BEFORE, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.AFTER); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.AFTER, context); if (bps != null && !discardAfter) { area.addTrait(Trait.BORDER_AFTER, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.START); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.START, context); if (bps != null && !discardStart) { area.addTrait(Trait.BORDER_START, bps); } - bps = getBorderProps(borderProps, CommonBorderPaddingBackground.END); + bps = getBorderProps(borderProps, CommonBorderPaddingBackground.END, context); if (bps != null && !discardEnd) { area.addTrait(Trait.BORDER_END, bps); } + } /** @@ -262,15 +265,14 @@ public final class TraitSetter { } - private static BorderProps getBorderProps(CommonBorderPaddingBackground bordProps, int side) { + private static BorderProps getBorderProps(CommonBorderPaddingBackground bordProps, + int side, PercentBaseContext context) { int width = bordProps.getBorderWidth(side, false); - if (width != 0) { - BorderProps bps; - bps = new BorderProps(bordProps.getBorderStyle(side), - width, - bordProps.getBorderColor(side), - BorderProps.SEPARATE); - return bps; + int radiusStart = bordProps.getBorderRadiusStart(side, false, context); + int radiusEnd = bordProps.getBorderRadiusEnd(side, false, context); + if (width != 0 || radiusStart != 0 || radiusEnd != 0) { + return new BorderProps(bordProps.getBorderStyle(side), width, radiusStart, radiusEnd, + bordProps.getBorderColor(side), BorderProps.Mode.SEPARATE); } else { return null; } @@ -280,13 +282,14 @@ public final class TraitSetter { assert borderInfo != null; int width = borderInfo.getRetainedWidth(); if (width != 0) { - return new BorderProps(borderInfo.getStyle(), width, borderInfo.getColor(), - (outer ? BorderProps.COLLAPSE_OUTER : BorderProps.COLLAPSE_INNER)); + return BorderProps.makeRectangular(borderInfo.getStyle(), width, borderInfo.getColor(), + (outer ? BorderProps.Mode.COLLAPSE_OUTER : BorderProps.Mode.COLLAPSE_INNER)); } else { return null; } } + /** * Add background to an area. This method is mainly used by table-related layout * managers to add background for column, body or row. Since the area corresponding to diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java index 8a6b0a703..fbd753d58 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -597,9 +597,9 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager blocks[i][j].addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); blocks[i][j].setPositioning(Block.ABSOLUTE); } - blocks[i][j].addTrait(side, new BorderProps(border.getStyle(), + blocks[i][j].addTrait(side, BorderProps.makeRectangular(border.getStyle(), border.getRetainedWidth(), border.getColor(), - outer ? BorderProps.COLLAPSE_OUTER : BorderProps.COLLAPSE_INNER)); + outer ? BorderProps.Mode.COLLAPSE_OUTER : BorderProps.Mode.COLLAPSE_INNER)); } private static void adjustXOffset(Block block, int amount) { diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index cb66f2abb..b87f5c2ca 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -32,7 +32,6 @@ import org.apache.batik.parser.AWTTransformProducer; import org.apache.xmlgraphics.image.loader.ImageSize; import org.apache.xmlgraphics.util.QName; -import org.apache.xmlgraphics.util.UnitConv; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Area; @@ -50,6 +49,7 @@ import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.FontMetrics; import org.apache.fop.traits.BorderProps; +import org.apache.fop.util.UnitConv; /** * Abstract base class for renderers like PDF and PostScript where many painting operations @@ -166,11 +166,20 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { BorderProps bpsStart = (BorderProps)borderArea.getTrait(Trait.BORDER_START); BorderProps bpsEnd = (BorderProps)borderArea.getTrait(Trait.BORDER_END); + Trait.Background backgroundTrait + = (Trait.Background)backgroundArea.getTrait(Trait.BACKGROUND); + drawBackground(startx, starty, width, height, (Trait.Background) backgroundArea.getTrait(Trait.BACKGROUND), - bpsBefore, bpsAfter, bpsStart, bpsEnd, backgroundArea.getBidiLevel()); + bpsBefore, bpsAfter, bpsStart, bpsEnd, backgroundArea.getBidiLevel()); + // TODO what is the default bg color? Should we serialize it? + Color bg = Color.white; + if (backgroundTrait != null && backgroundTrait.getColor() != null) { + bg = backgroundTrait.getColor(); + } + drawBorders(startx, starty, width, height, - bpsBefore, bpsAfter, bpsStart, bpsEnd, borderArea.getBidiLevel()); + bpsBefore, bpsAfter, bpsStart, bpsEnd, backgroundArea.getBidiLevel(), bg); } /** @@ -247,14 +256,17 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { paddRectHeight -= bpsBottom.width / 1000f; } + saveGraphicsState(); + + clipBackground(sx, sy, paddRectWidth, paddRectHeight, bpsTop, bpsBottom, bpsLeft, bpsRight); + if (back.getColor() != null) { updateColor(back.getColor(), true); fillRect(sx, sy, paddRectWidth, paddRectHeight); } + if (back.getImageInfo() != null) { ImageSize imageSize = back.getImageInfo().getSize(); - saveGraphicsState(); - clipRect(sx, sy, paddRectWidth, paddRectHeight); int horzCount = (int)((paddRectWidth * 1000 / imageSize.getWidthMpt()) + 1.0f); int vertCount = (int)((paddRectHeight @@ -290,13 +302,36 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { drawImage(back.getURL(), pos); } } - - restoreGraphicsState(); } + restoreGraphicsState(); } } /** + * TODO represent border related parameters in a class + * Clip the background to the inner border. + * This draws the border traits given the position and the traits. + * + * @param startx the start x position + * @param starty the start y position + * @param width the width of the area + * @param height the height of the area + * @param bpsBefore the border-before traits + * @param bpsAfter the border-after traits + * @param bpsStart the border-start traits + * @param bpsEnd the border-end traits + * @param innerBackgroundColor the background color of the block + */ + protected void clipBackground (float startx, float starty, + float width, float height, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + + clipRect(startx, starty, width, height); + + } + + /** * Draw the borders. * This draws the border traits given the position and the traits. * @@ -309,11 +344,12 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param bpsStart the border traits associated with start edge * @param bpsEnd the border traits associated with end edge * @param level of bidirectional embedding + * @param innerBackgroundColor the background color of the block */ protected void drawBorders( // CSOK: ParameterNumber float startx, float starty, float width, float height, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd, int level) { + BorderProps bpsStart, BorderProps bpsEnd, int level, Color innerBackgroundColor) { Rectangle2D.Float borderRect = new Rectangle2D.Float(startx, starty, width, height); BorderProps bpsTop = bpsBefore; BorderProps bpsBottom = bpsAfter; @@ -326,7 +362,7 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { bpsLeft = bpsEnd; bpsRight = bpsStart; } - drawBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight); + drawBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight, innerBackgroundColor); } private static final int TOP = 0; @@ -341,10 +377,12 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param bpsBottom the border traits associated with bottom edge * @param bpsLeft the border specification on the left edge * @param bpsRight the border specification on the right edge + * @param innerBackgroundColor the background color of the block */ protected void drawBorders( // CSOK: MethodLength Rectangle2D.Float borderRect, - BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight) { + BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight, + Color innerBackgroundColor) { //TODO generalize each of the four conditions into using a parameterized drawBorder() boolean[] border = new boolean[] { (bpsTop != null), (bpsRight != null), @@ -391,11 +429,12 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { moveTo(sx1, clipy); float sx1a = sx1; float ex1a = ex1; - if (bpsTop.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + + if (isCollapseOuter(bpsTop)) { + if (isCollapseOuter(bpsLeft)) { sx1a -= clipw[LEFT]; } - if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsRight)) { ex1a += clipw[RIGHT]; } lineTo(sx1a, outery); @@ -425,11 +464,11 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { moveTo(clipx, sy1); float sy1a = sy1; float ey1a = ey1; - if (bpsRight.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsRight)) { + if (isCollapseOuter(bpsTop)) { sy1a -= clipw[TOP]; } - if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsBottom)) { ey1a += clipw[BOTTOM]; } lineTo(outerx, sy1a); @@ -459,11 +498,11 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { moveTo(ex1, clipy); float sx1a = sx1; float ex1a = ex1; - if (bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsBottom)) { + if (isCollapseOuter(bpsLeft)) { sx1a -= clipw[LEFT]; } - if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsRight)) { ex1a += clipw[RIGHT]; } lineTo(ex1a, outery); @@ -493,11 +532,11 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { moveTo(clipx, ey1); float sy1a = sy1; float ey1a = ey1; - if (bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsLeft)) { + if (isCollapseOuter(bpsTop)) { sy1a -= clipw[TOP]; } - if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + if (isCollapseOuter(bpsBottom)) { ey1a += clipw[BOTTOM]; } lineTo(outerx, ey1a); @@ -513,6 +552,10 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { } } + private boolean isCollapseOuter(BorderProps bp) { + return bp != null && bp.isCollapseOuter(); + } + /** * Common method to render the background and borders for any inline area. * The all borders and padding are drawn outside the specified area. diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java index 35db42ac4..cd8304b4b 100644 --- a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -24,8 +24,10 @@ import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.io.IOException; import java.net.URI; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.fop.afp.AFPDitheredRectanglePainter; @@ -85,6 +87,13 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler private Map<String, PageSegmentDescriptor> pageSegmentMap = new java.util.HashMap<String, PageSegmentDescriptor>(); + + // Rounded corners are cached at the document level + private Map<String, String> roundedCornerNameCache + = new HashMap<String, String>(); + + private int roundedCornerCount = 0; + private static enum Location { ELSEWHERE, IN_DOCUMENT_HEADER, FOLLOWING_PAGE_SEQUENCE, IN_PAGE_HEADER } @@ -398,6 +407,49 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } } + /** + * Corner images can be reused by storing at the document level in the AFP + * The cache is used to map cahced images to caller generated descriptions of the corner + * @param cornerKey caller's identifier for the corner + * @return document id of the corner image + */ + public String cacheRoundedCorner(String cornerKey) { + + // Make a unique id + StringBuffer idBuilder = new StringBuffer("RC"); + + String tmp = Integer.toHexString(roundedCornerCount).toUpperCase(Locale.ENGLISH); + if (tmp.length() > 6) { + //Will never happen + //log.error("Rounded corners cache capacity exceeded"); + //We should get a visual clue + roundedCornerCount = 0; + tmp = "000000"; + } else if (tmp.length() < 6) { + for (int i = 0; i < 6 - tmp.length(); i++) { + idBuilder.append("0"); + } + idBuilder.append(tmp); + } + + roundedCornerCount++; + + String id = idBuilder.toString(); + + //cache the corner id + roundedCornerNameCache.put(cornerKey, id); + return id; + } + /** + * This method returns the an id that identifies a cached corner or null if non existent + * @param cornerKey caller's identifier for the corner + * @return document id of the corner image + */ + public String getCachedRoundedCorner(String cornerKey) { + return (String)roundedCornerNameCache.get(cornerKey); + } + + private void handleNOP(AFPPageSetup nop) { String content = nop.getContent(); if (content != null) { diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandler.java b/src/java/org/apache/fop/render/afp/AFPImageHandler.java index 118207d38..6404924a1 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandler.java @@ -19,7 +19,10 @@ package org.apache.fop.render.afp; +import java.awt.Point; import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; +import java.io.IOException; import java.util.Map; import org.apache.fop.afp.AFPDataObjectInfo; @@ -37,21 +40,60 @@ public abstract class AFPImageHandler implements ImageHandlerBase { private static final int Y = 1; /** foreign attribute reader */ - private final AFPForeignAttributeReader foreignAttributeReader + private static final AFPForeignAttributeReader FOREIGN_ATTRIBUTE_READER = new AFPForeignAttributeReader(); /** + * Generates an intermediate AFPDataObjectInfo that is later used to construct + * the appropriate data object in the AFP DataStream. + * + * @param rendererImageInfo the renderer image info + * @return a data object info object + * @throws IOException thrown if an I/O exception of some sort has occurred. + */ + public AFPDataObjectInfo generateDataObjectInfo( + AFPRendererImageInfo rendererImageInfo) throws IOException { + AFPDataObjectInfo dataObjectInfo = createDataObjectInfo(); + + // set resource information + dataObjectInfo.setResourceInfo(createResourceInformation( + rendererImageInfo.getURI(), + rendererImageInfo.getForeignAttributes())); + + + Point origin = rendererImageInfo.getOrigin(); + Rectangle2D position = rendererImageInfo.getPosition(); + int srcX = Math.round(origin.x + (float)position.getX()); + int srcY = Math.round(origin.y + (float)position.getY()); + Rectangle targetRect = new Rectangle( + srcX, + srcY, + (int)Math.round(position.getWidth()), + (int)Math.round(position.getHeight())); + + AFPRendererContext rendererContext + = (AFPRendererContext)rendererImageInfo.getRendererContext(); + AFPInfo afpInfo = rendererContext.getInfo(); + AFPPaintingState paintingState = afpInfo.getPaintingState(); + + dataObjectInfo.setObjectAreaInfo(createObjectAreaInfo(paintingState, targetRect)); + + return dataObjectInfo; + } + + /** * Sets resource information on the data object info. - * @param dataObjectInfo the data object info instance * @param uri the image's URI (or null if no URI is available) * @param foreignAttributes a Map of foreign attributes (or null) + * @return the resource information object */ - protected void setResourceInformation(AFPDataObjectInfo dataObjectInfo, + public static AFPResourceInfo createResourceInformation( String uri, Map foreignAttributes) { AFPResourceInfo resourceInfo - = foreignAttributeReader.getResourceInfo(foreignAttributes); + = FOREIGN_ATTRIBUTE_READER.getResourceInfo(foreignAttributes); resourceInfo.setUri(uri); - dataObjectInfo.setResourceInfo(resourceInfo); + + return resourceInfo; } /** diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java index f60e271cb..2a0db08bb 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java @@ -85,9 +85,10 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler implements ImageH AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)createDataObjectInfo(); // set resource information - setResourceInformation(graphicsObjectInfo, + + graphicsObjectInfo.setResourceInfo(createResourceInformation( image.getInfo().getOriginalURI(), - afpContext.getForeignAttributes()); + afpContext.getForeignAttributes())); // Positioning graphicsObjectInfo.setObjectAreaInfo( diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java index e318c49fb..7508c8ca0 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java @@ -94,9 +94,9 @@ public class AFPImageHandlerRawJPEG extends AFPImageHandler implements ImageHand AFPPaintingState paintingState = afpContext.getPaintingState(); // set resource information - setResourceInformation(imageObjectInfo, + imageObjectInfo.setResourceInfo(createResourceInformation( image.getInfo().getOriginalURI(), - afpContext.getForeignAttributes()); + afpContext.getForeignAttributes())); setDefaultResourceLevel(imageObjectInfo, afpContext.getResourceManager()); // Positioning diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java index e5f41d232..3adbd68f6 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java @@ -111,9 +111,9 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima AFPPaintingState paintingState = afpContext.getPaintingState(); // set resource information - setResourceInformation(imageObjectInfo, + imageObjectInfo.setResourceInfo(createResourceInformation( image.getInfo().getOriginalURI(), - afpContext.getForeignAttributes()); + afpContext.getForeignAttributes())); setDefaultResourceLevel(imageObjectInfo, afpContext.getResourceManager()); // Positioning @@ -201,6 +201,7 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima ImageInfo imageInfo = imageRendered.getInfo(); this.intrinsicSize = imageInfo.getSize(); this.effIntrinsicSize = intrinsicSize; + effIntrinsicSize.setSizeInPixels(renderedImage.getWidth(), renderedImage.getHeight()); AFPResourceInfo resourceInfo = imageObjectInfo.getResourceInfo(); this.usePageSegments = useFS10 && !resourceInfo.getLevel().isInline(); diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 7d659a8b4..3a41e5feb 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -21,23 +21,37 @@ package org.apache.fop.render.afp; import java.awt.Color; import java.awt.Dimension; +import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.security.MessageDigest; import java.util.Map; import org.w3c.dom.Document; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageProcessingHints; import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPEventProducer; +import org.apache.fop.afp.AFPObjectAreaInfo; import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPResourceInfo; import org.apache.fop.afp.AFPUnitConverter; import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; @@ -55,9 +69,11 @@ import org.apache.fop.afp.util.AFPResourceAccessor; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.Typeface; +import org.apache.fop.render.ImageHandlerUtil; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; import org.apache.fop.render.intermediate.BorderPainter; +import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFUtil; @@ -70,12 +86,12 @@ import org.apache.fop.util.CharUtilities; */ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { - //** logging instance */ - //private static Log log = LogFactory.getLog(AFPPainter.class); - private static final int X = 0; + private static final int Y = 1; + private final GraphicsPainter graphicsPainter; + /** the border painter */ private final AFPBorderPainterAdapter borderPainter; /** the rectangle painter */ @@ -93,8 +109,9 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { public AFPPainter(AFPDocumentHandler documentHandler) { super(documentHandler); this.state = IFState.create(); - this.borderPainter = new AFPBorderPainterAdapter( + this.graphicsPainter = new AFPGraphicsPainter( new AFPBorderPainter(getPaintingState(), getDataStream())); + this.borderPainter = new AFPBorderPainterAdapter(graphicsPainter, this, documentHandler); this.rectanglePainter = documentHandler.createRectanglePainter(); this.unitConv = getPaintingState().getUnitConverter(); this.eventProducer = AFPEventProducer.Provider.get(getUserAgent().getEventBroadcaster()); @@ -179,13 +196,13 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { /** {@inheritDoc} */ @Override protected RenderingContext createRenderingContext() { - AFPRenderingContext psContext = new AFPRenderingContext( + AFPRenderingContext renderingContext = new AFPRenderingContext( getUserAgent(), getDocumentHandler().getResourceManager(), getPaintingState(), getFontInfo(), getContext().getForeignAttributes()); - return psContext; + return renderingContext; } /** {@inheritDoc} */ @@ -224,6 +241,32 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { } /** {@inheritDoc} */ + protected void drawImage(Image image, Rectangle rect, + RenderingContext context, boolean convert, Map additionalHints) + throws IOException, ImageException { + + + AFPRenderingContext afpContext = (AFPRenderingContext) context; + + AFPResourceInfo resourceInfo = AFPImageHandler.createResourceInformation( + image.getInfo().getOriginalURI(), + afpContext.getForeignAttributes()); + + //Check if the image is cached before processing it again + if (afpContext.getResourceManager().isObjectCached(resourceInfo)) { + + AFPObjectAreaInfo areaInfo = AFPImageHandler.createObjectAreaInfo( + afpContext.getPaintingState(), rect); + + afpContext.getResourceManager().includeCachedObject(resourceInfo, areaInfo); + + } else { + super.drawImage(image, rect, context, convert, additionalHints); + } + + } + + /** {@inheritDoc} */ public void drawImage(Document doc, Rectangle rect) throws IFException { drawImageUsingDocument(doc, rect); } @@ -244,7 +287,7 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { } if (rect.width != 0 && rect.height != 0) { if (fill instanceof Color) { - getPaintingState().setColor((Color)fill); + getPaintingState().setColor((Color) fill); } else { throw new UnsupportedOperationException("Non-Color paints NYI"); } @@ -258,96 +301,576 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { } } - /** {@inheritDoc} */ @Override public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException { + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top != null || bottom != null || left != null || right != null) { - try { - this.borderPainter.drawBorders(rect, top, bottom, left, right); - } catch (IOException ife) { - throw new IFException("IO error while painting borders", ife); + this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor); + } + } + + + private static final class AFPGraphicsPainter implements GraphicsPainter { + + private final AFPBorderPainter graphicsPainter; + + private AFPGraphicsPainter(AFPBorderPainter delegate) { + this.graphicsPainter = delegate; + } + + public void drawBorderLine(int x1, int y1, int x2, int y2, + boolean horz, boolean startOrBefore, int style, Color color) + throws IOException { + BorderPaintingInfo borderPaintInfo = new BorderPaintingInfo( + toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), + horz, style, color); + graphicsPainter.paint(borderPaintInfo); + } + + private float toPoints(int mpt) { + return mpt / 1000f; + } + + public void drawLine(Point start, Point end, int width, + Color color, RuleStyle style) throws IOException { + if (start.y != end.y) { + //TODO Support arbitrary lines if necessary + throw new UnsupportedOperationException("Can only deal with horizontal lines right now"); } + //Simply delegates to drawBorderLine() as AFP line painting is not very sophisticated. + int halfWidth = width / 2; + drawBorderLine(start.x, start.y - halfWidth, end.x, start.y + halfWidth, + true, true, style.getEnumValue(), color); + } + + public void moveTo(int x, int y) throws IOException { + } + + public void lineTo(int x, int y) throws IOException { } + + public void arcTo(double startAngle, double endAngle, int cx, int cy, + int width, int height) throws IOException { + } + + public void rotateCoordinates(double angle) throws IOException { + throw new UnsupportedOperationException("Cannot handle coordinate rotation"); + } + + public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + throw new UnsupportedOperationException("Cannot handle coordinate translation"); + } + + public void scaleCoordinates(float xScale, float yScale) throws IOException { + throw new UnsupportedOperationException("Cannot handle coordinate scaling"); + } + + public void closePath() throws IOException { + } + + public void clip() throws IOException { + } + + public void saveGraphicsState() throws IOException { + } + + public void restoreGraphicsState() throws IOException { + } + + } //TODO Try to resolve the name-clash between the AFPBorderPainter in the afp package //and this one. Not done for now to avoid a lot of re-implementation and code duplication. private static class AFPBorderPainterAdapter extends BorderPainter { - private final AFPBorderPainter delegate; + private final class BorderImagePainter implements Graphics2DImagePainter { + private final double cornerCorrectionFactor; + private final Rectangle borderRect; + private final BorderProps bpsStart; + private final BorderProps bpsEnd; + private final BorderProps bpsBefore; + private final BorderProps bpsAfter; + private final boolean[] roundCorner; + private final Color innerBackgroundColor; + + /* TODO represent border related parameters in a class */ + private BorderImagePainter(double cornerCorrectionFactor, Rectangle borderRect, + BorderProps bpsStart, BorderProps bpsEnd, + BorderProps bpsBefore, BorderProps bpsAfter, + boolean[] roundCorner, Color innerBackgroundColor) { + this.cornerCorrectionFactor = cornerCorrectionFactor; + this.borderRect = borderRect; + this.bpsStart = bpsStart; + this.bpsBefore = bpsBefore; + this.roundCorner = roundCorner; + this.bpsEnd = bpsEnd; + this.bpsAfter = bpsAfter; + this.innerBackgroundColor = innerBackgroundColor; + } + + public void paint(Graphics2D g2d, Rectangle2D area) { + + //background + Area background = new Area(area); + Area cornerRegion = new Area(); + Area[] cornerBorder = new Area[]{new Area(), new Area(), new Area(), new Area()}; + + if (roundCorner[TOP_LEFT]) { + AffineTransform transform = new AffineTransform(); + int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusStart()); + int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusStart()); + + int beforeWidth = bpsBefore.width; + int startWidth = bpsStart.width; + int corner = TOP_LEFT; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + + cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[TOP_RIGHT]) { + AffineTransform transform + = new AffineTransform(-1, 0, 0, 1, borderRect.width, 0); + + int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusEnd()); + int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusStart()); + + int beforeWidth = bpsBefore.width; + int startWidth = bpsEnd.width; + int corner = TOP_RIGHT; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + + cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[BOTTOM_RIGHT]) { + AffineTransform transform = new AffineTransform(-1, 0, 0, -1, + borderRect.width, borderRect.height); + + int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusEnd()); + int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusEnd()); + + int beforeWidth = bpsAfter.width; + int startWidth = bpsEnd.width; + int corner = BOTTOM_RIGHT; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[BOTTOM_LEFT]) { + AffineTransform transform + = new AffineTransform(1, 0, 0, -1, 0, borderRect.height); + + int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusStart()); + int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusEnd()); + + int beforeWidth = bpsAfter.width; + int startWidth = bpsStart.width; + int corner = BOTTOM_LEFT; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + g2d.setColor(innerBackgroundColor); + g2d.fill(background); + + //paint the borders + //TODO refactor to repeating code into method + if (bpsBefore != null && bpsBefore.width > 0) { + GeneralPath borderPath = new GeneralPath(); + borderPath.moveTo(0, 0); + borderPath.lineTo(borderRect.width, 0); + borderPath.lineTo( + borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width), + bpsBefore.width); + borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width, bpsBefore.width); + + Area border = new Area(borderPath); + + border.subtract(cornerRegion); + + g2d.setColor(bpsBefore.color); + g2d.fill(border); + g2d.fill(cornerBorder[TOP]); + } + + if (bpsEnd != null && bpsEnd.width > 0) { + GeneralPath borderPath = new GeneralPath(); + borderPath.moveTo(borderRect.width, 0); + borderPath.lineTo(borderRect.width, borderRect.height); + borderPath.lineTo( + borderRect.width - bpsEnd.width, + borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width)); + borderPath.lineTo( + borderRect.width - bpsEnd.width, + bpsBefore == null ? 0 : bpsBefore.width); + + Area border = new Area(borderPath); + + border.subtract(cornerRegion); + + g2d.setColor(bpsEnd.color); + g2d.fill(border); + g2d.fill(cornerBorder[RIGHT]); + } + + if (bpsAfter != null && bpsAfter.width > 0) { + GeneralPath borderPath = new GeneralPath(); + borderPath.moveTo(0, borderRect.height); + borderPath.lineTo(borderRect.width, borderRect.height); + borderPath.lineTo( + borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width), + borderRect.height - bpsAfter.width); + borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width, + borderRect.height - bpsAfter.width); + Area border = new Area(borderPath); + + border.subtract(cornerRegion); + + g2d.setColor(bpsAfter.color); + g2d.fill(border); + g2d.fill(cornerBorder[BOTTOM]); + } + + if (bpsStart != null && bpsStart.width > 0) { + + GeneralPath borderPath = new GeneralPath(); + borderPath.moveTo(bpsStart.width, + bpsBefore == null ? 0 : bpsBefore.width); + borderPath.lineTo(bpsStart.width, + borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width)); + borderPath.lineTo(0, borderRect.height); + borderPath.lineTo(0, 0); + + Area border = new Area(borderPath); + + border.subtract(cornerRegion); + + g2d.setColor(bpsStart.color); + g2d.fill(border); + g2d.fill(cornerBorder[LEFT]); + } + } - public AFPBorderPainterAdapter(AFPBorderPainter borderPainter) { - this.delegate = borderPainter; + public Dimension getImageSize() { + return borderRect.getSize(); + } } - @Override - protected void clip() throws IOException { - //not supported by AFP + private final AFPPainter painter; + private final AFPDocumentHandler documentHandler; + + public AFPBorderPainterAdapter(GraphicsPainter graphicsPainter, AFPPainter painter, + AFPDocumentHandler documentHandler) { + super(graphicsPainter); + this.painter = painter; + this.documentHandler = documentHandler; } - @Override - protected void closePath() throws IOException { - //used for clipping only, so not implemented + public void drawBorders(final Rectangle borderRect, + final BorderProps bpsBefore, final BorderProps bpsAfter, + final BorderProps bpsStart, final BorderProps bpsEnd, Color innerBackgroundColor) + throws IFException { + drawRoundedCorners(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, innerBackgroundColor); } - @Override - protected void moveTo(int x, int y) throws IOException { - //used for clipping only, so not implemented + private boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + return !hasRoundedCorners(bpsBefore, bpsAfter, bpsStart, bpsEnd); } - @Override - protected void lineTo(int x, int y) throws IOException { - //used for clipping only, so not implemented + private boolean hasRoundedCorners(final BorderProps bpsBefore, final BorderProps bpsAfter, + final BorderProps bpsStart, final BorderProps bpsEnd) { + return ((bpsStart == null ? false : bpsStart.getRadiusStart() > 0) + && (bpsBefore == null ? false : bpsBefore.getRadiusStart() > 0)) + || ((bpsBefore == null ? false : bpsBefore.getRadiusEnd() > 0) + && (bpsEnd == null ? false : bpsEnd.getRadiusStart() > 0)) + || ((bpsEnd == null ? false : bpsEnd.getRadiusEnd() > 0) + && (bpsAfter == null ? false : bpsAfter.getRadiusEnd() > 0)) + || ((bpsAfter == null ? false : bpsAfter.getRadiusStart() > 0) + && (bpsStart == null ? false : bpsStart.getRadiusEnd() > 0)); } - @Override - protected void saveGraphicsState() throws IOException { - //used for clipping only, so not implemented + private void drawRoundedCorners(final Rectangle borderRect, + final BorderProps bpsBefore, final BorderProps bpsAfter, + final BorderProps bpsStart, final BorderProps bpsEnd, + final Color innerBackgroundColor) throws IFException { + final double cornerCorrectionFactor = calculateCornerCorrectionFactor(borderRect.width, + borderRect.height, bpsBefore, bpsAfter, bpsStart, bpsEnd); + final boolean[] roundCorner = new boolean[]{ + bpsBefore != null && bpsStart != null + && bpsBefore.getRadiusStart() > 0 + && bpsStart.getRadiusStart() > 0 + && isNotCollapseOuter(bpsBefore) + && isNotCollapseOuter(bpsStart), + bpsEnd != null && bpsBefore != null + && bpsEnd.getRadiusStart() > 0 + && bpsBefore.getRadiusEnd() > 0 + && isNotCollapseOuter(bpsEnd) + && isNotCollapseOuter(bpsBefore), + bpsEnd != null && bpsAfter != null + && bpsEnd.getRadiusEnd() > 0 + && bpsAfter.getRadiusEnd() > 0 + && isNotCollapseOuter(bpsEnd) + && isNotCollapseOuter(bpsAfter), + bpsStart != null && bpsAfter != null + && bpsStart.getRadiusEnd() > 0 + && bpsAfter.getRadiusStart() > 0 + && isNotCollapseOuter(bpsStart) + && isNotCollapseOuter(bpsAfter) + }; + + if (!roundCorner[TOP_LEFT] && !roundCorner[TOP_RIGHT] + && !roundCorner[BOTTOM_RIGHT] && !roundCorner[BOTTOM_LEFT]) { + try { + drawRectangularBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IOException ioe) { + throw new IFException("IO error drawing borders", ioe); + } + return; + } + + String areaKey = makeKey(borderRect, + bpsBefore, bpsEnd, bpsAfter, + bpsStart, innerBackgroundColor); + + Graphics2DImagePainter painter = null; + String name = documentHandler.getCachedRoundedCorner(areaKey); + + if (name == null) { + + name = documentHandler.cacheRoundedCorner(areaKey); + + painter = new BorderImagePainter(cornerCorrectionFactor, borderRect, + bpsStart, bpsEnd, bpsBefore, bpsAfter, + roundCorner, innerBackgroundColor); + } + paintCornersAsBitmap(painter, borderRect, name); } - @Override - protected void restoreGraphicsState() throws IOException { - //used for clipping only, so not implemented + private boolean isNotCollapseOuter(BorderProps bp) { + return !bp.isCollapseOuter(); } - private float toPoints(int mpt) { - return mpt / 1000f; + private Area makeCornerClip(final int beforeRadius, final int startRadius, + final AffineTransform transform) { + + Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); + + Area clip = new Area(clipR); + + Ellipse2D.Double e = new Ellipse2D.Double(); + e.x = 0; + e.y = 0; + e.width = 2 * startRadius; + e.height = 2 * beforeRadius; + + clip.subtract(new Area(e)); + + clip.transform(transform); + return clip; } - @Override - protected void drawBorderLine( // CSOK: ParameterNumber - int x1, int y1, int x2, int y2, boolean horz, - boolean startOrBefore, int style, Color color) throws IOException { - BorderPaintingInfo borderPaintInfo = new BorderPaintingInfo( - toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), - horz, style, color); - delegate.paint(borderPaintInfo); + + private Area makeCornerBorderBPD(final int beforeRadius, final int startRadius, + final int beforeWidth, final int startWidth, final AffineTransform transform) { + + Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); + + Ellipse2D.Double e = new Ellipse2D.Double(); + e.x = 0; + e.y = 0; + e.width = 2 * startRadius; + e.height = 2 * beforeRadius; + + Ellipse2D.Double i = new Ellipse2D.Double(); + i.x = startWidth; + i.y = beforeWidth; + i.width = 2 * (startRadius - startWidth); + i.height = 2 * (beforeRadius - beforeWidth); + + Area clip = new Area(e); + clip.subtract(new Area(i)); + clip.intersect(new Area(clipR)); + + GeneralPath cut = new GeneralPath(); + cut.moveTo(0, 0); + cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth); + cut.lineTo(startRadius, 0); + clip.intersect(new Area(cut)); + clip.transform(transform); + return clip; } - @Override - public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) - throws IOException { - if (start.y != end.y) { - //TODO Support arbitrary lines if necessary - throw new UnsupportedOperationException( - "Can only deal with horizontal lines right now"); + + private Area makeCornerBorderIPD(final int beforeRadius, final int startRadius, + final int beforeWidth, final int startWidth, final AffineTransform transform) { + + Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); + + + Ellipse2D.Double e = new Ellipse2D.Double(); + e.x = 0; + e.y = 0; + e.width = 2 * startRadius; + e.height = 2 * beforeRadius; + + Ellipse2D.Double i = new Ellipse2D.Double(); + i.x = startWidth; + i.y = beforeWidth; + i.width = 2 * (startRadius - startWidth); + i.height = 2 * (beforeRadius - beforeWidth); + + Area clip = new Area(e); + clip.subtract(new Area(i)); + clip.intersect(new Area(clipR)); + + GeneralPath cut = new GeneralPath(); + cut.moveTo(0, 0); + cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth); + cut.lineTo(startRadius, 0); + clip.subtract(new Area(cut)); + clip.transform(transform); + return clip; + } + + private String makeKey(Rectangle area, BorderProps beforeProps, + BorderProps endProps, BorderProps afterProps, BorderProps startProps, + Color innerBackgroundColor) { + + return hash(new StringBuffer() + .append(area.width) + .append(":") + .append(area.height) + .append(":") + .append(beforeProps) + .append(":") + .append(endProps) + .append(":") + .append(afterProps) + .append(":") + .append(startProps) + .append(":") + .append(innerBackgroundColor) + .toString()); + } + + + private String hash(String text) { + + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (Exception e) { + throw new RuntimeException("Internal error", e); } - //Simply delegates to drawBorderLine() as AFP line painting is not very sophisticated. - int halfWidth = width / 2; - drawBorderLine(start.x, start.y - halfWidth, end.x, start.y + halfWidth, - true, true, style.getEnumValue(), color); + byte[] result = md.digest(text.getBytes()); + + StringBuffer sb = new StringBuffer(); + char[] digits = {'0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + for (int idx = 0; idx < 6; ++idx) { + byte b = result[idx]; + sb.append(digits[(b & 0xf0) >> 4]); + sb.append(digits[b & 0x0f]); + } + return sb.toString(); } + private void paintCornersAsBitmap(Graphics2DImagePainter painter, + Rectangle boundingBox, String name) throws IFException { + //TODO parameters ok? + ImageInfo info = new ImageInfo(name, null); + + ImageSize size = new ImageSize(); + size.setSizeInMillipoints(boundingBox.width, boundingBox.height); + + //Use the foreign attributes map to set image handling hints + Map map = new java.util.HashMap(2); + map.put(AFPForeignAttributeReader.RESOURCE_NAME, name); + map.put(AFPForeignAttributeReader.RESOURCE_LEVEL, "print-file"); + + AFPRenderingContext context = (AFPRenderingContext) + this.painter.createRenderingContext(/*map*/); + + size.setResolution(context.getPaintingState().getResolution()); + size.calcPixelsFromSize(); + info.setSize(size); + ImageGraphics2D img = new ImageGraphics2D(info, painter); + + Map hints = new java.util.HashMap(); + + hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP); + hints.put("TARGET_RESOLUTION", + Integer.valueOf(context.getPaintingState().getResolution())); + + + try { + this.painter.drawImage(img, boundingBox, context, true, hints); + } catch (IOException ioe) { + throw new IFException( + "I/O error while painting corner using a bitmap", ioe); + } catch (ImageException ie) { + throw new IFException( + "Image error while painting corner using a bitmap", ie); + } + } + + protected void arcTo(double startAngle, double endAngle, int cx, int cy, int width, + int height) throws IOException { + throw new UnsupportedOperationException("Can only deal with horizontal lines right now"); + + } } /** {@inheritDoc} */ @Override public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) - throws IFException { + throws IFException { try { - this.borderPainter.drawLine(start, end, width, color, style); + this.graphicsPainter.drawLine(start, end, width, color, style); } catch (IOException ioe) { throw new IFException("I/O error in drawLine()", ioe); } @@ -367,14 +890,14 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { // register font as necessary Map<String, Typeface> fontMetricMap = getFontInfo().getFonts(); - final AFPFont afpFont = (AFPFont)fontMetricMap.get(fontKey); + final AFPFont afpFont = (AFPFont) fontMetricMap.get(fontKey); final Font font = getFontInfo().getFontInstance(triplet, fontSize); AFPPageFonts pageFonts = getPaintingState().getPageFonts(); AFPFontAttributes fontAttributes = pageFonts.registerFont(fontKey, afpFont, fontSize); final int fontReference = fontAttributes.getFontReference(); - final int[] coords = unitConv.mpts2units(new float[] {x, y} ); + final int[] coords = unitConv.mpts2units(new float[] {x, y}); final CharacterSet charSet = afpFont.getCharacterSet(fontSize); @@ -398,7 +921,7 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { builder.absoluteMoveInline(p.x); builder.setExtendedTextColor(state.getTextColor()); - builder.setCodedFont((byte)fontReference); + builder.setCodedFont((byte) fontReference); int l = text.length(); int[] dx = IFUtil.convertDPToDX ( dp ); @@ -533,4 +1056,21 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { getPaintingState().restore(); } + + /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + } + + /** {@inheritDoc} */ + public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + return borderPainter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd); + } + + /** {@inheritDoc} */ + public void fillBackground(Rectangle rect, Paint fill, BorderProps bpsBefore, + BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + // not supported in AFP + } } diff --git a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java index b36646117..e2d779192 100644 --- a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java +++ b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java @@ -91,9 +91,11 @@ public abstract class AbstractAFPImageHandlerRawStream extends AFPImageHandler AFPDataObjectInfo dataObjectInfo = createDataObjectInfo(); // set resource information - setResourceInformation(dataObjectInfo, + + dataObjectInfo.setResourceInfo(createResourceInformation( image.getInfo().getOriginalURI(), - afpContext.getForeignAttributes()); + afpContext.getForeignAttributes())); + // Positioning dataObjectInfo.setObjectAreaInfo(createObjectAreaInfo(afpContext.getPaintingState(), pos)); diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java index 147b6d54b..2205aea4c 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -330,7 +330,7 @@ public abstract class AbstractIFPainter<T extends IFDocumentHandler> implements /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException { + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top != null) { Rectangle b = new Rectangle( rect.x, rect.y, @@ -452,4 +452,10 @@ public abstract class AbstractIFPainter<T extends IFDocumentHandler> implements return new AffineTransform(matrix); } + /** {@inheritDoc} */ + public boolean isBackgroundRequired( BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + return true; + } + } diff --git a/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java b/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java new file mode 100644 index 000000000..112808f62 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.io.IOException; + +public class ArcToBezierCurveTransformer { + + private final BezierCurvePainter bezierCurvePainter; + + public ArcToBezierCurveTransformer(BezierCurvePainter bezierCurvePainter) { + this.bezierCurvePainter = bezierCurvePainter; + } + + /** + * Draws an arc on the ellipse centered at (cx, cy) with width width and height height + * from start angle startAngle (with respect to the x-axis counter-clockwise) + * to the end angle endAngle. + * The ellipses major axis are assumed to coincide with the coordinate axis. + * The current position MUST coincide with the starting position on the ellipse. + * @param startAngle the start angle + * @param endAngle the end angle + * @param cx the x coordinate of the ellipse center + * @param cy the y coordinate of the ellipse center + * @param width the extent of the ellipse in the x direction + * @param height the extent of the ellipse in the y direction + * @throws IOException if an I/O error occurs + */ + public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, + final int width, final int height) throws IOException { + + // Implementation follows http://www.spaceroots.org/documents/ellipse/ - + // Drawing an elliptical arc using polylines, quadratic or cubic Bézier curves + // L. Maisonobe, July 21, 2003 + + // Scaling the coordinate system to represent the ellipse as a circle: + final double etaStart = Math.atan(Math.tan(startAngle) * width / height) + + quadrant(startAngle); + final double etaEnd = Math.atan(Math.tan(endAngle) * width / height) + + quadrant(endAngle); + + final double sinStart = Math.sin(etaStart); + final double cosStart = Math.cos(etaStart); + final double sinEnd = Math.sin(etaEnd); + final double cosEnd = Math.cos(etaEnd); + + final double p0x = cx + cosStart * width; + final double p0y = cy + sinStart * height; + final double p3x = cx + cosEnd * width; + final double p3y = cy + sinEnd * height; + + double etaDiff = Math.abs(etaEnd - etaStart); + double tan = Math.tan((etaDiff) / 2d); + final double alpha = Math.sin(etaDiff) * (Math.sqrt(4d + 3d * tan * tan) - 1d) / 3d; + + int order = etaEnd > etaStart ? 1 : -1; + + // p1 = p0 + alpha*(-sin(startAngle), cos(startAngle)) + final double p1x = p0x - alpha * sinStart * width * order; + final double p1y = p0y + alpha * cosStart * height * order; + + // p1 = p3 + alpha*(sin(endAngle), -cos(endAngle)) + final double p2x = p3x + alpha * sinEnd * width * order; + final double p2y = p3y - alpha * cosEnd * height * order; + + //Draw the curve in original coordinate system + bezierCurvePainter.cubicBezierTo((int) p1x, (int) p1y, (int) p2x, (int) p2y, (int) p3x, (int) p3y); + } + + private double quadrant(double angle) { + if (angle <= Math.PI) { + if (angle <= Math.PI / 2d) { + return 0; + } else { + return Math.PI; + } + } else { + if (angle > Math.PI * 3d / 2d) { + return 2d * Math.PI; + } else { + return Math.PI; + } + } + } +} diff --git a/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java b/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java new file mode 100644 index 000000000..dd28dd5c8 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.io.IOException; + +public interface BezierCurvePainter { + /** + * Draw a cubic bezier from current position to (p3x, p3y) using the control points + * (p1x, p1y) and (p2x, p2y) + * @param p1x x coordinate of the first control point + * @param p1y y coordinate of the first control point + * @param p2x x coordinate of the second control point + * @param p2y y coordinate of the second control point + * @param p3x x coordinate of the end point + * @param p3y y coordinate of the end point + * @throws IOException if an I/O error occurs + */ + void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) throws IOException; +} diff --git a/src/java/org/apache/fop/render/intermediate/BorderPainter.java b/src/java/org/apache/fop/render/intermediate/BorderPainter.java index cc28028ef..19e30b50f 100644 --- a/src/java/org/apache/fop/render/intermediate/BorderPainter.java +++ b/src/java/org/apache/fop/render/intermediate/BorderPainter.java @@ -20,17 +20,47 @@ package org.apache.fop.render.intermediate; import java.awt.Color; -import java.awt.Point; import java.awt.Rectangle; import java.io.IOException; import org.apache.fop.traits.BorderProps; -import org.apache.fop.traits.RuleStyle; /** * This is an abstract base class for handling border painting. */ -public abstract class BorderPainter { +public class BorderPainter { + + // TODO Use a class to model border instead of an array + /** Convention index of before top */ + protected static final int TOP = 0; + + /** Convention index of right border */ + protected static final int RIGHT = 1; + + /** Convention index of bottom border */ + protected static final int BOTTOM = 2; + + /** Convention index of left border */ + protected static final int LEFT = 3; + + // TODO Use a class to model border corners instead of an array + /** Convention index of top-left border corners */ + protected static final int TOP_LEFT = 0; + + /** Convention index of top-right-end border corners */ + protected static final int TOP_RIGHT = 1; + + /** Convention index of bottom-right border corners */ + protected static final int BOTTOM_RIGHT = 2; + + /** Convention index of bottom-left border corners */ + protected static final int BOTTOM_LEFT = 3; + + private final GraphicsPainter graphicsPainter; + + public BorderPainter(GraphicsPainter graphicsPainter) { + this.graphicsPainter = graphicsPainter; + } /** * Draws borders. @@ -39,59 +69,96 @@ public abstract class BorderPainter { * @param bpsBottom the border specification on the bottom side * @param bpsLeft the border specification on the left side * @param bpsRight the border specification on the end side - * @throws IOException if an I/O error occurs while creating the borders + * @param innerBackgroundColor the inner background color + * @throws IFException if an error occurs while drawing the borders */ public void drawBorders(Rectangle borderRect, // CSOK: MethodLength BorderProps bpsTop, BorderProps bpsBottom, + BorderProps bpsLeft, BorderProps bpsRight, Color innerBackgroundColor) + throws IFException { + try { + drawRoundedBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight); + } catch (IOException ioe) { + throw new IFException("IO error drawing borders", ioe); + } + } + + private BorderProps sanitizeBorderProps(BorderProps bps) { + return bps == null ? bps : bps.width == 0 ? (BorderProps) null : bps; + } + + /** + * TODO merge with drawRoundedBorders()? + * @param borderRect the border rectangle + * @param bpsTop the border specification on the top side + * @param bpsBottom the border specification on the bottom side + * @param bpsLeft the border specification on the left side + * @param bpsRight the border specification on the end side + * @throws IOException + */ + protected void drawRectangularBorders(Rectangle borderRect, + BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight) throws IOException { + + bpsTop = sanitizeBorderProps(bpsTop); + bpsBottom = sanitizeBorderProps(bpsBottom); + bpsLeft = sanitizeBorderProps(bpsLeft); + bpsRight = sanitizeBorderProps(bpsRight); + + int startx = borderRect.x; int starty = borderRect.y; int width = borderRect.width; int height = borderRect.height; boolean[] b = new boolean[] { - (bpsTop != null), (bpsRight != null), - (bpsBottom != null), (bpsLeft != null)}; - if (!b[0] && !b[1] && !b[2] && !b[3]) { + (bpsTop != null), (bpsRight != null), + (bpsBottom != null), (bpsLeft != null)}; + if (!b[TOP] && !b[RIGHT] && !b[BOTTOM] && !b[LEFT]) { return; } int[] bw = new int[] { - (b[0] ? bpsTop.width : 0), - (b[1] ? bpsRight.width : 0), - (b[2] ? bpsBottom.width : 0), - (b[3] ? bpsLeft.width : 0)}; + (b[TOP] ? bpsTop.width : 0), + (b[RIGHT] ? bpsRight.width : 0), + (b[BOTTOM] ? bpsBottom.width : 0), + (b[LEFT] ? bpsLeft.width : 0)}; int[] clipw = new int[] { - BorderProps.getClippedWidth(bpsTop), - BorderProps.getClippedWidth(bpsRight), - BorderProps.getClippedWidth(bpsBottom), - BorderProps.getClippedWidth(bpsLeft)}; - starty += clipw[0]; - height -= clipw[0]; - height -= clipw[2]; - startx += clipw[3]; - width -= clipw[3]; - width -= clipw[1]; + BorderProps.getClippedWidth(bpsTop), + BorderProps.getClippedWidth(bpsRight), + BorderProps.getClippedWidth(bpsBottom), + BorderProps.getClippedWidth(bpsLeft)}; + starty += clipw[TOP]; + height -= clipw[TOP]; + height -= clipw[BOTTOM]; + startx += clipw[LEFT]; + width -= clipw[LEFT]; + width -= clipw[RIGHT]; boolean[] slant = new boolean[] { - (b[3] && b[0]), (b[0] && b[1]), (b[1] && b[2]), (b[2] && b[3])}; + (b[LEFT] && b[TOP]), + (b[TOP] && b[RIGHT]), + (b[RIGHT] && b[BOTTOM]), + (b[BOTTOM] && b[LEFT])}; if (bpsTop != null) { int sx1 = startx; - int sx2 = (slant[0] ? sx1 + bw[3] - clipw[3] : sx1); + int sx2 = (slant[TOP_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1); int ex1 = startx + width; - int ex2 = (slant[1] ? ex1 - bw[1] + clipw[1] : ex1); - int outery = starty - clipw[0]; - int clipy = outery + clipw[0]; - int innery = outery + bw[0]; + int ex2 = (slant[TOP_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1); + int outery = starty - clipw[TOP]; + int clipy = outery + clipw[TOP]; + int innery = outery + bw[TOP]; saveGraphicsState(); moveTo(sx1, clipy); + + int sx1a = sx1; int ex1a = ex1; - if (bpsTop.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[3]; + if (isCollapseOuter(bpsTop)) { + if (isCollapseOuter(bpsLeft)) { + sx1a -= clipw[LEFT]; } - if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[1]; + if (isCollapseOuter(bpsRight)) { + ex1a += clipw[RIGHT]; } lineTo(sx1a, outery); lineTo(ex1a, outery); @@ -107,23 +174,23 @@ public abstract class BorderPainter { } if (bpsRight != null) { int sy1 = starty; - int sy2 = (slant[1] ? sy1 + bw[0] - clipw[0] : sy1); + int sy2 = (slant[TOP_RIGHT] ? sy1 + bw[TOP] - clipw[TOP] : sy1); int ey1 = starty + height; - int ey2 = (slant[2] ? ey1 - bw[2] + clipw[2] : ey1); - int outerx = startx + width + clipw[1]; - int clipx = outerx - clipw[1]; - int innerx = outerx - bw[1]; - + int ey2 = (slant[BOTTOM_RIGHT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1); + int outerx = startx + width + clipw[RIGHT]; + int clipx = outerx - clipw[RIGHT]; + int innerx = outerx - bw[RIGHT]; saveGraphicsState(); moveTo(clipx, sy1); int sy1a = sy1; int ey1a = ey1; - if (bpsRight.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[0]; + + if (isCollapseOuter(bpsRight)) { + if (isCollapseOuter(bpsTop)) { + sy1a -= clipw[TOP]; } - if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[2]; + if (isCollapseOuter(bpsBottom)) { + ey1a += clipw[BOTTOM]; } lineTo(outerx, sy1a); lineTo(outerx, ey1a); @@ -139,23 +206,22 @@ public abstract class BorderPainter { } if (bpsBottom != null) { int sx1 = startx; - int sx2 = (slant[3] ? sx1 + bw[3] - clipw[3] : sx1); + int sx2 = (slant[BOTTOM_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1); int ex1 = startx + width; - int ex2 = (slant[2] ? ex1 - bw[1] + clipw[1] : ex1); - int outery = starty + height + clipw[2]; - int clipy = outery - clipw[2]; - int innery = outery - bw[2]; - + int ex2 = (slant[BOTTOM_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1); + int outery = starty + height + clipw[BOTTOM]; + int clipy = outery - clipw[BOTTOM]; + int innery = outery - bw[BOTTOM]; saveGraphicsState(); moveTo(ex1, clipy); int sx1a = sx1; int ex1a = ex1; - if (bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[3]; + if (isCollapseOuter(bpsBottom)) { + if (isCollapseOuter(bpsLeft)) { + sx1a -= clipw[LEFT]; } - if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[1]; + if (isCollapseOuter(bpsRight)) { + ex1a += clipw[RIGHT]; } lineTo(ex1a, outery); lineTo(sx1a, outery); @@ -171,23 +237,25 @@ public abstract class BorderPainter { } if (bpsLeft != null) { int sy1 = starty; - int sy2 = (slant[0] ? sy1 + bw[0] - clipw[0] : sy1); + int sy2 = (slant[TOP_LEFT] ? sy1 + bw[TOP] - clipw[TOP] : sy1); int ey1 = sy1 + height; - int ey2 = (slant[3] ? ey1 - bw[2] + clipw[2] : ey1); - int outerx = startx - clipw[3]; - int clipx = outerx + clipw[3]; - int innerx = outerx + bw[3]; + int ey2 = (slant[BOTTOM_LEFT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1); + int outerx = startx - clipw[LEFT]; + int clipx = outerx + clipw[LEFT]; + int innerx = outerx + bw[LEFT]; saveGraphicsState(); + moveTo(clipx, ey1); + int sy1a = sy1; int ey1a = ey1; - if (bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[0]; + if (isCollapseOuter(bpsLeft)) { + if (isCollapseOuter(bpsTop)) { + sy1a -= clipw[TOP]; } - if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[2]; + if (isCollapseOuter(bpsBottom)) { + ey1a += clipw[BOTTOM]; } lineTo(outerx, ey1a); lineTo(outerx, sy1a); @@ -202,77 +270,648 @@ public abstract class BorderPainter { } } + private boolean isCollapseOuter(BorderProps bp) { + return bp != null && bp.isCollapseOuter(); + } - /** - * Draws a border line. - * @param x1 X coordinate of the upper left corner - * of the line's bounding rectangle (in millipoints) - * @param y1 start Y coordinate of the upper left corner - * of the line's bounding rectangle (in millipoints) - * @param x2 end X coordinate of the lower right corner - * of the line's bounding rectangle (in millipoints) - * @param y2 end y coordinate of the lower right corner - * of the line's bounding rectangle (in millipoints) - * @param horz true if it is a horizontal line - * @param startOrBefore true if the line is the start or end edge of a border box - * @param style the border style - * @param color the border color - * @throws IOException if an I/O error occurs - */ - protected abstract void drawBorderLine( // CSOK: ParameterNumber - int x1, int y1, int x2, int y2, - boolean horz, boolean startOrBefore, int style, Color color) throws IOException; + /** TODO merge with drawRectangularBorders? + * @param borderRect the border rectangle + * @param bpsBefore the border specification on the before side + * @param bpsAfter the border specification on the after side + * @param bpsStart the border specification on the start side + * @param bpsEnd the border specification on the end side + * @throws IOException on io exception + * */ + protected void drawRoundedBorders(Rectangle borderRect, + BorderProps beforeBorderProps, BorderProps afterBorderProps, + BorderProps startBorderProps, BorderProps endBorderProps) throws IOException { + BorderSegment before = borderSegmentForBefore(beforeBorderProps); + BorderSegment after = borderSegmentForAfter(afterBorderProps); + BorderSegment start = borderSegmentForStart(startBorderProps); + BorderSegment end = borderSegmentForEnd(endBorderProps); + if (before.getWidth() == 0 && after.getWidth() == 0 && start.getWidth() == 0 && end.getWidth() == 0) { + return; + } + final int startx = borderRect.x + start.getClippedWidth(); + final int starty = borderRect.y + before.getClippedWidth(); + final int width = borderRect.width - start.getClippedWidth() - end.getClippedWidth(); + final int height = borderRect.height - before.getClippedWidth() - after.getClippedWidth(); + //Determine scale factor if any adjacent elliptic corners overlap + double cornerCorrectionFactor = calculateCornerScaleCorrection(width, height, before, after, start, + end); + drawBorderSegment(start, before, end, 0, width, startx, starty, cornerCorrectionFactor); + drawBorderSegment(before, end, after, 1, height, startx + width, starty, cornerCorrectionFactor); + drawBorderSegment(end, after, start, 2, width, startx + width, starty + height, + cornerCorrectionFactor); + drawBorderSegment(after, start, before, 3, height, startx, starty + height, cornerCorrectionFactor); + } - /** - * Draws a line/rule. - * @param start start point (coordinates in millipoints) - * @param end end point (coordinates in millipoints) - * @param width width of the line - * @param color the line color - * @param style the rule style - * @throws IOException if an I/O error occurs - */ - public abstract void drawLine(Point start, Point end, - int width, Color color, RuleStyle style) throws IOException; + private void drawBorderSegment(BorderSegment start, BorderSegment before, BorderSegment end, + int orientation, int width, int x, int y, double cornerCorrectionFactor) throws IOException { + if (before.getWidth() != 0) { + //Let x increase in the START->END direction + final int sx2 = start.getWidth() - start.getClippedWidth(); + final int ex1 = width; + final int ex2 = ex1 - end.getWidth() + end.getClippedWidth(); + final int outery = -before.getClippedWidth(); + final int innery = outery + before.getWidth(); + final int ellipseSBRadiusX = correctRadius(cornerCorrectionFactor, start.getRadiusEnd()); + final int ellipseSBRadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusStart()); + final int ellipseBERadiusX = correctRadius(cornerCorrectionFactor, end.getRadiusStart()); + final int ellipseBERadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusEnd()); + saveGraphicsState(); + translateCoordinates(x, y); + if (orientation != 0) { + rotateCoordinates(Math.PI * orientation / 2d); + } + final int ellipseSBX = ellipseSBRadiusX; + final int ellipseSBY = ellipseSBRadiusY; + final int ellipseBEX = ex1 - ellipseBERadiusX; + final int ellipseBEY = ellipseBERadiusY; + int sx1a = 0; + int ex1a = ex1; + if (ellipseSBRadiusX != 0 && ellipseSBRadiusY != 0) { + final double[] joinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX, + ellipseSBRadiusY, sx2, innery); + final double outerJoinPointX = joinMetrics[0]; + final double outerJoinPointY = joinMetrics[1]; + final double sbJoinAngle = joinMetrics[2]; + moveTo((int) outerJoinPointX, (int) outerJoinPointY); + arcTo(Math.PI + sbJoinAngle, Math.PI * 3 / 2, + ellipseSBX, ellipseSBY, ellipseSBRadiusX, ellipseSBRadiusY); + } else { + moveTo(0, 0); + if (before.isCollapseOuter()) { + if (start.isCollapseOuter()) { + sx1a -= start.getClippedWidth(); + } + if (end.isCollapseOuter()) { + ex1a += end.getClippedWidth(); + } + lineTo(sx1a, outery); + lineTo(ex1a, outery); + } + } + if (ellipseBERadiusX != 0 && ellipseBERadiusY != 0) { + final double[] outerJoinMetrics = getCornerBorderJoinMetrics( + ellipseBERadiusX, ellipseBERadiusY, ex1 - ex2, innery); + final double beJoinAngle = ex1 == ex2 ? Math.PI / 2 : Math.PI / 2 - outerJoinMetrics[2]; + lineTo(ellipseBEX, 0); + arcTo(Math.PI * 3 / 2 , Math.PI * 3 / 2 + beJoinAngle, + ellipseBEX, ellipseBEY, ellipseBERadiusX, ellipseBERadiusY); + if (ellipseBEX < ex2 && ellipseBEY > innery) { + final double[] innerJoinMetrics = getCornerBorderJoinMetrics( + (double) ex2 - ellipseBEX, (double) ellipseBEY - innery, ex1 - ex2, innery); + final double innerJoinPointX = innerJoinMetrics[0]; + final double innerJoinPointY = innerJoinMetrics[1]; + final double beInnerJoinAngle = Math.PI / 2 - innerJoinMetrics[2]; + lineTo((int) (ex2 - innerJoinPointX), (int) (innerJoinPointY + innery)); + arcTo(beInnerJoinAngle + Math.PI * 3 / 2, Math.PI * 3 / 2, + ellipseBEX, ellipseBEY, ex2 - ellipseBEX, ellipseBEY - innery); + } else { + lineTo(ex2, innery); + } + } else { + lineTo(ex1, 0); + lineTo(ex2, innery); + } + if (ellipseSBRadiusX == 0) { + lineTo(sx2, innery); + } else { + if (ellipseSBX > sx2 && ellipseSBY > innery) { + final double[] innerJoinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX - sx2, + ellipseSBRadiusY - innery, sx2, innery); + final double sbInnerJoinAngle = innerJoinMetrics[2]; + lineTo(ellipseSBX, innery); + arcTo(Math.PI * 3 / 2, sbInnerJoinAngle + Math.PI, + ellipseSBX, ellipseSBY, ellipseSBX - sx2, ellipseSBY - innery); + } else { + lineTo(sx2, innery); + } + } + closePath(); + clip(); + if (ellipseBERadiusY == 0 && ellipseSBRadiusY == 0) { + drawBorderLine(sx1a, outery, ex1a, innery, true, true, + before.getStyle(), before.getColor()); + } else { + int innerFillY = Math.max(Math.max(ellipseBEY, ellipseSBY), innery); + drawBorderLine(sx1a, outery, ex1a, innerFillY, true, true, + before.getStyle(), before.getColor()); + } + restoreGraphicsState(); + } + } - /** - * Moves the cursor to the given coordinate. - * @param x the X coordinate (in millipoints) - * @param y the Y coordinate (in millipoints) - * @throws IOException if an I/O error occurs - */ - protected abstract void moveTo(int x, int y) throws IOException; + private static int correctRadius(double cornerCorrectionFactor, int radius) { + return (int) (Math.round(cornerCorrectionFactor * radius)); + } + + private static BorderSegment borderSegmentForBefore(BorderProps before) { + return AbstractBorderSegment.asBorderSegment(before); + } + + private static BorderSegment borderSegmentForAfter(BorderProps after) { + return AbstractBorderSegment.asFlippedBorderSegment(after); + } + + private static BorderSegment borderSegmentForStart(BorderProps start) { + return AbstractBorderSegment.asFlippedBorderSegment(start); + } + + private static BorderSegment borderSegmentForEnd(BorderProps end) { + return AbstractBorderSegment.asBorderSegment(end); + } + + private interface BorderSegment { + + Color getColor(); + + int getStyle(); + + int getWidth(); + + int getClippedWidth(); + + int getRadiusStart(); + + int getRadiusEnd(); + + boolean isCollapseOuter(); + + boolean isSpecified(); + } + + private abstract static class AbstractBorderSegment implements BorderSegment { + + private static BorderSegment asBorderSegment(BorderProps borderProps) { + return borderProps == null ? NullBorderSegment.INSTANCE : new WrappingBorderSegment(borderProps); + } + + private static BorderSegment asFlippedBorderSegment(BorderProps borderProps) { + return borderProps == null ? NullBorderSegment.INSTANCE : new FlippedBorderSegment(borderProps); + } + + public boolean isSpecified() { + return !(this instanceof NullBorderSegment); + } + + private static class WrappingBorderSegment extends AbstractBorderSegment { + + protected final BorderProps borderProps; + + private final int clippedWidth; + + WrappingBorderSegment(BorderProps borderProps) { + this.borderProps = borderProps; + clippedWidth = BorderProps.getClippedWidth(borderProps); + } + + public int getStyle() { + return borderProps.style; + } + + public Color getColor() { + return borderProps.color; + } + + public int getWidth() { + return borderProps.width; + } + + public int getClippedWidth() { + return clippedWidth; + } + public boolean isCollapseOuter() { + return borderProps.isCollapseOuter(); + } + + public int getRadiusStart() { + return borderProps.getRadiusStart(); + } + + public int getRadiusEnd() { + return borderProps.getRadiusEnd(); + } + } + + private static class FlippedBorderSegment extends WrappingBorderSegment { + + FlippedBorderSegment(BorderProps borderProps) { + super(borderProps); + } + + public int getRadiusStart() { + return borderProps.getRadiusEnd(); + } + + public int getRadiusEnd() { + return borderProps.getRadiusStart(); + } + } + + private static final class NullBorderSegment extends AbstractBorderSegment { + + public static final NullBorderSegment INSTANCE = new NullBorderSegment(); + + private NullBorderSegment() { + } + + public int getWidth() { + return 0; + } + + public int getClippedWidth() { + return 0; + } + + public int getRadiusStart() { + return 0; + } + + public int getRadiusEnd() { + return 0; + } + + public boolean isCollapseOuter() { + return false; + } + + public Color getColor() { + throw new UnsupportedOperationException(); + } + + public int getStyle() { + throw new UnsupportedOperationException(); + } + + public boolean isSpecified() { + return false; + } + } + } + + private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, double xWidth, + double yWidth) { + if (xWidth > 0) { + return getCornerBorderJoinMetrics(ellipseCenterX, ellipseCenterY, yWidth / xWidth); + } else { + return new double[]{0, ellipseCenterY, 0}; + } + } + + private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, + double borderWidthRatio) { + double x = ellipseCenterY * ellipseCenterX * ( + ellipseCenterY + ellipseCenterX * borderWidthRatio + - Math.sqrt(2d * ellipseCenterX * ellipseCenterY * borderWidthRatio) + ) / (ellipseCenterY * ellipseCenterY + + ellipseCenterX * ellipseCenterX * borderWidthRatio * borderWidthRatio); + double y = borderWidthRatio * x; + return new double[]{x, y, Math.atan((ellipseCenterY - y) / (ellipseCenterX - x))}; + } /** - * Draws a line from the current cursor position to the given coordinates. - * @param x the X coordinate (in millipoints) - * @param y the Y coordinate (in millipoints) + * Clip the background to the inner border + * @param rect clipping rectangle + * @param bpsBefore before border + * @param bpsAfter after border + * @param bpsStart start border + * @param bpsEnd end border * @throws IOException if an I/O error occurs */ - protected abstract void lineTo(int x, int y) throws IOException; + public void clipBackground(Rectangle rect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IOException { + BorderSegment before = borderSegmentForBefore(bpsBefore); + BorderSegment after = borderSegmentForAfter(bpsAfter); + BorderSegment start = borderSegmentForStart(bpsStart); + BorderSegment end = borderSegmentForEnd(bpsEnd); + int startx = rect.x; + int starty = rect.y; + int width = rect.width; + int height = rect.height; + double correctionFactor = calculateCornerCorrectionFactor(width + start.getWidth() + end.getWidth(), + height + before.getWidth() + after.getWidth(), bpsBefore, bpsAfter, bpsStart, bpsEnd); + Corner cornerBeforeEnd = Corner.createBeforeEndCorner(before, end, correctionFactor); + Corner cornerEndAfter = Corner.createEndAfterCorner(end, after, correctionFactor); + Corner cornerAfterStart = Corner.createAfterStartCorner(after, start, correctionFactor); + Corner cornerStartBefore = Corner.createStartBeforeCorner(start, before, correctionFactor); + new PathPainter(startx + cornerStartBefore.radiusX, starty) + .lineHorizTo(width - cornerStartBefore.radiusX - cornerBeforeEnd.radiusX) + .drawCorner(cornerBeforeEnd) + .lineVertTo(height - cornerBeforeEnd.radiusY - cornerEndAfter.radiusY) + .drawCorner(cornerEndAfter) + .lineHorizTo(cornerEndAfter.radiusX + cornerAfterStart.radiusX - width) + .drawCorner(cornerAfterStart) + .lineVertTo(cornerAfterStart.radiusY + cornerStartBefore.radiusY - height) + .drawCorner(cornerStartBefore); + clip(); + } + + /** - * Closes the current path. - * @throws IOException if an I/O error occurs + * The four corners + * SB - Start-Before + * BE - Before-End + * EA - End-After + * AS - After-Start + * + * 0 --> x + * | + * v + * y + * + * SB BE + * *----* + * | | + * | | + * *----* + * AS EA + * */ - protected abstract void closePath() throws IOException; + private enum CornerAngles { + /** The before-end angles */ + BEFORE_END(Math.PI * 3 / 2, 0), + /** The end-after angles */ + END_AFTER(0, Math.PI / 2), + /** The after-start angles*/ + AFTER_START(Math.PI / 2, Math.PI), + /** The start-before angles */ + START_BEFORE(Math.PI, Math.PI * 3 / 2); + + /** Angle of the start of the corner arch relative to the x-axis in the counter-clockwise direction */ + private final double start; + + /** Angle of the end of the corner arch relative to the x-axis in the counter-clockwise direction */ + private final double end; + + CornerAngles(double start, double end) { + this.start = start; + this.end = end; + } + + } + + private static final class Corner { + + private static final Corner SQUARE = new Corner(0, 0, null, 0, 0, 0, 0); + + /** The radius of the elliptic corner in the x direction */ + private final int radiusX; + + /** The radius of the elliptic corner in the y direction */ + private final int radiusY; + + /** The start and end angles of the corner ellipse */ + private final CornerAngles angles; + + /** The offset in the x direction of the center of the ellipse relative to the starting point */ + private final int centerX; + + /** The offset in the y direction of the center of the ellipse relative to the starting point */ + private final int centerY; + + /** The value in the x direction that the corner extends relative to the starting point */ + private final int incrementX; + + /** The value in the y direction that the corner extends relative to the starting point */ + private final int incrementY; + + private Corner(int radiusX, int radiusY, CornerAngles angles, int ellipseOffsetX, + int ellipseOffsetY, int incrementX, int incrementY) { + this.radiusX = radiusX; + this.radiusY = radiusY; + this.angles = angles; + this.centerX = ellipseOffsetX; + this.centerY = ellipseOffsetY; + this.incrementX = incrementX; + this.incrementY = incrementY; + } + + private static int extentFromRadiusStart(BorderSegment border, double correctionFactor) { + return extentFromRadius(border.getRadiusStart(), border, correctionFactor); + } + + private static int extentFromRadiusEnd(BorderSegment border, double correctionFactor) { + return extentFromRadius(border.getRadiusEnd(), border, correctionFactor); + } + + private static int extentFromRadius(int radius, BorderSegment border, double correctionFactor) { + return Math.max((int) (radius * correctionFactor) - border.getWidth(), 0); + } + + public static Corner createBeforeEndCorner(BorderSegment before, BorderSegment end, + double correctionFactor) { + int width = end.getRadiusStart(); + int height = before.getRadiusEnd(); + if (width == 0 || height == 0) { + return SQUARE; + } + int x = extentFromRadiusStart(end, correctionFactor); + int y = extentFromRadiusEnd(before, correctionFactor); + return new Corner(x, y, CornerAngles.BEFORE_END, 0, y, x, y); + } + + public static Corner createEndAfterCorner(BorderSegment end, BorderSegment after, + double correctionFactor) { + int width = end.getRadiusEnd(); + int height = after.getRadiusStart(); + if (width == 0 || height == 0) { + return SQUARE; + } + int x = extentFromRadiusEnd(end, correctionFactor); + int y = extentFromRadiusStart(after, correctionFactor); + return new Corner(x, y, CornerAngles.END_AFTER, -x, 0, -x, y); + } + + public static Corner createAfterStartCorner(BorderSegment after, BorderSegment start, + double correctionFactor) { + int width = start.getRadiusStart(); + int height = after.getRadiusEnd(); + if (width == 0 || height == 0) { + return SQUARE; + } + int x = extentFromRadiusStart(start, correctionFactor); + int y = extentFromRadiusEnd(after, correctionFactor); + return new Corner(x, y, CornerAngles.AFTER_START, 0, -y, -x, -y); + } + + public static Corner createStartBeforeCorner(BorderSegment start, BorderSegment before, + double correctionFactor) { + int width = start.getRadiusEnd(); + int height = before.getRadiusStart(); + if (width == 0 || height == 0) { + return SQUARE; + } + int x = extentFromRadiusEnd(start, correctionFactor); + int y = extentFromRadiusStart(before, correctionFactor); + return new Corner(x, y, CornerAngles.START_BEFORE, x, 0, x, -y); + } + } /** - * Reduces the current clipping region to the current path. - * @throws IOException if an I/O error occurs + * This is a helper class for constructing curves composed of move, line and arc operations. Coordinates + * are relative to the terminal point of the previous operation */ - protected abstract void clip() throws IOException; + private final class PathPainter { + + /** Current x position */ + private int x; + + /** Current y position */ + private int y; + + PathPainter(int x, int y) throws IOException { + moveTo(x, y); + } + + private void moveTo(int x, int y) throws IOException { + this.x += x; + this.y += y; + BorderPainter.this.moveTo(this.x, this.y); + } + + public PathPainter lineTo(int x, int y) throws IOException { + this.x += x; + this.y += y; + BorderPainter.this.lineTo(this.x, this.y); + return this; + } + + public PathPainter lineHorizTo(int x) throws IOException { + return lineTo(x, 0); + } + + public PathPainter lineVertTo(int y) throws IOException { + return lineTo(0, y); + } + + PathPainter drawCorner(Corner corner) throws IOException { + if (corner.radiusX == 0 && corner.radiusY == 0) { + return this; + } + if (corner.radiusX == 0 || corner.radiusY == 0) { + x += corner.incrementX; + y += corner.incrementY; + BorderPainter.this.lineTo(x, y); + return this; + } + BorderPainter.this.arcTo(corner.angles.start, corner.angles.end, x + corner.centerX, + y + corner.centerY, corner.radiusX, corner.radiusY); + x += corner.incrementX; + y += corner.incrementY; + return this; + } + } /** - * Save the graphics state on the stack. - * @throws IOException if an I/O error occurs + * Calculate the correction factor to handle over-sized elliptic corner radii. + * + * @param width the border width + * @param height the border height + * @param before the before border properties + * @param after the after border properties + * @param start the start border properties + * @param end the end border properties + * */ - protected abstract void saveGraphicsState() throws IOException; + protected static double calculateCornerCorrectionFactor(int width, int height, BorderProps before, + BorderProps after, BorderProps start, BorderProps end) { + return calculateCornerScaleCorrection(width, height, borderSegmentForBefore(before), + borderSegmentForAfter(after), borderSegmentForStart(start), borderSegmentForEnd(end)); + } /** - * Restore the last graphics state from the stack. - * @throws IOException if an I/O error occurs + * Calculate the scaling factor to handle over-sized elliptic corner radii. + * + * @param width the border width + * @param height the border height + * @param before the before border segment + * @param after the after border segment + * @param start the start border segment + * @param end the end border segment */ - protected abstract void restoreGraphicsState() throws IOException; + protected static double calculateCornerScaleCorrection(int width, int height, BorderSegment before, + BorderSegment after, BorderSegment start, BorderSegment end) { + return CornerScaleCorrectionCalculator.calculate(width, height, before, after, start, end); + } + + private static final class CornerScaleCorrectionCalculator { + + private double correctionFactor = 1; + + private CornerScaleCorrectionCalculator(int width, int height, + BorderSegment before, BorderSegment after, + BorderSegment start, BorderSegment end) { + calculateForSegment(width, start, before, end); + calculateForSegment(height, before, end, after); + calculateForSegment(width, end, after, start); + calculateForSegment(height, after, start, before); + } + + public static double calculate(int width, int height, + BorderSegment before, BorderSegment after, + BorderSegment start, BorderSegment end) { + return new CornerScaleCorrectionCalculator(width, height, before, after, start, end) + .correctionFactor; + } + + private void calculateForSegment(int width, BorderSegment bpsStart, BorderSegment bpsBefore, + BorderSegment bpsEnd) { + if (bpsBefore.isSpecified()) { + double ellipseExtent = bpsStart.getRadiusEnd() + bpsEnd.getRadiusStart(); + if (ellipseExtent > 0) { + double thisCorrectionFactor = width / ellipseExtent; + if (thisCorrectionFactor < correctionFactor) { + correctionFactor = thisCorrectionFactor; + } + } + } + } + } + + private void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore, + int style, Color color) throws IOException { + graphicsPainter.drawBorderLine(x1, y1, x2, y2, horz, startOrBefore, style, color); + } + + private void moveTo(int x, int y) throws IOException { + graphicsPainter.moveTo(x, y); + } + + private void lineTo(int x, int y) throws IOException { + graphicsPainter.lineTo(x, y); + } + + private void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, + final int width, final int height) throws IOException { + graphicsPainter.arcTo(startAngle, endAngle, cx, cy, width, height); + } + + private void rotateCoordinates(double angle) throws IOException { + graphicsPainter.rotateCoordinates(angle); + } + + private void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + graphicsPainter.translateCoordinates(xTranslate, yTranslate); + } + + private void closePath() throws IOException { + graphicsPainter.closePath(); + } + + private void clip() throws IOException { + graphicsPainter.clip(); + } + + private void saveGraphicsState() throws IOException { + graphicsPainter.saveGraphicsState(); + } + + private void restoreGraphicsState() throws IOException { + graphicsPainter.restoreGraphicsState(); + } } diff --git a/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java b/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java new file mode 100644 index 000000000..369cacd43 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Point; +import java.io.IOException; + +import org.apache.fop.traits.RuleStyle; + +/** + * Used primarily by {@link BorderPainter}, implementations are created for rendering + * primitive graphical operations. + * + */ +public interface GraphicsPainter { + + /** + * Draws a border line. + * @param x1 X coordinate of the upper left corner + * of the line's bounding rectangle (in millipoints) + * @param y1 start Y coordinate of the upper left corner + * of the line's bounding rectangle (in millipoints) + * @param x2 end X coordinate of the lower right corner + * of the line's bounding rectangle (in millipoints) + * @param y2 end y coordinate of the lower right corner + * of the line's bounding rectangle (in millipoints) + * @param horz true if it is a horizontal line + * @param startOrBefore true if the line is the start or end edge of a border box + * @param style the border style + * @param color the border color + * @throws IOException if an I/O error occurs + */ + void drawBorderLine(int x1, int y1, int x2, int y2, + boolean horz, boolean startOrBefore, int style, Color color) throws IOException; + + /** + * Draws a line/rule. + * @param start start point (coordinates in millipoints) + * @param end end point (coordinates in millipoints) + * @param width width of the line + * @param color the line color + * @param style the rule style + * @throws IOException if an I/O error occurs + */ + void drawLine(Point start, Point end, + int width, Color color, RuleStyle style) throws IOException; + + /** + * Moves the cursor to the given coordinate. + * @param x the X coordinate (in millipoints) + * @param y the Y coordinate (in millipoints) + * @throws IOException if an I/O error occurs + */ + void moveTo(int x, int y) throws IOException; + + /** + * Draws a line from the current cursor position to the given coordinates. + * @param x the X coordinate (in millipoints) + * @param y the Y coordinate (in millipoints) + * @throws IOException if an I/O error occurs + */ + void lineTo(int x, int y) throws IOException; + + /** + * Draws an arc on the ellipse centered at (cx, cy) with width width and height height + * from start angle startAngle (with respect to the x-axis counter-clockwise) + * to the end angle endAngle. + * The ellipses major axis are assumed to coincide with the coordinate axis. + * The current position MUST coincide with the starting position on the ellipse. + * @param startAngle the start angle + * @param endAngle the end angle + * @param cx the x coordinate of the ellipse center + * @param cy the y coordinate of the ellipse center + * @param width the extent of the ellipse in the x direction + * @param height the extent of the ellipse in the y direction + * @throws IOException if an I/O error occurs + */ + void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, + final int width, final int height) throws IOException; + + /** + * Rotate the coordinate frame + * @param angle angle in radians to rotate the coordinate frame + * @throws IOException if an I/O error occurs + */ + void rotateCoordinates(double angle) throws IOException; + + /** + * Translate the coordinate frame + * @param xTranslate translation in the x direction + * @param yTranslate translation in the y direction + * @throws IOException if an I/O error occurs + */ + void translateCoordinates(int xTranslate, int yTranslate) throws IOException; + + /** + * Scale the coordinate frame + * @param xScale scale factor in the x direction + * @param yScale scale factor in the y direction + * @throws IOException if an I/O error occurs + */ + void scaleCoordinates(float xScale, float yScale) throws IOException; + + /** + * Closes the current path. + * @throws IOException if an I/O error occurs + */ + void closePath() throws IOException; + + /** + * Reduces the current clipping region to the current path. + * @throws IOException if an I/O error occurs + */ + void clip() throws IOException; + + /** + * Save the graphics state on the stack. + * @throws IOException if an I/O error occurs + */ + void saveGraphicsState() throws IOException; + + /** + * Restore the last graphics state from the stack. + * @throws IOException if an I/O error occurs + */ + void restoreGraphicsState() throws IOException; +} diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 06dfbd181..5d2beb65c 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -168,6 +168,34 @@ public interface IFPainter { void clipRect(Rectangle rect) throws IFException; //TODO clipRect() shall be considered temporary until verified with SVG and PCL + + /** + * Restricts the current clipping region to the inner border. + * @param rect the rectangle's coordinates and extent + * @param bpsBefore the border segment on the before-side (top) + * @param bpsAfter the border segment on the after-side (bottom) + * @param bpsStart the border segment on the start-side (left) + * @param bpsEnd the border segment on the end-side (right) + * @throws IFException if an error occurs while handling this event + */ + void clipBackground (Rectangle rect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException; + + + /** + * TODO Painter-specific rounded borders logic required background drawing to be + * made optional. A future refactoring of the rounded borders code should aim to make + * the need for this abstraction obsolete + * @param bpsBefore the before border + * @param bpsAfter the after border + * @param bpsStart the start border + * @param bpsEnd the end border + * @return true if and only if background drawing is required + */ + boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd); + /** * Fills a rectangular area. * @param rect the rectangle's coordinates and extent @@ -184,11 +212,12 @@ public interface IFPainter { * @param bottom the border segment on the bottom edge * @param left the border segment on the left edge * @param right the border segment on the right edge + * @param innerBackgroundColor the color of the inner background * @throws IFException if an error occurs while handling this event */ void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException; + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException; /** * Draws a line. NOTE: Currently, only horizontal lines are implemented! diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index d1fa50737..24b7a924c 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -650,6 +650,19 @@ public class IFParser implements IFConstants { int y = Integer.parseInt(attributes.getValue("y")); int width = Integer.parseInt(attributes.getValue("width")); int height = Integer.parseInt(attributes.getValue("height")); + BorderProps[] borders = new BorderProps[4]; + for (int i = 0; i < 4; i++) { + String b = attributes.getValue(SIDES[i]); + if (b != null) { + borders[i] = BorderProps.valueOf(userAgent, b); + } + } + + if (!(borders[0] == null && borders[1] == null + && borders[2] == null && borders[3] == null)) { + painter.clipBackground(new Rectangle(x, y, width, height), + borders[0], borders[1], borders[2], borders[3]); + } painter.clipRect(new Rectangle(x, y, width, height)); } @@ -668,7 +681,26 @@ public class IFParser implements IFConstants { } catch (PropertyException pe) { throw new IFException("Error parsing the fill attribute", pe); } - painter.fillRect(new Rectangle(x, y, width, height), fillColor); + + Rectangle rectangularArea = new Rectangle(x, y, width, height); + + //TODO should rect be overloaded to include rounded corners + // or should we introduce a new IF element? + BorderProps[] borders = new BorderProps[4]; + for (int i = 0; i < 4; i++) { + String b = attributes.getValue(SIDES[i]); + if (b != null) { + borders[i] = BorderProps.valueOf(userAgent, b); + } + } + + if (!(borders[0] == null && borders[1] == null + && borders[2] == null && borders[3] == null)) { + painter.clipBackground(rectangularArea, + borders[0], borders[1], borders[2], borders[3]); + } + + painter.fillRect(rectangularArea , fillColor); } } @@ -709,9 +741,16 @@ public class IFParser implements IFConstants { borders[i] = BorderProps.valueOf(userAgent, b); } } + Color backgroundColor; + + try { + backgroundColor = getAttributeAsColor(attributes, "inner-background-color"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the color attribute", pe); + } painter.drawBorderRect(new Rectangle(x, y, width, height), - borders[0], borders[1], borders[2], borders[3]); + borders[0], borders[1], borders[2], borders[3], backgroundColor); } } diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index fe3bbf72c..592a57ebe 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -1327,18 +1327,44 @@ public class IFRenderer extends AbstractPathOrientedRenderer { handleIFException(ife); } } - + /** {@inheritDoc} */ + protected void clipBackground(float startx, float starty, + float width, float height, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + pushGroup(new IFGraphicContext.Group()); + Rectangle rect = toMillipointRectangle(startx, starty, width, height); + try { + painter.clipBackground( rect, + bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IFException ife) { + handleIFException(ife); + } + } /** {@inheritDoc} */ protected void closePath() { throw new IllegalStateException("Not used"); } /** {@inheritDoc} */ + protected void drawBackground(float startx, float starty, + float width, float height, + Trait.Background back, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + if (painter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd)) { + super.drawBackground(startx, starty, width, height, + back, bpsBefore, bpsAfter, + bpsStart, bpsEnd); + } + } + + /** {@inheritDoc} */ protected void drawBorders( // CSOK: ParameterNumber float startx, float starty, float width, float height, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd, int level) { + BorderProps bpsStart, BorderProps bpsEnd, int level, Color innerBackgroundColor) { Rectangle rect = toMillipointRectangle(startx, starty, width, height); try { BorderProps bpsTop = bpsBefore; @@ -1352,7 +1378,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { bpsLeft = bpsEnd; bpsRight = bpsStart; } - painter.drawBorderRect(rect, bpsTop, bpsBottom, bpsLeft, bpsRight); + painter.drawBorderRect(rect, bpsTop, bpsBottom, bpsLeft, bpsRight, innerBackgroundColor); } catch (IFException ife) { handleIFException(ife); } diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 41d1b01ef..24239e5f4 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -519,6 +519,37 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { } /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + addAttribute(atts, "x", Integer.toString(rect.x)); + addAttribute(atts, "y", Integer.toString(rect.y)); + addAttribute(atts, "width", Integer.toString(rect.width)); + addAttribute(atts, "height", Integer.toString(rect.height)); + if (hasRoundedCorners(bpsBefore, bpsAfter, bpsStart, bpsEnd)) { + + if (bpsBefore != null) { + addAttribute(atts, "top", bpsBefore.toString()); + } + if (bpsAfter != null) { + addAttribute(atts, "bottom", bpsAfter.toString()); + } + if (bpsStart != null) { + addAttribute(atts, "left", bpsStart.toString()); + } + if (bpsEnd != null) { + addAttribute(atts, "right", bpsEnd.toString()); + } + } + handler.element(EL_CLIP_RECT, atts); + } catch (SAXException e) { + throw new IFException("SAX error in clipRect()", e); + } + } + + + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; @@ -536,9 +567,38 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { } } + //TODO create a class representing all borders should exist + //with query methods like this + private boolean hasRoundedCorners(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + boolean rtn = false; + + if (bpsBefore != null && bpsBefore.getRadiusStart() > 0 + && bpsStart != null && bpsStart.getRadiusStart() > 0) { + rtn = true; + } + + if (bpsBefore != null && bpsBefore.getRadiusEnd() > 0 + && bpsEnd != null && bpsEnd.getRadiusStart() > 0) { + rtn = true; + } + + if (bpsEnd != null && bpsEnd.getRadiusEnd() > 0 + && bpsAfter != null && bpsAfter.getRadiusEnd() > 0) { + rtn = true; + } + + if (bpsAfter != null && bpsAfter.getRadiusStart() > 0 + && bpsStart != null && bpsStart.getRadiusEnd() > 0) { + rtn = true; + } + + return rtn; + } + /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException { + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top == null && bottom == null && left == null && right == null) { return; } @@ -560,6 +620,12 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { if (right != null) { addAttribute(atts, "right", right.toString()); } + + if (innerBackgroundColor != null) { + addAttribute(atts, "inner-background-color", + ColorUtil.colorToString(innerBackgroundColor)); + } + handler.element(EL_BORDER_RECT, atts); } catch (SAXException e) { throw new IFException("SAX error in drawBorderRect()", e); @@ -856,4 +922,10 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { throw new IFException("SAX error serializing object", e); } } + + /** {@inheritDoc} */ + public boolean isBackgroundRequired(BorderProps bpsTop, BorderProps bpsBottom, + BorderProps bpsLeft, BorderProps bpsRight) { + return true; + } } diff --git a/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java b/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java deleted file mode 100644 index b2b29188b..000000000 --- a/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.java2d; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.fop.fo.Constants; -import org.apache.fop.render.intermediate.BorderPainter; -import org.apache.fop.traits.RuleStyle; -import org.apache.fop.util.ColorUtil; - -/** - * Java2D-specific implementation of the {@link BorderPainter}. - */ -public class Java2DBorderPainter extends BorderPainter { - - /** logging instance */ - private static Log log = LogFactory.getLog(Java2DBorderPainter.class); - - private Java2DPainter painter; - - private GeneralPath currentPath = null; - - /** - * Construct a java2d border painter. - * @param painter a painter - */ - public Java2DBorderPainter(Java2DPainter painter) { - this.painter = painter; - } - - private Java2DGraphicsState getG2DState() { - return this.painter.g2dState; - } - - private Graphics2D getG2D() { - return getG2DState().getGraph(); - } - - /** {@inheritDoc} */ - protected void drawBorderLine( // CSOK: ParameterNumber - int x1, int y1, int x2, int y2, boolean horz, - boolean startOrBefore, int style, Color color) { - float w = x2 - x1; - float h = y2 - y1; - if ((w < 0) || (h < 0)) { - log.error("Negative extent received. Border won't be painted."); - return; - } - switch (style) { - case Constants.EN_DASHED: - getG2D().setColor(color); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - float ym = y1 + (h / 2); - BasicStroke s = new BasicStroke(h, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - float xm = x1 + (w / 2); - BasicStroke s = new BasicStroke(w, BasicStroke.CAP_BUTT, - BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); - } - break; - case Constants.EN_DOTTED: - getG2D().setColor(color); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - float ym = y1 + (h / 2); - BasicStroke s = new BasicStroke(h, BasicStroke.CAP_ROUND, - BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - float xm = x1 + (w / 2); - BasicStroke s = new BasicStroke(w, BasicStroke.CAP_ROUND, - BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); - } - break; - case Constants.EN_DOUBLE: - getG2D().setColor(color); - if (horz) { - float h3 = h / 3; - float ym1 = y1 + (h3 / 2); - float ym2 = ym1 + h3 + h3; - BasicStroke s = new BasicStroke(h3); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); - getG2D().draw(new Line2D.Float(x1, ym2, x2, ym2)); - } else { - float w3 = w / 3; - float xm1 = x1 + (w3 / 2); - float xm2 = xm1 + w3 + w3; - BasicStroke s = new BasicStroke(w3); - getG2D().setStroke(s); - getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); - getG2D().draw(new Line2D.Float(xm2, y1, xm2, y2)); - } - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); - if (horz) { - Color uppercol = ColorUtil.lightenColor(color, -colFactor); - Color lowercol = ColorUtil.lightenColor(color, colFactor); - float h3 = h / 3; - float ym1 = y1 + (h3 / 2); - getG2D().setStroke(new BasicStroke(h3)); - getG2D().setColor(uppercol); - getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); - getG2D().setColor(color); - getG2D().draw(new Line2D.Float(x1, ym1 + h3, x2, ym1 + h3)); - getG2D().setColor(lowercol); - getG2D().draw(new Line2D.Float(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3)); - } else { - Color leftcol = ColorUtil.lightenColor(color, -colFactor); - Color rightcol = ColorUtil.lightenColor(color, colFactor); - float w3 = w / 3; - float xm1 = x1 + (w3 / 2); - getG2D().setStroke(new BasicStroke(w3)); - getG2D().setColor(leftcol); - getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); - getG2D().setColor(color); - getG2D().draw(new Line2D.Float(xm1 + w3, y1, xm1 + w3, y2)); - getG2D().setColor(rightcol); - getG2D().draw(new Line2D.Float(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2)); - } - break; - case Constants.EN_INSET: - case Constants.EN_OUTSET: - colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); - if (horz) { - color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor); - getG2D().setStroke(new BasicStroke(h)); - float ym1 = y1 + (h / 2); - getG2D().setColor(color); - getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); - } else { - color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor); - float xm1 = x1 + (w / 2); - getG2D().setStroke(new BasicStroke(w)); - getG2D().setColor(color); - getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); - } - break; - case Constants.EN_HIDDEN: - break; - default: - getG2D().setColor(color); - if (horz) { - float ym = y1 + (h / 2); - getG2D().setStroke(new BasicStroke(h)); - getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); - } else { - float xm = x1 + (w / 2); - getG2D().setStroke(new BasicStroke(w)); - getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); - } - } - } - - /** {@inheritDoc} */ - public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) { - if (start.y != end.y) { - //TODO Support arbitrary lines if necessary - throw new UnsupportedOperationException( - "Can only deal with horizontal lines right now"); - } - - saveGraphicsState(); - int half = width / 2; - int starty = start.y - half; - Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); - getG2DState().updateClip(boundingRect); - - switch (style.getEnumValue()) { - case Constants.EN_SOLID: - case Constants.EN_DASHED: - case Constants.EN_DOUBLE: - drawBorderLine(start.x, start.y - half, end.x, end.y + half, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_DOTTED: - int shift = half; //This shifts the dots to the right by half a dot's width - drawBorderLine(start.x + shift, start.y - half, end.x + shift, end.y + half, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - getG2DState().updateColor(ColorUtil.lightenColor(color, 0.6f)); - moveTo(start.x, starty); - lineTo(end.x, starty); - lineTo(end.x, starty + 2 * half); - lineTo(start.x, starty + 2 * half); - closePath(); - getG2D().fill(currentPath); - currentPath = null; - getG2DState().updateColor(color); - if (style.getEnumValue() == Constants.EN_GROOVE) { - moveTo(start.x, starty); - lineTo(end.x, starty); - lineTo(end.x, starty + half); - lineTo(start.x + half, starty + half); - lineTo(start.x, starty + 2 * half); - } else { - moveTo(end.x, starty); - lineTo(end.x, starty + 2 * half); - lineTo(start.x, starty + 2 * half); - lineTo(start.x, starty + half); - lineTo(end.x - half, starty + half); - } - closePath(); - getG2D().fill(currentPath); - currentPath = null; - - case Constants.EN_NONE: - // No rule is drawn - break; - default: - } // end switch - restoreGraphicsState(); - } - - /** {@inheritDoc} */ - protected void clip() { - if (currentPath == null) { - throw new IllegalStateException("No current path available!"); - } - getG2DState().updateClip(currentPath); - currentPath = null; - } - - /** {@inheritDoc} */ - protected void closePath() { - currentPath.closePath(); - } - - /** {@inheritDoc} */ - protected void lineTo(int x, int y) { - if (currentPath == null) { - currentPath = new GeneralPath(); - } - currentPath.lineTo(x, y); - } - - /** {@inheritDoc} */ - protected void moveTo(int x, int y) { - if (currentPath == null) { - currentPath = new GeneralPath(); - } - currentPath.moveTo(x, y); - } - - /** {@inheritDoc} */ - protected void saveGraphicsState() { - this.painter.saveGraphicsState(); - } - - /** {@inheritDoc} */ - protected void restoreGraphicsState() { - this.painter.restoreGraphicsState(); - this.currentPath = null; - } - -} diff --git a/src/java/org/apache/fop/render/java2d/Java2DGraphicsPainter.java b/src/java/org/apache/fop/render/java2d/Java2DGraphicsPainter.java new file mode 100644 index 000000000..588d417fa --- /dev/null +++ b/src/java/org/apache/fop/render/java2d/Java2DGraphicsPainter.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.java2d; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.Constants; +import org.apache.fop.render.intermediate.GraphicsPainter; +import org.apache.fop.traits.RuleStyle; +import org.apache.fop.util.ColorUtil; + +class Java2DGraphicsPainter implements GraphicsPainter { + + /** logging instance */ + static final Log log = LogFactory.getLog(Java2DGraphicsPainter.class); + + + private GeneralPath currentPath; + + private final Java2DPainter painter; + + + Java2DGraphicsPainter(Java2DPainter painter) { + this.painter = painter; + } + + private Java2DGraphicsState getG2DState() { + return this.painter.g2dState; + } + + + private Graphics2D getG2D() { + return getG2DState().getGraph(); + } + + public void drawBorderLine(int x1, int y1, int x2, int y2, + boolean horz, boolean startOrBefore, int style, Color color) + throws IOException { + float w = x2 - x1; + float h = y2 - y1; + if ((w < 0) || (h < 0)) { + log.error("Negative extent received. Border won't be painted."); + return; + } + switch (style) { + case Constants.EN_DASHED: + getG2D().setColor(color); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int)(w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + float ym = y1 + (h / 2); + BasicStroke s = new BasicStroke(h, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); + } else { + float unit = Math.abs(2 * w); + int rep = (int)(h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + float xm = x1 + (w / 2); + BasicStroke s = new BasicStroke(w, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); + } + break; + case Constants.EN_DOTTED: + getG2D().setColor(color); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int)(w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + float ym = y1 + (h / 2); + BasicStroke s = new BasicStroke(h, BasicStroke.CAP_ROUND, + BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); + } else { + float unit = Math.abs(2 * w); + int rep = (int)(h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + float xm = x1 + (w / 2); + BasicStroke s = new BasicStroke(w, BasicStroke.CAP_ROUND, + BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); + } + break; + case Constants.EN_DOUBLE: + getG2D().setColor(color); + if (horz) { + float h3 = h / 3; + float ym1 = y1 + (h3 / 2); + float ym2 = ym1 + h3 + h3; + BasicStroke s = new BasicStroke(h3); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); + getG2D().draw(new Line2D.Float(x1, ym2, x2, ym2)); + } else { + float w3 = w / 3; + float xm1 = x1 + (w3 / 2); + float xm2 = xm1 + w3 + w3; + BasicStroke s = new BasicStroke(w3); + getG2D().setStroke(s); + getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); + getG2D().draw(new Line2D.Float(xm2, y1, xm2, y2)); + } + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); + if (horz) { + Color uppercol = ColorUtil.lightenColor(color, -colFactor); + Color lowercol = ColorUtil.lightenColor(color, colFactor); + float h3 = h / 3; + float ym1 = y1 + (h3 / 2); + getG2D().setStroke(new BasicStroke(h3)); + getG2D().setColor(uppercol); + getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); + getG2D().setColor(color); + getG2D().draw(new Line2D.Float(x1, ym1 + h3, x2, ym1 + h3)); + getG2D().setColor(lowercol); + getG2D().draw(new Line2D.Float(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3)); + } else { + Color leftcol = ColorUtil.lightenColor(color, -colFactor); + Color rightcol = ColorUtil.lightenColor(color, colFactor); + float w3 = w / 3; + float xm1 = x1 + (w3 / 2); + getG2D().setStroke(new BasicStroke(w3)); + getG2D().setColor(leftcol); + getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); + getG2D().setColor(color); + getG2D().draw(new Line2D.Float(xm1 + w3, y1, xm1 + w3, y2)); + getG2D().setColor(rightcol); + getG2D().draw(new Line2D.Float(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2)); + } + break; + case Constants.EN_INSET: + case Constants.EN_OUTSET: + colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); + if (horz) { + color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor); + getG2D().setStroke(new BasicStroke(h)); + float ym1 = y1 + (h / 2); + getG2D().setColor(color); + getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1)); + } else { + color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor); + float xm1 = x1 + (w / 2); + getG2D().setStroke(new BasicStroke(w)); + getG2D().setColor(color); + getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2)); + } + break; + case Constants.EN_HIDDEN: + break; + default: + getG2D().setColor(color); + if (horz) { + float ym = y1 + (h / 2); + getG2D().setStroke(new BasicStroke(h)); + getG2D().draw(new Line2D.Float(x1, ym, x2, ym)); + } else { + float xm = x1 + (w / 2); + getG2D().setStroke(new BasicStroke(w)); + getG2D().draw(new Line2D.Float(xm, y1, xm, y2)); + } + } + } + + public void drawLine(Point start, Point end, int width, Color color, + RuleStyle style) throws IOException { + if (start.y != end.y) { + //TODO Support arbitrary lines if necessary + throw new UnsupportedOperationException( + "Can only deal with horizontal lines right now"); + } + + saveGraphicsState(); + int half = width / 2; + int starty = start.y - half; + Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); + getG2DState().updateClip(boundingRect); + + switch (style.getEnumValue()) { + case Constants.EN_SOLID: + case Constants.EN_DASHED: + case Constants.EN_DOUBLE: + drawBorderLine(start.x, start.y - half, end.x, end.y + half, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_DOTTED: + int shift = half; //This shifts the dots to the right by half a dot's width + drawBorderLine(start.x + shift, start.y - half, end.x + shift, end.y + half, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + getG2DState().updateColor(ColorUtil.lightenColor(color, 0.6f)); + moveTo(start.x, starty); + lineTo(end.x, starty); + lineTo(end.x, starty + 2 * half); + lineTo(start.x, starty + 2 * half); + closePath(); + getG2D().fill(currentPath); + currentPath = null; + getG2DState().updateColor(color); + if (style.getEnumValue() == Constants.EN_GROOVE) { + moveTo(start.x, starty); + lineTo(end.x, starty); + lineTo(end.x, starty + half); + lineTo(start.x + half, starty + half); + lineTo(start.x, starty + 2 * half); + } else { + moveTo(end.x, starty); + lineTo(end.x, starty + 2 * half); + lineTo(start.x, starty + 2 * half); + lineTo(start.x, starty + half); + lineTo(end.x - half, starty + half); + } + closePath(); + getG2D().fill(currentPath); + currentPath = null; + + case Constants.EN_NONE: + // No rule is drawn + break; + default: + } // end switch + restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void moveTo(int x, int y) throws IOException { + if (currentPath == null) { + currentPath = new GeneralPath(); + } + currentPath.moveTo(x, y); + } + + /** {@inheritDoc} */ + public void lineTo(int x, int y) throws IOException { + if (currentPath == null) { + currentPath = new GeneralPath(); + } + currentPath.lineTo(x, y); + } + + /** {@inheritDoc} */ + public void arcTo(double startAngle, double endAngle, int cx, int cy, + int width, int height) throws IOException { + } + + /** {@inheritDoc} */ + public void rotateCoordinates(double angle) throws IOException { + } + + /** {@inheritDoc} */ + public void translateCoordinates(int xTranslate, int yTranslate) + throws IOException { + } + + /** {@inheritDoc} */ + public void scaleCoordinates(float xScale, float yScale) + throws IOException { + } + + /** {@inheritDoc} */ + public void closePath() throws IOException { + currentPath.closePath(); + } + + /** {@inheritDoc} */ + public void clip() throws IOException { + if (currentPath == null) { + throw new IllegalStateException("No current path available!"); + } + getG2DState().updateClip(currentPath); + currentPath = null; + } + + /** {@inheritDoc} */ + public void saveGraphicsState() throws IOException { + this.painter.saveGraphicsState(); + } + + /** {@inheritDoc} */ + public void restoreGraphicsState() throws IOException { + this.painter.restoreGraphicsState(); + this.currentPath = null; + } +} diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 89031787a..e34fb4bbb 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -38,6 +38,8 @@ import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; +import org.apache.fop.render.intermediate.BorderPainter; +import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; @@ -58,7 +60,9 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { /** The font information */ protected FontInfo fontInfo; - private Java2DBorderPainter borderPainter; + private final GraphicsPainter graphicsPainter; + + private final BorderPainter borderPainter; /** The current state, holds a Graphics2D and its context */ protected Java2DGraphicsState g2dState; @@ -92,7 +96,8 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { } this.fontInfo = fontInfo; this.g2dState = new Java2DGraphicsState(g2d, fontInfo, g2d.getTransform()); - this.borderPainter = new Java2DBorderPainter(this); + graphicsPainter = new Java2DGraphicsPainter(this); + this.borderPainter = new BorderPainter(graphicsPainter); } /** {@inheritDoc} */ @@ -174,6 +179,13 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { } /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; @@ -188,19 +200,18 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, BorderProps left, BorderProps right) throws IFException { if (top != null || bottom != null || left != null || right != null) { - try { - this.borderPainter.drawBorders(rect, top, bottom, left, right); - } catch (IOException e) { - //Won't happen with Java2D - throw new IllegalStateException("Unexpected I/O error"); - } + this.borderPainter.drawBorders(rect, top, bottom, left, right, null); } } /** {@inheritDoc} */ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { - this.borderPainter.drawLine(start, end, width, color, style); + try { + this.graphicsPainter.drawLine(start, end, width, color, style); + } catch (IOException ioe) { + throw new IFException("Unexpected error drawing line", ioe); + } } /** {@inheritDoc} */ @@ -263,4 +274,6 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { g2dState.transform(transform); } + + } diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index 614ebf1d2..179a4f527 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -174,6 +174,14 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements } /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + //PCL cannot clip (only HP GL/2 can) + //If you need clipping support, switch to RenderingMode.BITMAP. + + } + + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; @@ -201,7 +209,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements final BorderProps top, final BorderProps bottom, final BorderProps left, final BorderProps right) throws IFException { if (isSpeedOptimized()) { - super.drawBorderRect(rect, top, bottom, left, right); + super.drawBorderRect(rect, top, bottom, left, right, null); return; } if (top != null || bottom != null || left != null || right != null) { @@ -525,4 +533,6 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements gen.setCursorPos(transPoint.getX(), transPoint.getY()); } + + } diff --git a/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java deleted file mode 100644 index f8090fdeb..000000000 --- a/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.pdf; - -import java.awt.Color; -import java.awt.Point; -import java.awt.Rectangle; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.fop.fo.Constants; -import org.apache.fop.render.intermediate.BorderPainter; -import org.apache.fop.traits.RuleStyle; -import org.apache.fop.util.ColorUtil; - -/** - * PDF-specific implementation of the {@link BorderPainter}. - */ -public class PDFBorderPainter extends BorderPainter { - - /** logging instance */ - private static final Log LOG = LogFactory.getLog(PDFBorderPainter.class); - - private PDFContentGenerator generator; - - /** - * Construct a border painter. - * @param generator a pdf content generator - */ - public PDFBorderPainter(PDFContentGenerator generator) { - this.generator = generator; - } - - /** {@inheritDoc} */ - protected void drawBorderLine( // CSOK: ParameterNumber - int x1, int y1, int x2, int y2, boolean horz, - boolean startOrBefore, int style, Color col) { - drawBorderLine(generator, x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f, - horz, startOrBefore, style, col); - } - - /** - * @param generator pdf content generator - * @see BorderPainter#drawBorderLine - */ - public static void drawBorderLine( // CSOK: ParameterNumber|MethodLength - PDFContentGenerator generator, - float x1, float y1, float x2, float y2, boolean horz, // CSOK: JavadocMethod - boolean startOrBefore, int style, Color col) { // CSOK: JavadocMethod - float colFactor; - float w = x2 - x1; - float h = y2 - y1; - if ((w < 0) || (h < 0)) { - LOG.error("Negative extent received (w=" + w + ", h=" + h - + "). Border won't be painted."); - return; - } - switch (style) { - case Constants.EN_DASHED: - generator.setColor(col, false); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - generator.add("[" + format(unit) + "] 0 d "); - generator.add(format(h) + " w\n"); - float ym = y1 + (h / 2); - generator.add(format(x1) + " " + format(ym) + " m " - + format(x2) + " " + format(ym) + " l S\n"); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - generator.add("[" + format(unit) + "] 0 d "); - generator.add(format(w) + " w\n"); - float xm = x1 + (w / 2); - generator.add(format(xm) + " " + format(y1) + " m " - + format(xm) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_DOTTED: - generator.setColor(col, false); - generator.add("1 J "); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - generator.add("[0 " + format(unit) + "] 0 d "); - generator.add(format(h) + " w\n"); - float ym = y1 + (h / 2); - generator.add(format(x1) + " " + format(ym) + " m " - + format(x2) + " " + format(ym) + " l S\n"); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - generator.add("[0 " + format(unit) + " ] 0 d "); - generator.add(format(w) + " w\n"); - float xm = x1 + (w / 2); - generator.add(format(xm) + " " + format(y1) + " m " - + format(xm) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_DOUBLE: - generator.setColor(col, false); - generator.add("[] 0 d "); - if (horz) { - float h3 = h / 3; - generator.add(format(h3) + " w\n"); - float ym1 = y1 + (h3 / 2); - float ym2 = ym1 + h3 + h3; - generator.add(format(x1) + " " + format(ym1) + " m " - + format(x2) + " " + format(ym1) + " l S\n"); - generator.add(format(x1) + " " + format(ym2) + " m " - + format(x2) + " " + format(ym2) + " l S\n"); - } else { - float w3 = w / 3; - generator.add(format(w3) + " w\n"); - float xm1 = x1 + (w3 / 2); - float xm2 = xm1 + w3 + w3; - generator.add(format(xm1) + " " + format(y1) + " m " - + format(xm1) + " " + format(y2) + " l S\n"); - generator.add(format(xm2) + " " + format(y1) + " m " - + format(xm2) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); - generator.add("[] 0 d "); - if (horz) { - Color uppercol = ColorUtil.lightenColor(col, -colFactor); - Color lowercol = ColorUtil.lightenColor(col, colFactor); - float h3 = h / 3; - generator.add(format(h3) + " w\n"); - float ym1 = y1 + (h3 / 2); - generator.setColor(uppercol, false); - generator.add(format(x1) + " " + format(ym1) + " m " - + format(x2) + " " + format(ym1) + " l S\n"); - generator.setColor(col, false); - generator.add(format(x1) + " " + format(ym1 + h3) + " m " - + format(x2) + " " + format(ym1 + h3) + " l S\n"); - generator.setColor(lowercol, false); - generator.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " - + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n"); - } else { - Color leftcol = ColorUtil.lightenColor(col, -colFactor); - Color rightcol = ColorUtil.lightenColor(col, colFactor); - float w3 = w / 3; - generator.add(format(w3) + " w\n"); - float xm1 = x1 + (w3 / 2); - generator.setColor(leftcol, false); - generator.add(format(xm1) + " " + format(y1) + " m " - + format(xm1) + " " + format(y2) + " l S\n"); - generator.setColor(col, false); - generator.add(format(xm1 + w3) + " " + format(y1) + " m " - + format(xm1 + w3) + " " + format(y2) + " l S\n"); - generator.setColor(rightcol, false); - generator.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " - + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_INSET: - case Constants.EN_OUTSET: - colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); - generator.add("[] 0 d "); - Color c = col; - if (horz) { - c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); - generator.add(format(h) + " w\n"); - float ym1 = y1 + (h / 2); - generator.setColor(c, false); - generator.add(format(x1) + " " + format(ym1) + " m " - + format(x2) + " " + format(ym1) + " l S\n"); - } else { - c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); - generator.add(format(w) + " w\n"); - float xm1 = x1 + (w / 2); - generator.setColor(c, false); - generator.add(format(xm1) + " " + format(y1) + " m " - + format(xm1) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_HIDDEN: - break; - default: - generator.setColor(col, false); - generator.add("[] 0 d "); - if (horz) { - generator.add(format(h) + " w\n"); - float ym = y1 + (h / 2); - generator.add(format(x1) + " " + format(ym) + " m " - + format(x2) + " " + format(ym) + " l S\n"); - } else { - generator.add(format(w) + " w\n"); - float xm = x1 + (w / 2); - generator.add(format(xm) + " " + format(y1) + " m " - + format(xm) + " " + format(y2) + " l S\n"); - } - } - } - - /** {@inheritDoc} */ - public void drawLine(Point start, Point end, - int width, Color color, RuleStyle style) { - if (start.y != end.y) { - //TODO Support arbitrary lines if necessary - throw new UnsupportedOperationException( - "Can only deal with horizontal lines right now"); - } - - saveGraphicsState(); - int half = width / 2; - int starty = start.y - half; - Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); - switch (style.getEnumValue()) { - case Constants.EN_SOLID: - case Constants.EN_DASHED: - case Constants.EN_DOUBLE: - drawBorderLine(start.x, start.y - half, end.x, end.y + half, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_DOTTED: - generator.clipRect(boundingRect); - //This displaces the dots to the right by half a dot's width - //TODO There's room for improvement here - generator.add("1 0 0 1 " + format(half) + " 0 cm\n"); - drawBorderLine(start.x, start.y - half, end.x, end.y + half, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - generator.setColor(ColorUtil.lightenColor(color, 0.6f), true); - generator.add(format(start.x) + " " + format(starty) + " m\n"); - generator.add(format(end.x) + " " + format(starty) + " l\n"); - generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n"); - generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); - generator.add("h\n"); - generator.add("f\n"); - generator.setColor(color, true); - if (style == RuleStyle.GROOVE) { - generator.add(format(start.x) + " " + format(starty) + " m\n"); - generator.add(format(end.x) + " " + format(starty) + " l\n"); - generator.add(format(end.x) + " " + format(starty + half) + " l\n"); - generator.add(format(start.x + half) + " " + format(starty + half) + " l\n"); - generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); - } else { - generator.add(format(end.x) + " " + format(starty) + " m\n"); - generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n"); - generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); - generator.add(format(start.x) + " " + format(starty + half) + " l\n"); - generator.add(format(end.x - half) + " " + format(starty + half) + " l\n"); - } - generator.add("h\n"); - generator.add("f\n"); - break; - default: - throw new UnsupportedOperationException("rule style not supported"); - } - restoreGraphicsState(); - } - - static final String format(int coordinate) { - return format(coordinate / 1000f); - } - - static final String format(float coordinate) { - return PDFContentGenerator.format(coordinate); - } - - /** {@inheritDoc} */ - protected void moveTo(int x, int y) { - generator.add(format(x) + " " + format(y) + " m "); - } - - /** {@inheritDoc} */ - protected void lineTo(int x, int y) { - generator.add(format(x) + " " + format(y) + " l "); - } - - /** {@inheritDoc} */ - protected void closePath() { - generator.add("h "); - } - - /** {@inheritDoc} */ - protected void clip() { - generator.add("W\n" + "n\n"); - } - - /** {@inheritDoc} */ - protected void saveGraphicsState() { - generator.add("q\n"); - } - - /** {@inheritDoc} */ - protected void restoreGraphicsState() { - generator.add("Q\n"); - } - -} diff --git a/src/java/org/apache/fop/render/pdf/PDFGraphicsPainter.java b/src/java/org/apache/fop/render/pdf/PDFGraphicsPainter.java new file mode 100644 index 000000000..ff1ffb5ff --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFGraphicsPainter.java @@ -0,0 +1,495 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.fop.fo.Constants; +import org.apache.fop.render.intermediate.ArcToBezierCurveTransformer; +import org.apache.fop.render.intermediate.BezierCurvePainter; +import org.apache.fop.render.intermediate.GraphicsPainter; +import org.apache.fop.traits.RuleStyle; +import org.apache.fop.util.ColorUtil; + +/** + * PDF-specific implementation of the {@link GraphicsPainter}. + */ +public class PDFGraphicsPainter implements GraphicsPainter, BezierCurvePainter { + + private final PDFContentGeneratorHelper generator; + + /** Used for drawing arcs since PS does not natively support drawing elliptic curves */ + private final ArcToBezierCurveTransformer arcToBezierCurveTransformer; + + public PDFGraphicsPainter(PDFContentGenerator generator) { + this.generator = new PDFContentGeneratorHelper(generator); + this.arcToBezierCurveTransformer = new ArcToBezierCurveTransformer(this); + } + + /** {@inheritDoc} */ + public void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, + boolean startOrBefore, int style, Color col) { + //TODO lose scale? + drawBorderLine2(x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f, + horz, startOrBefore, style, col); + } + + /** {@inheritDoc} */ + private void drawBorderLine2(float x1, float y1, float x2, float y2, boolean horz, + boolean startOrBefore, int style, Color col) { + float w = x2 - x1; + float h = y2 - y1; + float colFactor; + switch (style) { + case Constants.EN_DASHED: + generator.setColor(col); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int) (w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + float ym = y1 + (h / 2); + generator.setDashLine(unit) + .setLineWidth(h) + .strokeLine(x1, ym, x2, ym); + } else { + float unit = Math.abs(2 * w); + int rep = (int) (h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + float xm = x1 + (w / 2); + generator.setDashLine(unit) + .setLineWidth(w) + .strokeLine(xm, y1, xm, y2); + } + break; + case Constants.EN_DOTTED: + generator.setColor(col).setRoundCap(); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int) (w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + float ym = y1 + (h / 2); + generator.setDashLine(0, unit) + .setLineWidth(h) + .strokeLine(x1, ym, x2, ym); + } else { + float unit = Math.abs(2 * w); + int rep = (int) (h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + float xm = x1 + (w / 2); + generator.setDashLine(0, unit) + .setLineWidth(w) + .strokeLine(xm, y1, xm, y2); + } + break; + case Constants.EN_DOUBLE: + generator.setColor(col) + .setSolidLine(); + if (horz) { + float h3 = h / 3; + float ym1 = y1 + (h3 / 2); + float ym2 = ym1 + h3 + h3; + generator.setLineWidth(h3) + .strokeLine(x1, ym1, x2, ym1) + .strokeLine(x1, ym2, x2, ym2); + } else { + float w3 = w / 3; + float xm1 = x1 + (w3 / 2); + float xm2 = xm1 + w3 + w3; + generator.setLineWidth(w3) + .strokeLine(xm1, y1, xm1, y2) + .strokeLine(xm2, y1, xm2, y2); + } + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); + generator.setSolidLine(); + if (horz) { + Color uppercol = ColorUtil.lightenColor(col, -colFactor); + Color lowercol = ColorUtil.lightenColor(col, colFactor); + float h3 = h / 3; + float ym1 = y1 + (h3 / 2); + generator.setLineWidth(h3) + .setColor(uppercol) + .strokeLine(x1, ym1, x2, ym1) + .setColor(col) + .strokeLine(x1, ym1 + h3, x2, ym1 + h3) + .setColor(lowercol) + .strokeLine(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3); + } else { + Color leftcol = ColorUtil.lightenColor(col, -colFactor); + Color rightcol = ColorUtil.lightenColor(col, colFactor); + float w3 = w / 3; + float xm1 = x1 + (w3 / 2); + generator.setLineWidth(w3) + .setColor(leftcol) + .strokeLine(xm1, y1, xm1, y2) + .setColor(col) + .strokeLine(xm1 + w3, y1, xm1 + w3, y2) + .setColor(rightcol) + .strokeLine(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2); + } + break; + case Constants.EN_INSET: + case Constants.EN_OUTSET: + colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); + generator.setSolidLine(); + Color c = col; + if (horz) { + c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); + float ym1 = y1 + (h / 2); + generator.setLineWidth(h) + .setColor(c) + .strokeLine(x1, ym1, x2, ym1); + } else { + c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); + float xm1 = x1 + (w / 2); + generator.setLineWidth(w) + .setColor(c) + .strokeLine(xm1, y1, xm1, y2); + } + break; + case Constants.EN_HIDDEN: + break; + default: + generator.setColor(col).setSolidLine(); + if (horz) { + float ym = y1 + (h / 2); + generator.setLineWidth(h) + .strokeLine(x1, ym, x2, ym); + } else { + float xm = x1 + (w / 2); + generator.setLineWidth(w) + .strokeLine(xm, y1, xm, y2); + } + } + } + + /** {@inheritDoc} */ + public void drawLine(Point start, Point end, + int width, Color color, RuleStyle style) { + if (start.y != end.y) { + //TODO Support arbitrary lines if necessary + throw new UnsupportedOperationException( + "Can only deal with horizontal lines right now"); + } + saveGraphicsState(); + int half = width / 2; + int starty = start.y - half; + Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); + switch (style.getEnumValue()) { + case Constants.EN_SOLID: + case Constants.EN_DASHED: + case Constants.EN_DOUBLE: + drawBorderLine(start.x, start.y - half, end.x, end.y + half, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_DOTTED: + generator.clipRect(boundingRect) + //This displaces the dots to the right by half a dot's width + //TODO There's room for improvement here + .transformCoordinatesLine(1, 0, 0 , 1, half, 0); + drawBorderLine(start.x, start.y - half, end.x, end.y + half, true, true, style.getEnumValue(), + color); + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + generator.setFillColor(ColorUtil.lightenColor(color, 0.6f)) + .fillRect(start.x, start.y, end.x, starty + 2 * half) + .setFillColor(color) + .fillRidge(style, start.x, start.y, end.x, end.y, half); + break; + default: + throw new UnsupportedOperationException("rule style not supported"); + } + restoreGraphicsState(); + } + + private static String format(int coordinate) { + //TODO lose scale? + return format(coordinate / 1000f); + } + + private static String format(float coordinate) { + return PDFContentGenerator.format(coordinate); + } + + /** {@inheritDoc} */ + public void moveTo(int x, int y) { + generator.moveTo(x, y); + } + + /** {@inheritDoc} */ + public void lineTo(int x, int y) { + generator.lineTo(x, y); + } + + /** {@inheritDoc} */ + public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, + final int width, final int height) throws IOException { + arcToBezierCurveTransformer.arcTo(startAngle, endAngle, cx, cy, width, height); + } + + /** {@inheritDoc} */ + public void closePath() { + generator.closePath(); + } + + /** {@inheritDoc} */ + public void clip() { + generator.clip(); + } + + /** {@inheritDoc} */ + public void saveGraphicsState() { + generator.saveGraphicsState(); + } + + /** {@inheritDoc} */ + public void restoreGraphicsState() { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void rotateCoordinates(double angle) throws IOException { + float s = (float) Math.sin(angle); + float c = (float) Math.cos(angle); + generator.transformFloatCoordinates(c, s, -s, c, 0, 0); + } + + /** {@inheritDoc} */ + public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + generator.transformCoordinates(1000, 0, 0, 1000, xTranslate, yTranslate); + } + + /** {@inheritDoc} */ + public void scaleCoordinates(float xScale, float yScale) throws IOException { + generator.transformFloatCoordinates(xScale, 0, 0, yScale, 0, 0); + } + + /** {@inheritDoc} */ + public void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) { + generator.cubicBezierTo(p1x, p1y, p2x, p2y, p3x, p3y); + } + + // TODO consider enriching PDFContentGenerator with part of this API + private static class PDFContentGeneratorHelper { + + private final PDFContentGenerator generator; + + public PDFContentGeneratorHelper(PDFContentGenerator generator) { + this.generator = generator; + } + + public PDFContentGeneratorHelper moveTo(int x, int y) { + return add("m", format(x), format(y)); + } + + public PDFContentGeneratorHelper lineTo(int x, int y) { + return add("l", format(x), format(y)); + } + + /** {@inheritDoc} */ + public PDFContentGeneratorHelper cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) { + return add("c", format(p1x), format(p1y), format(p2x), format(p2y), format(p3x), format(p3y)); + } + + public PDFContentGeneratorHelper closePath() { + return add("h"); + } + + public PDFContentGeneratorHelper clip() { + return addLine("W\nn"); + } + + public PDFContentGeneratorHelper clipRect(Rectangle rectangle) { + generator.clipRect(rectangle); + return this; + } + + public PDFContentGeneratorHelper saveGraphicsState() { + return addLine("q"); + } + + public PDFContentGeneratorHelper restoreGraphicsState() { + return addLine("Q"); + } + + public PDFContentGeneratorHelper setSolidLine() { + generator.add("[] 0 d "); + return this; + } + + public PDFContentGeneratorHelper setRoundCap() { + return add("J", "1"); + } + + public PDFContentGeneratorHelper strokeLine(float xStart, float yStart, float xEnd, float yEnd) { + add("m", xStart, yStart); + return addLine("l S", xEnd, yEnd); + } + + public PDFContentGeneratorHelper fillRect(int xStart, int yStart, int xEnd, int yEnd) { + String xS = format(xStart); + String xE = format(xEnd); + String yS = format(yStart); + String yE = format(yEnd); + return addLine("m", xS, yS) + .addLine("l", xE, yS) + .addLine("l", xE, yE) + .addLine("l", xS, yE) + .addLine("h") + .addLine("f"); + } + + public PDFContentGeneratorHelper fillRidge(RuleStyle style, int xStart, int yStart, int xEnd, + int yEnd, int half) { + String xS = format(xStart); + String xE = format(xEnd); + String yS = format(yStart); + if (style == RuleStyle.GROOVE) { + addLine("m", xS, yS) + .addLine("l", xE, yS) + .addLine("l", xE, format(yStart + half)) + .addLine("l", format(xStart + half), format(yStart + half)) + .addLine("l", xS, format(yStart + 2 * half)); + } else { + addLine("m", xE, yS) + .addLine("l", xE, format(yStart + 2 * half)) + .addLine("l", xS, format(yStart + 2 * half)) + .addLine("l", xS, format(yStart + half)) + .addLine("l", format(xEnd - half), format(yStart + half)); + } + return addLine("h").addLine("f"); + } + + public PDFContentGeneratorHelper setLineWidth(float width) { + return addLine("w", width); + } + + public PDFContentGeneratorHelper setDashLine(float first, float... rest) { + StringBuilder sb = new StringBuilder(); + sb.append("[").append(format(first)); + for (float unit : rest) { + sb.append(" ").append(format(unit)); + } + sb.append("] 0 d "); + generator.add(sb.toString()); + return this; + } + + public PDFContentGeneratorHelper setColor(Color col) { + generator.setColor(col, false); + return this; + } + + public PDFContentGeneratorHelper setFillColor(Color col) { + generator.setColor(col, true); + return this; + } + + public PDFContentGeneratorHelper transformFloatCoordinates(float a, float b, float c, float d, + float e, float f) { + return add("cm", a, b, c, d, e, f); + } + + public PDFContentGeneratorHelper transformCoordinates(int a, int b, int c, int d, int e, int f) { + return add("cm", format(a), format(b), format(c), format(d), format(e), format(f)); + } + + public PDFContentGeneratorHelper transformCoordinatesLine(int a, int b, int c, int d, int e, int f) { + return addLine("cm", format(a), format(b), format(c), format(d), format(e), format(f)); + } + + public PDFContentGeneratorHelper add(String op) { + assert op.equals(op.trim()); + generator.add(op + " "); + return this; + } + + private PDFContentGeneratorHelper add(String op, String... args) { + add(createArgs(args), op); + return this; + } + + public PDFContentGeneratorHelper addLine(String op) { + assert op.equals(op.trim()); + generator.add(op + "\n"); + return this; + } + + public PDFContentGeneratorHelper addLine(String op, String... args) { + addLine(createArgs(args), op); + return this; + } + + private PDFContentGeneratorHelper add(String op, float... args) { + add(createArgs(args), op); + return this; + } + + public PDFContentGeneratorHelper addLine(String op, float... args) { + addLine(createArgs(args), op); + return this; + } + + private StringBuilder createArgs(float... args) { + StringBuilder sb = new StringBuilder(); + for (float arg : args) { + sb.append(format(arg)).append(" "); + } + return sb; + } + + private StringBuilder createArgs(String... args) { + StringBuilder sb = new StringBuilder(); + for (String arg : args) { + sb.append(arg).append(" "); + } + return sb; + } + + private void add(StringBuilder args, String op) { + assert op.equals(op.trim()); + generator.add(args.append(op).append(" ").toString()); + } + + private void addLine(StringBuilder args, String op) { + assert op.equals(op.trim()); + generator.add(args.append(op).append("\n").toString()); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index fa1c50318..7b636fddd 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -43,6 +43,8 @@ import org.apache.fop.pdf.PDFTextUtil; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; +import org.apache.fop.render.intermediate.BorderPainter; +import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; @@ -61,7 +63,9 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { /** The current content generator */ protected PDFContentGenerator generator; - private final PDFBorderPainter borderPainter; + private final GraphicsPainter graphicsPainter; + + private final BorderPainter borderPainter; private boolean accessEnabled; @@ -114,7 +118,8 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { super(documentHandler); this.logicalStructureHandler = logicalStructureHandler; this.generator = documentHandler.getGenerator(); - this.borderPainter = new PDFBorderPainter(this.generator); + this.graphicsPainter = new PDFGraphicsPainter(this.generator); + this.borderPainter = new BorderPainter(this.graphicsPainter); this.state = IFState.create(); accessEnabled = this.getUserAgent().isAccessibilityEnabled(); languageAvailabilityChecker = accessEnabled @@ -250,6 +255,20 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { } /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + + try { + borderPainter.clipBackground(rect, + bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IOException ioe) { + throw new IFException("I/O error while clipping background", ioe); + } + + } + + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; @@ -283,23 +302,26 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { /** {@inheritDoc} */ @Override public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException { + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top != null || bottom != null || left != null || right != null) { generator.endTextObject(); - try { - this.borderPainter.drawBorders(rect, top, bottom, left, right); - } catch (IOException ioe) { - throw new IFException("I/O error while drawing borders", ioe); - } + this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor); } } + + + /** {@inheritDoc} */ @Override public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { generator.endTextObject(); - this.borderPainter.drawLine(start, end, width, color, style); + try { + this.graphicsPainter.drawLine(start, end, width, color, style); + } catch (IOException ioe) { + throw new IFException("Cannot draw line", ioe); + } } private Typeface getTypeface(String fontName) { diff --git a/src/java/org/apache/fop/render/ps/PSBorderPainter.java b/src/java/org/apache/fop/render/ps/PSBorderPainter.java deleted file mode 100644 index a52974d36..000000000 --- a/src/java/org/apache/fop/render/ps/PSBorderPainter.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.ps; - -import java.awt.Color; -import java.awt.Point; -import java.io.IOException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.xmlgraphics.ps.PSGenerator; - -import org.apache.fop.fo.Constants; -import org.apache.fop.render.intermediate.BorderPainter; -import org.apache.fop.traits.RuleStyle; -import org.apache.fop.util.ColorUtil; - -/** - * PostScript-specific implementation of the {@link BorderPainter}. - */ -public class PSBorderPainter extends BorderPainter { - - /** logging instance */ - private static Log log = LogFactory.getLog(PSBorderPainter.class); - - private PSGenerator generator; - - /** - * Creates a new border painter for PostScript. - * @param generator the PostScript generator - */ - public PSBorderPainter(PSGenerator generator) { - this.generator = generator; - } - - /** {@inheritDoc} */ - protected void drawBorderLine( // CSOK: ParameterNumber - int x1, int y1, int x2, int y2, boolean horz, - boolean startOrBefore, int style, Color col) throws IOException { - drawBorderLine(generator, toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), - horz, startOrBefore, style, col); - } - - private static void drawLine(PSGenerator gen, - float startx, float starty, float endx, float endy) throws IOException { - gen.writeln(gen.formatDouble(startx) + " " - + gen.formatDouble(starty) + " " + gen.mapCommand("moveto") + " " - + gen.formatDouble(endx) + " " - + gen.formatDouble(endy) + " " + gen.mapCommand("lineto") + " " - + gen.mapCommand("stroke") + " " + gen.mapCommand("newpath")); - } - - /** - * @param gen ps content generator - * @see BorderPainter#drawBorderLine - */ - public static void drawBorderLine( // CSOK: ParameterNumber - PSGenerator gen, - float x1, float y1, float x2, float y2, boolean horz, // CSOK: JavadocMethod - boolean startOrBefore, int style, Color col) // CSOK: JavadocMethod - throws IOException { // CSOK: JavadocMethod - float w = x2 - x1; - float h = y2 - y1; - if ((w < 0) || (h < 0)) { - log.error("Negative extent received. Border won't be painted."); - return; - } - switch (style) { - case Constants.EN_DASHED: - gen.useColor(col); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - gen.useDash("[" + unit + "] 0"); - gen.useLineCap(0); - gen.useLineWidth(h); - float ym = y1 + (h / 2); - drawLine(gen, x1, ym, x2, ym); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - gen.useDash("[" + unit + "] 0"); - gen.useLineCap(0); - gen.useLineWidth(w); - float xm = x1 + (w / 2); - drawLine(gen, xm, y1, xm, y2); - } - break; - case Constants.EN_DOTTED: - gen.useColor(col); - gen.useLineCap(1); //Rounded! - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - gen.useDash("[0 " + unit + "] 0"); - gen.useLineWidth(h); - float ym = y1 + (h / 2); - drawLine(gen, x1, ym, x2, ym); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - gen.useDash("[0 " + unit + "] 0"); - gen.useLineWidth(w); - float xm = x1 + (w / 2); - drawLine(gen, xm, y1, xm, y2); - } - break; - case Constants.EN_DOUBLE: - gen.useColor(col); - gen.useDash(null); - if (horz) { - float h3 = h / 3; - gen.useLineWidth(h3); - float ym1 = y1 + (h3 / 2); - float ym2 = ym1 + h3 + h3; - drawLine(gen, x1, ym1, x2, ym1); - drawLine(gen, x1, ym2, x2, ym2); - } else { - float w3 = w / 3; - gen.useLineWidth(w3); - float xm1 = x1 + (w3 / 2); - float xm2 = xm1 + w3 + w3; - drawLine(gen, xm1, y1, xm1, y2); - drawLine(gen, xm2, y1, xm2, y2); - } - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); - gen.useDash(null); - if (horz) { - Color uppercol = ColorUtil.lightenColor(col, -colFactor); - Color lowercol = ColorUtil.lightenColor(col, colFactor); - float h3 = h / 3; - gen.useLineWidth(h3); - float ym1 = y1 + (h3 / 2); - gen.useColor(uppercol); - drawLine(gen, x1, ym1, x2, ym1); - gen.useColor(col); - drawLine(gen, x1, ym1 + h3, x2, ym1 + h3); - gen.useColor(lowercol); - drawLine(gen, x1, ym1 + h3 + h3, x2, ym1 + h3 + h3); - } else { - Color leftcol = ColorUtil.lightenColor(col, -colFactor); - Color rightcol = ColorUtil.lightenColor(col, colFactor); - float w3 = w / 3; - gen.useLineWidth(w3); - float xm1 = x1 + (w3 / 2); - gen.useColor(leftcol); - drawLine(gen, xm1, y1, xm1, y2); - gen.useColor(col); - drawLine(gen, xm1 + w3, y1, xm1 + w3, y2); - gen.useColor(rightcol); - drawLine(gen, xm1 + w3 + w3, y1, xm1 + w3 + w3, y2); - } - break; - case Constants.EN_INSET: - case Constants.EN_OUTSET: - colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); - gen.useDash(null); - if (horz) { - Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); - gen.useLineWidth(h); - float ym1 = y1 + (h / 2); - gen.useColor(c); - drawLine(gen, x1, ym1, x2, ym1); - } else { - Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); - gen.useLineWidth(w); - float xm1 = x1 + (w / 2); - gen.useColor(c); - drawLine(gen, xm1, y1, xm1, y2); - } - break; - case Constants.EN_HIDDEN: - break; - default: - gen.useColor(col); - gen.useDash(null); - gen.useLineCap(0); - if (horz) { - gen.useLineWidth(h); - float ym = y1 + (h / 2); - drawLine(gen, x1, ym, x2, ym); - } else { - gen.useLineWidth(w); - float xm = x1 + (w / 2); - drawLine(gen, xm, y1, xm, y2); - } - } - } - - /** {@inheritDoc} */ - public void drawLine(Point start, Point end, - int width, Color color, RuleStyle style) throws IOException { - if (start.y != end.y) { - //TODO Support arbitrary lines if necessary - throw new UnsupportedOperationException( - "Can only deal with horizontal lines right now"); - } - - saveGraphicsState(); - int half = width / 2; - int starty = start.y - half; - //Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); - - switch (style.getEnumValue()) { - case Constants.EN_SOLID: - case Constants.EN_DASHED: - case Constants.EN_DOUBLE: - drawBorderLine(start.x, starty, end.x, starty + width, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_DOTTED: - clipRect(start.x, starty, end.x - start.x, width); - //This displaces the dots to the right by half a dot's width - //TODO There's room for improvement here - generator.concatMatrix(1, 0, 0, 1, toPoints(half), 0); - drawBorderLine(start.x, starty, end.x, starty + width, - true, true, style.getEnumValue(), color); - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - generator.useColor(ColorUtil.lightenColor(color, 0.6f)); - moveTo(start.x, starty); - lineTo(end.x, starty); - lineTo(end.x, starty + 2 * half); - lineTo(start.x, starty + 2 * half); - closePath(); - generator.write(" " + generator.mapCommand("fill")); - generator.writeln(" " + generator.mapCommand("newpath")); - generator.useColor(color); - if (style == RuleStyle.GROOVE) { - moveTo(start.x, starty); - lineTo(end.x, starty); - lineTo(end.x, starty + half); - lineTo(start.x + half, starty + half); - lineTo(start.x, starty + 2 * half); - } else { - moveTo(end.x, starty); - lineTo(end.x, starty + 2 * half); - lineTo(start.x, starty + 2 * half); - lineTo(start.x, starty + half); - lineTo(end.x - half, starty + half); - } - closePath(); - generator.write(" " + generator.mapCommand("fill")); - generator.writeln(" " + generator.mapCommand("newpath")); - break; - default: - throw new UnsupportedOperationException("rule style not supported"); - } - - restoreGraphicsState(); - - } - - private static float toPoints(int mpt) { - return mpt / 1000f; - } - - /** {@inheritDoc} */ - protected void moveTo(int x, int y) throws IOException { - generator.writeln(generator.formatDouble(toPoints(x)) + " " - + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("moveto")); - } - - /** {@inheritDoc} */ - protected void lineTo(int x, int y) throws IOException { - generator.writeln(generator.formatDouble(toPoints(x)) + " " - + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("lineto")); - } - - /** {@inheritDoc} */ - protected void closePath() throws IOException { - generator.writeln("cp"); - } - - private void clipRect(int x, int y, int width, int height) throws IOException { - generator.defineRect(toPoints(x), toPoints(y), toPoints(width), toPoints(height)); - clip(); - } - - /** {@inheritDoc} */ - protected void clip() throws IOException { - generator.writeln(generator.mapCommand("clip") + " " + generator.mapCommand("newpath")); - } - - /** {@inheritDoc} */ - protected void saveGraphicsState() throws IOException { - generator.saveGraphicsState(); - } - - /** {@inheritDoc} */ - protected void restoreGraphicsState() throws IOException { - generator.restoreGraphicsState(); - } - -} diff --git a/src/java/org/apache/fop/render/ps/PSGraphicsPainter.java b/src/java/org/apache/fop/render/ps/PSGraphicsPainter.java new file mode 100644 index 000000000..ab766f701 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSGraphicsPainter.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.awt.Color; +import java.awt.Point; +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.ps.PSGenerator; + +import org.apache.fop.fo.Constants; +import org.apache.fop.render.intermediate.ArcToBezierCurveTransformer; +import org.apache.fop.render.intermediate.BezierCurvePainter; +import org.apache.fop.render.intermediate.GraphicsPainter; +import org.apache.fop.traits.RuleStyle; +import org.apache.fop.util.ColorUtil; + +/** + * PostScript-specific implementation of the {@link BorderPainter}. + */ +public class PSGraphicsPainter implements GraphicsPainter, BezierCurvePainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(PSGraphicsPainter.class); + + private final PSGenerator generator; + + /** Used for drawing arcs since PS does not natively support drawing elliptic curves */ + private final ArcToBezierCurveTransformer arcToBezierCurveTransformer; + + /** + * Creates a new border painter for PostScript. + * @param generator the PostScript generator + */ + public PSGraphicsPainter(PSGenerator generator) { + this.generator = generator; + this.arcToBezierCurveTransformer = new ArcToBezierCurveTransformer(this); + } + + /** {@inheritDoc} */ + public void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, + boolean startOrBefore, int style, Color col) throws IOException { + drawBorderLine(generator, toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), + horz, startOrBefore, style, col); + } + + private static void drawLine(PSGenerator gen, + float startx, float starty, float endx, float endy) throws IOException { + gen.writeln(gen.formatDouble(startx) + " " + + gen.formatDouble(starty) + " " + gen.mapCommand("moveto") + " " + + gen.formatDouble(endx) + " " + + gen.formatDouble(endy) + " " + gen.mapCommand("lineto") + " " + + gen.mapCommand("stroke") + " " + gen.mapCommand("newpath")); + } + + /** {@inheritDoc} */ + public static void drawBorderLine(PSGenerator gen, + float x1, float y1, float x2, float y2, boolean horz, + boolean startOrBefore, int style, Color col) throws IOException { + float w = x2 - x1; + float h = y2 - y1; + if ((w < 0) || (h < 0)) { + log.error("Negative extent received. Border won't be painted."); + return; + } + switch (style) { + case Constants.EN_DASHED: + gen.useColor(col); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int) (w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + gen.useDash("[" + unit + "] 0"); + gen.useLineCap(0); + gen.useLineWidth(h); + float ym = y1 + (h / 2); + drawLine(gen, x1, ym, x2, ym); + } else { + float unit = Math.abs(2 * w); + int rep = (int) (h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + gen.useDash("[" + unit + "] 0"); + gen.useLineCap(0); + gen.useLineWidth(w); + float xm = x1 + (w / 2); + drawLine(gen, xm, y1, xm, y2); + } + break; + case Constants.EN_DOTTED: + gen.useColor(col); + gen.useLineCap(1); //Rounded! + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int) (w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + gen.useDash("[0 " + unit + "] 0"); + gen.useLineWidth(h); + float ym = y1 + (h / 2); + drawLine(gen, x1, ym, x2, ym); + } else { + float unit = Math.abs(2 * w); + int rep = (int) (h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + gen.useDash("[0 " + unit + "] 0"); + gen.useLineWidth(w); + float xm = x1 + (w / 2); + drawLine(gen, xm, y1, xm, y2); + } + break; + case Constants.EN_DOUBLE: + gen.useColor(col); + gen.useDash(null); + if (horz) { + float h3 = h / 3; + gen.useLineWidth(h3); + float ym1 = y1 + (h3 / 2); + float ym2 = ym1 + h3 + h3; + drawLine(gen, x1, ym1, x2, ym1); + drawLine(gen, x1, ym2, x2, ym2); + } else { + float w3 = w / 3; + gen.useLineWidth(w3); + float xm1 = x1 + (w3 / 2); + float xm2 = xm1 + w3 + w3; + drawLine(gen, xm1, y1, xm1, y2); + drawLine(gen, xm2, y1, xm2, y2); + } + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); + gen.useDash(null); + if (horz) { + Color uppercol = ColorUtil.lightenColor(col, -colFactor); + Color lowercol = ColorUtil.lightenColor(col, colFactor); + float h3 = h / 3; + gen.useLineWidth(h3); + float ym1 = y1 + (h3 / 2); + gen.useColor(uppercol); + drawLine(gen, x1, ym1, x2, ym1); + gen.useColor(col); + drawLine(gen, x1, ym1 + h3, x2, ym1 + h3); + gen.useColor(lowercol); + drawLine(gen, x1, ym1 + h3 + h3, x2, ym1 + h3 + h3); + } else { + Color leftcol = ColorUtil.lightenColor(col, -colFactor); + Color rightcol = ColorUtil.lightenColor(col, colFactor); + float w3 = w / 3; + gen.useLineWidth(w3); + float xm1 = x1 + (w3 / 2); + gen.useColor(leftcol); + drawLine(gen, xm1, y1, xm1, y2); + gen.useColor(col); + drawLine(gen, xm1 + w3, y1, xm1 + w3, y2); + gen.useColor(rightcol); + drawLine(gen, xm1 + w3 + w3, y1, xm1 + w3 + w3, y2); + } + break; + case Constants.EN_INSET: + case Constants.EN_OUTSET: + colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); + gen.useDash(null); + if (horz) { + Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); + gen.useLineWidth(h); + float ym1 = y1 + (h / 2); + gen.useColor(c); + drawLine(gen, x1, ym1, x2, ym1); + } else { + Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor); + gen.useLineWidth(w); + float xm1 = x1 + (w / 2); + gen.useColor(c); + drawLine(gen, xm1, y1, xm1, y2); + } + break; + case Constants.EN_HIDDEN: + break; + default: + gen.useColor(col); + gen.useDash(null); + gen.useLineCap(0); + if (horz) { + gen.useLineWidth(h); + float ym = y1 + (h / 2); + drawLine(gen, x1, ym, x2, ym); + } else { + gen.useLineWidth(w); + float xm = x1 + (w / 2); + drawLine(gen, xm, y1, xm, y2); + } + } + } + + /** {@inheritDoc} */ + public void drawLine(Point start, Point end, + int width, Color color, RuleStyle style) throws IOException { + if (start.y != end.y) { + //TODO Support arbitrary lines if necessary + throw new UnsupportedOperationException( + "Can only deal with horizontal lines right now"); + } + + saveGraphicsState(); + int half = width / 2; + int starty = start.y - half; + //Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); + + switch (style.getEnumValue()) { + case Constants.EN_SOLID: + case Constants.EN_DASHED: + case Constants.EN_DOUBLE: + drawBorderLine(start.x, starty, end.x, starty + width, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_DOTTED: + clipRect(start.x, starty, end.x - start.x, width); + //This displaces the dots to the right by half a dot's width + //TODO There's room for improvement here + generator.concatMatrix(1, 0, 0, 1, toPoints(half), 0); + drawBorderLine(start.x, starty, end.x, starty + width, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + generator.useColor(ColorUtil.lightenColor(color, 0.6f)); + moveTo(start.x, starty); + lineTo(end.x, starty); + lineTo(end.x, starty + 2 * half); + lineTo(start.x, starty + 2 * half); + closePath(); + generator.write(" " + generator.mapCommand("fill")); + generator.writeln(" " + generator.mapCommand("newpath")); + generator.useColor(color); + if (style == RuleStyle.GROOVE) { + moveTo(start.x, starty); + lineTo(end.x, starty); + lineTo(end.x, starty + half); + lineTo(start.x + half, starty + half); + lineTo(start.x, starty + 2 * half); + } else { + moveTo(end.x, starty); + lineTo(end.x, starty + 2 * half); + lineTo(start.x, starty + 2 * half); + lineTo(start.x, starty + half); + lineTo(end.x - half, starty + half); + } + closePath(); + generator.write(" " + generator.mapCommand("fill")); + generator.writeln(" " + generator.mapCommand("newpath")); + break; + default: + throw new UnsupportedOperationException("rule style not supported"); + } + + restoreGraphicsState(); + + } + + private static float toPoints(int mpt) { + return mpt / 1000f; + } + + /** {@inheritDoc} */ + public void moveTo(int x, int y) throws IOException { + generator.writeln(generator.formatDouble(toPoints(x)) + " " + + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("moveto")); + } + + /** {@inheritDoc} */ + public void lineTo(int x, int y) throws IOException { + generator.writeln(generator.formatDouble(toPoints(x)) + " " + + generator.formatDouble(toPoints(y)) + " " + generator.mapCommand("lineto")); + } + + /** {@inheritDoc} */ + public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, + final int width, final int height) throws IOException { + arcToBezierCurveTransformer.arcTo(startAngle, endAngle, cx, cy, width, height); + } + + /** {@inheritDoc} */ + public void closePath() throws IOException { + generator.writeln("cp"); + } + + private void clipRect(int x, int y, int width, int height) throws IOException { + generator.defineRect(toPoints(x), toPoints(y), toPoints(width), toPoints(height)); + clip(); + } + + /** {@inheritDoc} */ + public void clip() throws IOException { + generator.writeln(generator.mapCommand("clip") + " " + generator.mapCommand("newpath")); + } + + /** {@inheritDoc} */ + public void saveGraphicsState() throws IOException { + generator.saveGraphicsState(); + } + + /** {@inheritDoc} */ + public void restoreGraphicsState() throws IOException { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void rotateCoordinates(double angle) throws IOException { + StringBuffer sb = new StringBuffer() + .append(generator.formatDouble(angle * 180d / Math.PI)) + .append(" rotate "); + generator.writeln(sb.toString()); + } + + /** {@inheritDoc} */ + public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + StringBuffer sb = new StringBuffer() + .append(generator.formatDouble(toPoints(xTranslate))) + .append(" ") + .append(generator.formatDouble(toPoints(yTranslate))) + .append(" translate "); + generator.writeln(sb.toString()); + } + + /** {@inheritDoc} */ + public void scaleCoordinates(float xScale, float yScale) throws IOException { + StringBuffer sb = new StringBuffer() + .append(generator.formatDouble(xScale)) + .append(" ") + .append(generator.formatDouble(yScale)) + .append(" scale "); + generator.writeln(sb.toString()); + } + + /** {@inheritDoc} */ + public void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) + throws IOException { + StringBuffer sb = new StringBuffer() + .append(generator.formatDouble(toPoints(p1x))) + .append(" ") + .append(generator.formatDouble(toPoints(p1y))) + .append(" ") + .append(generator.formatDouble(toPoints(p2x))) + .append(" ") + .append(generator.formatDouble(toPoints(p2y))) + .append(" ") + .append(generator.formatDouble(toPoints(p3x))) + .append(" ") + .append(generator.formatDouble(toPoints(p3y))) + .append(" curveto "); + generator.writeln(sb.toString()); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index 63e947e55..97bf7e647 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -48,6 +48,8 @@ import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; +import org.apache.fop.render.intermediate.BorderPainter; +import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFUtil; @@ -64,7 +66,9 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { /** logging instance */ private static Log log = LogFactory.getLog(PSPainter.class); - private PSBorderPainter borderPainter; + private final GraphicsPainter graphicsPainter; + + private BorderPainter borderPainter; private boolean inTextMode = false; @@ -78,7 +82,8 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { protected PSPainter(PSDocumentHandler documentHandler, IFState state) { super(documentHandler); - this.borderPainter = new PSBorderPainter(getGenerator()); + this.graphicsPainter = new PSGraphicsPainter(getGenerator()); + this.borderPainter = new BorderPainter(graphicsPainter); this.state = state; } @@ -199,6 +204,20 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + + try { + borderPainter.clipBackground(rect, + bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IOException ioe) { + throw new IFException("I/O error while clipping background", ioe); + } + + } + + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; @@ -225,15 +244,15 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, - BorderProps left, BorderProps right) throws IFException { + BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top != null || bottom != null || left != null || right != null) { try { endTextObject(); if (getDocumentHandler().getPSUtil().getRenderingMode() == PSRenderingMode.SIZE && hasOnlySolidBorders(top, bottom, left, right)) { - super.drawBorderRect(rect, top, bottom, left, right); + super.drawBorderRect(rect, top, bottom, left, right, innerBackgroundColor); } else { - this.borderPainter.drawBorders(rect, top, bottom, left, right); + this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor); } } catch (IOException ioe) { throw new IFException("I/O error in drawBorderRect()", ioe); @@ -246,7 +265,7 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { throws IFException { try { endTextObject(); - this.borderPainter.drawLine(start, end, width, color, style); + this.graphicsPainter.drawLine(start, end, width, color, style); } catch (IOException ioe) { throw new IFException("I/O error in drawLine()", ioe); } diff --git a/src/java/org/apache/fop/traits/BorderProps.java b/src/java/org/apache/fop/traits/BorderProps.java index ae7a9a434..e274517d9 100644 --- a/src/java/org/apache/fop/traits/BorderProps.java +++ b/src/java/org/apache/fop/traits/BorderProps.java @@ -34,47 +34,90 @@ import org.apache.fop.util.ColorUtil; */ public class BorderProps implements Serializable { - private static final long serialVersionUID = -886871454032189183L; + private static final long serialVersionUID = 8022237892391068187L; - /** Separate border model */ - public static final int SEPARATE = 0; - /** Collapsing border model, for borders inside a table */ - public static final int COLLAPSE_INNER = 1; - /** Collapsing border model, for borders at the table's outer border */ - public static final int COLLAPSE_OUTER = 2; + public enum Mode { + SEPARATE("separate") { + @Override + int getClippedWidth(BorderProps bp) { + return 0; + } + }, + COLLAPSE_INNER("collapse-inner"), // for borders inside a table + COLLAPSE_OUTER("collapse-outer"); // for borders at the table's outer border + + private final String value; + + Mode(String value) { + this.value = value; + } + + int getClippedWidth(BorderProps bp) { + return bp.width / 2; + }; + } /** Border style (one of EN_*) */ - public int style; // Enum for border style // CSOK: VisibilityModifier + public final int style; // Enum for border style // CSOK: VisibilityModifier /** Border color */ - public Color color; // CSOK: VisibilityModifier + public final Color color; // CSOK: VisibilityModifier + /** Border width */ - public int width; // CSOK: VisibilityModifier - /** Border mode (one of SEPARATE, COLLAPSE_INNER and COLLAPSE_OUTER) */ - public int mode; // CSOK: VisibilityModifier + public final int width; // CSOK: VisibilityModifier + + private final int radiusStart; + + private final int radiusEnd; + + /** Border mode */ + private final Mode mode; // CSOK: VisibilityModifier /** * Constructs a new BorderProps instance. * @param style border style (one of EN_*) * @param width border width + * @param radiusStart radius of start corner in the direction perpendicular to border segment + * @param radiusEnd radius of end corner in the direction perpendicular to border segment * @param color border color * @param mode border mode ((one of SEPARATE, COLLAPSE_INNER and COLLAPSE_OUTER) */ - public BorderProps(int style, int width, Color color, int mode) { + public BorderProps(int style, int width, int radiusStart, int radiusEnd, Color color, Mode mode) { this.style = style; this.width = width; + this.radiusStart = radiusStart; + this.radiusEnd = radiusEnd; this.color = color; this.mode = mode; } /** - * Constructs a new BorderProps instance. - * @param style border style (one of the XSL enum values for border style) + * Factory method for a new BorderProps instance with rectangular corners. + * @param style border style (one of EN_*) * @param width border width * @param color border color * @param mode border mode ((one of SEPARATE, COLLAPSE_INNER and COLLAPSE_OUTER) */ - public BorderProps(String style, int width, Color color, int mode) { - this(getConstantForStyle(style), width, color, mode); + public static BorderProps makeRectangular(int style, int width, Color color, Mode mode) { + return new BorderProps(style, width, 0, 0, color, mode); + } + + private BorderProps(String style, int width, int radiusStart, int radiusEnd, Color color, Mode mode) { + this(getConstantForStyle(style), width, radiusStart, radiusEnd, color, mode); + } + + /** + * + * @return the radius of the corner adjacent to the before or start border + */ + public int getRadiusStart() { + return radiusStart; + } + + /** + * @return the radius of the corner adjacent to the after or end border + */ + public int getRadiusEnd() { + return radiusEnd; } /** @@ -82,11 +125,7 @@ public class BorderProps implements Serializable { * @return the effective width of the clipped part of the border */ public static int getClippedWidth(BorderProps bp) { - if ((bp != null) && (bp.mode != SEPARATE)) { - return bp.width / 2; - } else { - return 0; - } + return bp == null ? 0 : bp.mode.getClippedWidth(bp); } private String getStyleString() { @@ -97,6 +136,10 @@ public class BorderProps implements Serializable { return BorderStyle.valueOf(style).getEnumValue(); } + public boolean isCollapseOuter() { + return mode == Mode.COLLAPSE_OUTER; + } + /** {@inheritDoc} */ @Override public int hashCode() { @@ -112,12 +155,14 @@ public class BorderProps implements Serializable { return true; } else { if (obj instanceof BorderProps) { - BorderProps other = (BorderProps)obj; + BorderProps other = (BorderProps) obj; return (style == other.style) && org.apache.xmlgraphics.java2d.color.ColorUtil.isSameColor( color, other.color) && width == other.width - && mode == other.mode; + && mode == other.mode + && radiusStart == other.radiusStart + && radiusEnd == other.radiusEnd; } } return false; @@ -131,60 +176,79 @@ public class BorderProps implements Serializable { * @return a BorderProps instance */ public static BorderProps valueOf(FOUserAgent foUserAgent, String s) { - if (s.startsWith("(") && s.endsWith(")")) { - s = s.substring(1, s.length() - 1); - Pattern pattern = Pattern.compile("([^,\\(]+(?:\\(.*\\))?)"); - Matcher m = pattern.matcher(s); - boolean found; - found = m.find(); - String style = m.group(); - found = m.find(); - String color = m.group(); - found = m.find(); - int width = Integer.parseInt(m.group()); - int mode = SEPARATE; - found = m.find(); - if (found) { - String ms = m.group(); - if ("collapse-inner".equalsIgnoreCase(ms)) { - mode = COLLAPSE_INNER; - } else if ("collapse-outer".equalsIgnoreCase(ms)) { - mode = COLLAPSE_OUTER; - } - } - Color c; - try { - c = ColorUtil.parseColorString(foUserAgent, color); - } catch (PropertyException e) { - throw new IllegalArgumentException(e.getMessage()); - } - - return new BorderProps(style, width, c, mode); - } else { - throw new IllegalArgumentException("BorderProps must be surrounded by parentheses"); - } + return BorderPropsDeserializer.INSTANCE.valueOf(foUserAgent, s); } - /** {@inheritDoc} */ @Override public String toString() { StringBuffer sbuf = new StringBuffer(); - sbuf.append('('); - sbuf.append(getStyleString()); - sbuf.append(','); - sbuf.append(ColorUtil.colorToString(color)); - sbuf.append(','); - sbuf.append(width); - if (mode != SEPARATE) { - sbuf.append(','); - if (mode == COLLAPSE_INNER) { - sbuf.append("collapse-inner"); - } else { - sbuf.append("collapse-outer"); + sbuf.append('(') + .append(getStyleString()).append(',') + .append(ColorUtil.colorToString(color)).append(',') + .append(width); + if (!mode.equals(Mode.SEPARATE)) { + sbuf.append(",").append(mode.value); + } + + if (radiusStart != 0 || radiusEnd != 0) { + if (mode.equals(Mode.SEPARATE)) { + // Because of the corner radii properties the mode must be set + // so that the parameter index is consistent + sbuf.append(",").append(Mode.SEPARATE.value); } + sbuf.append(',').append(radiusStart) + .append(',').append(radiusEnd); } sbuf.append(')'); return sbuf.toString(); } + private static final class BorderPropsDeserializer { + + private static final BorderPropsDeserializer INSTANCE = new BorderPropsDeserializer(); + + private static final Pattern PATTERN = Pattern.compile("([^,\\(]+(?:\\(.*\\))?)"); + + private BorderPropsDeserializer() { + } + + public BorderProps valueOf(FOUserAgent foUserAgent, String s) { + if (s.startsWith("(") && s.endsWith(")")) { + s = s.substring(1, s.length() - 1); + Matcher m = PATTERN.matcher(s); + m.find(); + String style = m.group(); + m.find(); + String color = m.group(); + m.find(); + int width = Integer.parseInt(m.group()); + Mode mode = Mode.SEPARATE; + if (m.find()) { + String ms = m.group(); + if (Mode.COLLAPSE_INNER.value.equalsIgnoreCase(ms)) { + mode = Mode.COLLAPSE_INNER; + } else if (Mode.COLLAPSE_OUTER.value.equalsIgnoreCase(ms)) { + mode = Mode.COLLAPSE_OUTER; + } + } + Color c; + try { + c = ColorUtil.parseColorString(foUserAgent, color); + } catch (PropertyException e) { + throw new IllegalArgumentException(e.getMessage()); + } + int startRadius = 0; + int endRadius = 0; + if (m.find()) { + startRadius = Integer.parseInt(m.group()); + m.find(); + endRadius = Integer.parseInt(m.group()); + } + return new BorderProps(style, width, startRadius, endRadius, c, mode); + } else { + throw new IllegalArgumentException("BorderProps must be surrounded by parentheses"); + } + } + } + } |