From: Jeremias Maerki Date: Thu, 30 Sep 2010 11:51:08 +0000 (+0000) Subject: Bugzilla #49913: X-Git-Tag: fop-2_0~276^2~17 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ba7f42b128a5265f443c401613378aaa504d727d;p=xmlgraphics-fop.git Bugzilla #49913: Initial code drop for support of rounded corners. Submitted by: Peter Hancock Changes to patch: - Java 1.5 methods eliminated - Some (not all!) Checkstyle violations fixed. - Fixed some excessive and missing whitespace - paintCornersAsBitmap() in AFPPainter was missing a call to ImageSize.calcPixelsFromSize() - Made AT representation of border traits cleaner - Added a TODO for table borders (inner and outer versions) git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_RoundedCorners@1003020 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd index 4e0b7d115..904eeec21 100644 --- a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd +++ b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd @@ -99,6 +99,7 @@ + diff --git a/src/java/org/apache/fop/afp/AFPResourceManager.java b/src/java/org/apache/fop/afp/AFPResourceManager.java index b7e1abc01..6aad0ca50 100644 --- a/src/java/org/apache/fop/afp/AFPResourceManager.java +++ b/src/java/org/apache/fop/afp/AFPResourceManager.java @@ -61,11 +61,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/**/ includeNameMap - = new java.util.HashMap()/**/; - - private Map pageSegmentMap = new java.util.HashMap(); + /** Mapping of resourceInfo to AbstractCachedObject */ + private final Map/**/ includeObjectCache + = new java.util.HashMap()/**/; private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults(); @@ -134,17 +132,7 @@ public class AFPResourceManager { AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo(); updateResourceInfoUri(resourceInfo); - String objectName = (String)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; - } - - objectName = (String)pageSegmentMap.get(resourceInfo); - if (objectName != null) { - // an existing data resource so reference it by adding an include to the current page - includePageSegment(dataObjectInfo, objectName); + if (includeCachedObject(resourceInfo, null)) { return; } @@ -170,7 +158,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()) { @@ -189,22 +177,109 @@ public class AFPResourceManager { // add data object into its resource group destination resourceGroup.addObject(namedObj); - // create the include object - 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 + AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo(); + 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) { @@ -292,8 +367,12 @@ public class AFPResourceManager { resourceInfo.setName(resourceName); resourceInfo.setUri(uri.toASCIIString()); - String objectName = (String)includeNameMap.get(resourceInfo); - if (objectName == null) { + + AbstractCachedObject cachedObject = (AbstractCachedObject) + includeObjectCache.get(resourceInfo); + + if (cachedObject == null ) { + if (log.isDebugEnabled()) { log.debug("Adding included resource: " + resourceName); } @@ -306,8 +385,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 } diff --git a/src/java/org/apache/fop/afp/AFPStreamer.java b/src/java/org/apache/fop/afp/AFPStreamer.java index 33d1dbf90..154ca4cc9 100644 --- a/src/java/org/apache/fop/afp/AFPStreamer.java +++ b/src/java/org/apache/fop/afp/AFPStreamer.java @@ -158,7 +158,7 @@ public class AFPStreamer implements Streamable { */ // write out any external resource groups public void close() throws IOException { - Iterator it = pathResourceGroupMap.entrySet().iterator(); + Iterator it = pathResourceGroupMap.values().iterator(); while (it.hasNext()) { StreamedResourceGroup resourceGroup = (StreamedResourceGroup)it.next(); resourceGroup.close(); diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index 42c5462e4..9e9d576c3 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -197,12 +197,13 @@ public final class Trait implements Serializable { public static final Integer OVERLINE_COLOR = new Integer(35); /** Trait for color of linethrough decorations when rendering inline parent. */ public static final Integer LINETHROUGH_COLOR = new Integer(36); - + /** The ptr trait. Used for accessibility */ public static final Integer PTR = new Integer(37); + /** Maximum value used by trait keys */ - public static final int MAX_TRAIT_KEY = 37; + public static final int MAX_TRAIT_KEY = 38; private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1]; diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index 5f23502f3..d9b0f2527 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -779,8 +779,30 @@ public interface Constants { */ int PR_X_ALT_TEXT = 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_START = 280; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_START_RADIUS_END = 281; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_END_RADIUS_START = 282; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_END_RADIUS_END = 283; + /** Property constant FOP proprietary*/ + int PR_X_BORDER_RADIUS = 284; + /** Number of property constants defined */ - int PROPERTY_COUNT = 275; + int PROPERTY_COUNT = 284; // compound property constants diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index c711a76c0..e79ed25b6 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -29,6 +29,7 @@ import org.apache.fop.fo.flow.table.TableFObj.ColumnNumberPropertyMaker; 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; @@ -97,6 +98,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; @@ -201,6 +203,15 @@ 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); + genericCondCornerRadius.addShorthand(generics[PR_X_BORDER_RADIUS]); + + // GenericBreak genericBreak = new EnumProperty.Maker(0); genericBreak.setInherited(false); @@ -2544,6 +2555,59 @@ public final class FOPropertyMapping implements Constants { m.setDefault(""); addPropertyMaker("fox:alt-text", m); + + // fox:border-*-radius-* + + // border-before-radius-start + m = new CondLengthProperty.Maker(PR_X_BORDER_BEFORE_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + + addPropertyMaker("fox:border-before-radius-start", m); + + // border-before-radius-end + m = new CondLengthProperty.Maker(PR_X_BORDER_BEFORE_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-before-radius-end", m); + + // border-after-radius-start + m = new CondLengthProperty.Maker(PR_X_BORDER_AFTER_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-after-radius-start", m); + + // border-after-radius-end + m = new CondLengthProperty.Maker(PR_X_BORDER_AFTER_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-after-radius-end", m); + + // border-start-radius-before + m = new CondLengthProperty.Maker(PR_X_BORDER_START_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-start-radius-before", m); + + // border-start-radius-after + m = new CondLengthProperty.Maker(PR_X_BORDER_START_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-start-radius-after", m); + + // border-end-radius-before + m = new CondLengthProperty.Maker(PR_X_BORDER_END_RADIUS_START); + m.useGeneric(genericCondCornerRadius); + m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard"); + addPropertyMaker("fox:border-end-radius-before", m); + + // border-end-radius-after + m = new CondLengthProperty.Maker(PR_X_BORDER_END_RADIUS_END); + m.useGeneric(genericCondCornerRadius); + 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); @@ -2696,6 +2760,13 @@ 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); + // 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 a12bed0fa..1ff44700d 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -49,6 +49,17 @@ public class ExtensionElementMapping extends ElementMapping { PROPERTY_ATTRIBUTES.add("disable-column-balancing"); //These are FOP's extension properties for accessibility PROPERTY_ATTRIBUTES.add("alt-text"); + + //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"); } /** 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..d3e37a7c9 --- /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} + * int, Property, PropertyMaker, PropertyList) + */ + 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 d39dc24f0..3b4b818b5 100644 --- a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java +++ b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java @@ -96,6 +96,8 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl /** the "end" edge */ public static final int END = 3; + + /** * Utility class to express border info. */ @@ -108,32 +110,40 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl 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; @@ -168,6 +178,20 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl } } + /** + * @return the border-*-start-radius + */ + public CondLengthProperty getRadiusStart() { + return this.radiusStart; + } + + /** + * @return the border-*-end-radius + */ + public CondLengthProperty getRadiusEnd() { + return this.radiusEnd; + } + /** {@inheritDoc} */ public String toString() { StringBuffer sb = new StringBuffer("BorderInfo"); @@ -177,6 +201,10 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl sb.append(mColor); sb.append(", "); sb.append(mWidth); + sb.append(", "); + sb.append(radiusStart); + sb.append(", "); + sb.append(radiusEnd); sb.append("}"); return sb.toString(); } @@ -190,8 +218,10 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl if (obj instanceof BorderInfo) { BorderInfo bi = (BorderInfo)obj; return (this.mColor == bi.mColor - && this.mStyle == bi.mStyle - && this.mWidth == bi.mWidth); + && this.mStyle == bi.mStyle + && this.mWidth == bi.mWidth + && this.radiusStart == bi.radiusStart + && this.radiusEnd == bi.radiusEnd); } return false; @@ -204,18 +234,27 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl 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); + = BorderInfo.getInstance(Constants.EN_NONE, new ConditionalNullLength(), null, + new ConditionalNullLength(), new ConditionalNullLength()); + + + /** * A conditional length of value 0. Returned by the @@ -292,8 +331,11 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl 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 +361,30 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl 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_START, + Constants.PR_X_BORDER_START_RADIUS_END); 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_START, + Constants.PR_X_BORDER_END_RADIUS_END); } @@ -347,10 +397,10 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl * @throws PropertyException in case of an error */ public static CommonBorderPaddingBackground getInstance(PropertyList pList) - throws PropertyException { + throws PropertyException { CommonBorderPaddingBackground newInstance - = new CommonBorderPaddingBackground(pList); + = new CommonBorderPaddingBackground(pList); CommonBorderPaddingBackground cachedInstance = null; /* if padding-* and background-position-* resolve to absolute lengths * the whole instance can be cached */ @@ -402,21 +452,26 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl } 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) { + // if (style != Constants.EN_NONE) { FOUserAgent ua = pList.getFObj().getUserAgent(); setBorderInfo(BorderInfo.getInstance(style, - pList.get(widthProp).getCondLength(), - pList.get(colorProp).getColor(ua)), side); - } + pList.get(widthProp).getCondLength(), + pList.get(colorProp).getColor(ua), + pList.get(radiusStartProp).getCondLength(), + pList.get(radiusEndProp).getCondLength()), side); + // } } + + /** * Sets a border. * @param info the border information @@ -539,6 +594,40 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl } } + /** + * 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 * @@ -603,9 +692,9 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl */ 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); } /** @@ -617,18 +706,18 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl */ public int getBPPaddingAndBorder(boolean discard, PercentBaseContext context) { return getPaddingBefore(discard, context) + getPaddingAfter(discard, context) - + getBorderBeforeWidth(discard) + getBorderAfterWidth(discard); + + getBorderBeforeWidth(discard) + getBorderAfterWidth(discard); } /** {@inheritDoc} */ 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"; } /** @@ -739,19 +828,20 @@ public class CommonBorderPaddingBackground { // CSOK: FinalCl 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 - && this.borderInfo[BEFORE] == cbpb.borderInfo[BEFORE] - && this.borderInfo[AFTER] == cbpb.borderInfo[AFTER] - && this.borderInfo[START] == cbpb.borderInfo[START] - && this.borderInfo[END] == cbpb.borderInfo[END] - && this.padding[BEFORE] == cbpb.padding[BEFORE] - && this.padding[AFTER] == cbpb.padding[AFTER] - && this.padding[START] == cbpb.padding[START] - && this.padding[END] == cbpb.padding[END]); + && this.backgroundColor == cbpb.backgroundColor + && this.backgroundImage.equals(cbpb.backgroundImage) + && this.backgroundPositionHorizontal == cbpb.backgroundPositionHorizontal + && this.backgroundPositionVertical == cbpb.backgroundPositionVertical + && this.backgroundRepeat == cbpb.backgroundRepeat + && this.borderInfo[BEFORE] == cbpb.borderInfo[BEFORE] + && this.borderInfo[AFTER] == cbpb.borderInfo[AFTER] + && this.borderInfo[START] == cbpb.borderInfo[START] + && this.borderInfo[END] == cbpb.borderInfo[END] + && this.padding[BEFORE] == cbpb.padding[BEFORE] + && this.padding[AFTER] == cbpb.padding[AFTER] + && this.padding[START] == cbpb.padding[START] + && this.padding[END] == cbpb.padding[END] + ); } return false; diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java index 6039ad941..a3d44f0a5 100644 --- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java +++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java @@ -79,19 +79,19 @@ public final class TraitSetter { addBorderTrait(area, bpProps, bNotFirst, CommonBorderPaddingBackground.START, - BorderProps.SEPARATE, Trait.BORDER_START); + BorderProps.SEPARATE, Trait.BORDER_START, context); addBorderTrait(area, bpProps, bNotLast, CommonBorderPaddingBackground.END, - BorderProps.SEPARATE, Trait.BORDER_END); + BorderProps.SEPARATE, Trait.BORDER_END, context); addBorderTrait(area, bpProps, false, CommonBorderPaddingBackground.BEFORE, - BorderProps.SEPARATE, Trait.BORDER_BEFORE); + BorderProps.SEPARATE, Trait.BORDER_BEFORE, context); addBorderTrait(area, bpProps, false, CommonBorderPaddingBackground.AFTER, - BorderProps.SEPARATE, Trait.BORDER_AFTER); + BorderProps.SEPARATE, Trait.BORDER_AFTER, context); } /** @@ -104,13 +104,17 @@ public final class TraitSetter { private static void addBorderTrait(Area area, CommonBorderPaddingBackground bpProps, boolean bDiscard, int iSide, int mode, - Object oTrait) { + Object oTrait, PercentBaseContext context) { int iBP = bpProps.getBorderWidth(iSide, bDiscard); - if (iBP > 0) { - area.addTrait(oTrait, - new BorderProps(bpProps.getBorderStyle(iSide), - iBP, bpProps.getBorderColor(iSide), - mode)); + int radiusStart = bpProps.getBorderRadiusStart(iSide, bDiscard, context); + int radiusEnd = bpProps.getBorderRadiusEnd(iSide, bDiscard, context); + if (iBP > 0 || radiusStart > 0 || radiusEnd > 0) { + BorderProps bps = new BorderProps(bpProps.getBorderStyle(iSide), + iBP, bpProps.getBorderColor(iSide), + mode); + bps.setRadiusStart(radiusStart); + bps.setRadiusEnd(radiusEnd); + area.addTrait(oTrait, bps); } } @@ -125,19 +129,19 @@ public final class TraitSetter { */ public static void addBorders(Area area, CommonBorderPaddingBackground bordProps, PercentBaseContext context) { - BorderProps bps = getBorderProps(bordProps, CommonBorderPaddingBackground.BEFORE); + BorderProps bps = getBorderProps(bordProps, CommonBorderPaddingBackground.BEFORE, context); if (bps != null) { area.addTrait(Trait.BORDER_BEFORE, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.AFTER); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.AFTER, context); if (bps != null) { area.addTrait(Trait.BORDER_AFTER, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.START); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.START, context); if (bps != null) { area.addTrait(Trait.BORDER_START, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.END); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.END, context); if (bps != null) { area.addTrait(Trait.BORDER_END, bps); } @@ -161,22 +165,23 @@ public final class TraitSetter { boolean discardBefore, boolean discardAfter, boolean discardStart, boolean discardEnd, PercentBaseContext context) { - BorderProps bps = getBorderProps(bordProps, CommonBorderPaddingBackground.BEFORE); + BorderProps bps = getBorderProps(bordProps, CommonBorderPaddingBackground.BEFORE, context); if (bps != null && !discardBefore) { area.addTrait(Trait.BORDER_BEFORE, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.AFTER); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.AFTER, context); if (bps != null && !discardAfter) { area.addTrait(Trait.BORDER_AFTER, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.START); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.START, context); if (bps != null && !discardStart) { area.addTrait(Trait.BORDER_START, bps); } - bps = getBorderProps(bordProps, CommonBorderPaddingBackground.END); + bps = getBorderProps(bordProps, CommonBorderPaddingBackground.END, context); if (bps != null && !discardEnd) { area.addTrait(Trait.BORDER_END, bps); } + } /** @@ -260,14 +265,19 @@ 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) { + int radiusStart = bordProps.getBorderRadiusStart(side, false, context); + int radiusEnd = bordProps.getBorderRadiusEnd(side, false, context); + if (width != 0 || radiusStart != 0 || radiusEnd != 0) { BorderProps bps; bps = new BorderProps(bordProps.getBorderStyle(side), width, bordProps.getBorderColor(side), BorderProps.SEPARATE); + bps.setRadiusStart(radiusStart); + bps.setRadiusEnd(radiusEnd); return bps; } else { return null; @@ -280,12 +290,19 @@ public final class TraitSetter { if (width != 0) { BorderProps bps = new BorderProps(borderInfo.getStyle(), width, borderInfo.getColor(), (outer ? BorderProps.COLLAPSE_OUTER : BorderProps.COLLAPSE_INNER)); + + //TODO Enabling this causes graphic problems. Revisit! + /* + bps.setRadiusStart(borderInfo.getRadiusStart().getLengthValue()); + bps.setRadiusEnd(borderInfo.getRadiusEnd().getLengthValue()); + */ return bps; } 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/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index cfce3a875..ea78b958c 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -48,6 +48,7 @@ import org.apache.fop.area.inline.Viewport; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.render.intermediate.BorderPainter; import org.apache.fop.traits.BorderProps; /** @@ -151,11 +152,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); + + Color bg = null; + if (backgroundTrait != null) { + bg = backgroundTrait.getColor(); + } + drawBorders(startx, starty, width, height, - bpsBefore, bpsAfter, bpsStart, bpsEnd); + bpsBefore, bpsAfter, bpsStart, bpsEnd, bg); } /** @@ -199,14 +209,23 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { paddRectHeight -= bpsAfter.width / 1000f; } + saveGraphicsState(); + + //TODO remove this choice + if (BorderPainter.isRoundedCornersSupported()) { + clipBackground(sx, sy, paddRectWidth, paddRectHeight, + bpsBefore, bpsAfter, bpsStart, bpsEnd); + } else { + clipRect(sx, sy, paddRectWidth, paddRectHeight); + } + 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 @@ -242,12 +261,33 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { drawImage(back.getURL(), pos); } } - - restoreGraphicsState(); } + restoreGraphicsState(); } } + /** + * 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 + */ + 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. @@ -264,9 +304,9 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { protected void drawBorders( // CSOK: ParameterNumber float startx, float starty, float width, float height, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsStart, BorderProps bpsEnd, Color innerBackgroundColor) { Rectangle2D.Float borderRect = new Rectangle2D.Float(startx, starty, width, height); - drawBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + drawBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, innerBackgroundColor); } private static final int BEFORE = 0; @@ -284,7 +324,8 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { */ protected void drawBorders( // CSOK: MethodLength Rectangle2D.Float borderRect, - BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd, + Color innerBackgroundColor) { //TODO generalize each of the four conditions into using a parameterized drawBorder() boolean[] border = new boolean[] { (bpsBefore != null), (bpsEnd != null), diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java index 21d4faf56..e90f0b29a 100644 --- a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import org.apache.fop.afp.AFPDitheredRectanglePainter; @@ -79,6 +80,13 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler private Map/**/pageSegmentMap = new java.util.HashMap/**/(); + + // Rounded corners are cached at the document level + private Map/**/ roundedCornerNameCache + = new HashMap()/**/; + + private int roundedCornerCount = 0; + /** Medium Map referenced on previous page **/ private String lastMediumMap; @@ -339,6 +347,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(); + 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); + } + + // ---=== AFPCustomizable ===--- /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandler.java b/src/java/org/apache/fop/render/afp/AFPImageHandler.java index 244263213..0e4e7a63a 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 07b712ceb..5617c4668 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java @@ -26,8 +26,6 @@ import java.io.IOException; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; -import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; -import org.apache.xmlgraphics.util.MimeConstants; import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPGraphics2D; @@ -86,9 +84,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/AFPImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java index ca0544542..48eeb79bf 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java @@ -317,9 +317,9 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)createDataObjectInfo(); // 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/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 0bbfef3e8..895316e6b 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -21,20 +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.security.MessageDigest; import java.util.Map; import org.w3c.dom.Document; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.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; @@ -51,6 +68,7 @@ import org.apache.fop.afp.ptoca.PtocaProducer; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; +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; @@ -66,8 +84,11 @@ import org.apache.fop.util.CharUtilities; */ public class AFPPainter extends AbstractIFPainter { + + + //** logging instance */ - //private static Log log = LogFactory.getLog(AFPPainter.class); + private static Log log = LogFactory.getLog(AFPPainter.class); private static final int X = 0; private static final int Y = 1; @@ -90,8 +111,9 @@ public class AFPPainter extends AbstractIFPainter { super(); this.documentHandler = documentHandler; this.state = IFState.create(); + this.borderPainter = new AFPBorderPainterAdapter( - new AFPBorderPainter(getPaintingState(), getDataStream())); + new AFPBorderPainter(getPaintingState(), getDataStream()), this, documentHandler); this.rectanglePainter = documentHandler.createRectanglePainter(); this.unitConv = getPaintingState().getUnitConverter(); } @@ -115,7 +137,7 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) - throws IFException { + throws IFException { //AFP doesn't support clipping, so we treat viewport like a group //this is the same code as for startGroup() try { @@ -172,13 +194,13 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ protected RenderingContext createRenderingContext() { - AFPRenderingContext psContext = new AFPRenderingContext( + AFPRenderingContext renderingContext = new AFPRenderingContext( getUserAgent(), documentHandler.getResourceManager(), getPaintingState(), getFontInfo(), getContext().getForeignAttributes()); - return psContext; + return renderingContext; } /** {@inheritDoc} */ @@ -196,6 +218,32 @@ public class AFPPainter extends AbstractIFPainter { } } + /** {@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); @@ -233,24 +281,769 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { + BorderProps start, BorderProps end, Color innerBackgroundColor) throws IFException { if (before != null || after != null || start != null || end != null) { try { - this.borderPainter.drawBorders(rect, before, after, start, end); - } catch (IOException ife) { - throw new IFException("IO error while painting borders", ife); + this.borderPainter.drawBorders(rect, before, after, start, end, + innerBackgroundColor); + } catch (IFException ife) { + throw new IFException("Error while painting borders", ife); } } } + //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 class BorderImagePainter implements Graphics2DImagePainter { + private final double esf; + 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; + + private BorderImagePainter(double esf, Rectangle borderRect, + BorderProps bpsStart, BorderProps bpsEnd, + BorderProps bpsBefore, BorderProps bpsAfter, + boolean[] roundCorner, Color innerBackgroundColor) { + this.esf = esf; + 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[BEFORE_START]) { + + AffineTransform transform = new AffineTransform(); + int beforeRadius = (int)(esf * bpsBefore.getRadiusStart()); + int startRadius = (int)(esf * bpsStart.getRadiusStart()); + + int beforeWidth = bpsBefore.width; + int startWidth = bpsStart.width; + int corner = BEFORE_START; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[BEFORE].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + + cornerBorder[START].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[BEFORE_END]) { + AffineTransform transform + = new AffineTransform(-1, 0, 0, 1, borderRect.width, 0); + + int beforeRadius = (int)(esf * bpsBefore.getRadiusEnd()); + int startRadius = (int)(esf * bpsEnd.getRadiusStart()); + + int beforeWidth = bpsBefore.width; + int startWidth = bpsEnd.width; + int corner = BEFORE_END; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[BEFORE].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + + cornerBorder[END].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[AFTER_END]) { + AffineTransform transform = new AffineTransform(-1, 0, 0, -1, + borderRect.width, borderRect.height); + + int beforeRadius = (int)(esf * bpsAfter.getRadiusEnd()); + int startRadius = (int)(esf * bpsEnd.getRadiusEnd()); + + int beforeWidth = bpsAfter.width; + int startWidth = bpsEnd.width; + int corner = AFTER_END; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[AFTER].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + cornerBorder[END].add(makeCornerBorderIPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + } + + if (roundCorner[AFTER_START]) { + AffineTransform transform + = new AffineTransform(1, 0, 0, -1, 0, borderRect.height); + + int beforeRadius = (int)(esf * bpsAfter.getRadiusStart()); + int startRadius = (int)(esf * bpsStart.getRadiusEnd()); + + int beforeWidth = bpsAfter.width; + int startWidth = bpsStart.width; + int corner = AFTER_START; + + background.subtract(makeCornerClip(beforeRadius, startRadius, + transform)); + + Area clip = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); + clip.transform(transform); + cornerRegion.add(clip); + + cornerBorder[AFTER].add(makeCornerBorderBPD(beforeRadius, + startRadius, beforeWidth, startWidth, transform)); + cornerBorder[START].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[BEFORE]); + } + + 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[END]); + } + + 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[AFTER]); + } + + 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[START]); + } + } + + public Dimension getImageSize() { + return borderRect.getSize(); + } + } + + public static final String CORNER_MODE_PROPERTY = "fop.round-corners.afp"; + public static final String MODE_SEPERATE = "seperate"; + public static final String MODE_ALL_IN_ONE = "all-in-one"; + public static final String MODE_DEFAULT = "all-in-one"; + private AFPBorderPainter delegate; + private final AFPPainter painter; + private final AFPDocumentHandler documentHandler; - public AFPBorderPainterAdapter(AFPBorderPainter borderPainter) { + public AFPBorderPainterAdapter(AFPBorderPainter borderPainter, AFPPainter painter, + AFPDocumentHandler documentHandler) { this.delegate = borderPainter; + this.painter = painter; + this.documentHandler = documentHandler; + } + + public void drawBorders(final Rectangle borderRect, + final BorderProps bpsBefore, final BorderProps bpsAfter, + final BorderProps bpsStart, final BorderProps bpsEnd, Color innerBackgroundColor) + throws IFException { + + if (isRoundedCornersSupported()) { + if (MODE_SEPERATE.equals(System.getProperty(CORNER_MODE_PROPERTY))) { + try { + drawRectangularBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IOException ioe) { + throw new IFException("Error drawing border", ioe); + } + drawSeperateRoundedCorners(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, + innerBackgroundColor); + } else { + drawRoundedCorners(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, + innerBackgroundColor); + } + } else { + try { + drawRectangularBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + } catch (IOException ioe) { + throw new IFException("Error drawing border", ioe); + } + } + } + + private boolean isModeSeperate() { + return (MODE_SEPERATE.equals(System.getProperty(CORNER_MODE_PROPERTY, MODE_DEFAULT))); + } + + private boolean isModeAllInOne() { + return (MODE_ALL_IN_ONE.equals(System.getProperty(CORNER_MODE_PROPERTY, MODE_DEFAULT))); + } + + private boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + + boolean rtn = !(isRoundedCornersSupported() && isModeAllInOne() + && hasRoundedCorners(bpsBefore, bpsAfter, + bpsStart, bpsEnd)); + return rtn; + } + + 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)); + } + + private void drawSeperateRoundedCorners(final Rectangle borderRect, + final BorderProps bpsBefore, final BorderProps bpsAfter, + final BorderProps bpsStart, final BorderProps bpsEnd, + final Color innerBackgroundColor) + throws IFException { + + double esf = cornerScaleFactor(borderRect.width, borderRect.height, + bpsBefore, bpsAfter, + bpsStart, bpsEnd); + + if (bpsBefore != null && bpsStart != null) { + int beforeRadiusStart = (int)(esf * bpsBefore.getRadiusStart()); + int startRadiusStart = (int)(esf * bpsStart.getRadiusStart()); + + Rectangle area = new Rectangle(borderRect.x, borderRect.y, + startRadiusStart, beforeRadiusStart); + + drawCorner(BEFORE_START, area, bpsBefore, bpsStart, + beforeRadiusStart, startRadiusStart, innerBackgroundColor); + } + + if (bpsEnd != null && bpsBefore != null) { + int endRadiusStart = (int)(esf * bpsEnd.getRadiusStart()); + int beforeRadiusEnd = (int)(esf * bpsBefore.getRadiusEnd()); + Rectangle area = new Rectangle(borderRect.x + borderRect.width - endRadiusStart, + borderRect.y, endRadiusStart, beforeRadiusEnd); + + drawCorner(BEFORE_END, area, bpsBefore, bpsEnd, + beforeRadiusEnd, endRadiusStart, innerBackgroundColor); + } + + + if (bpsEnd != null && bpsAfter != null) { + int endRadiusEnd = (int)(esf * bpsEnd.getRadiusEnd()); + int afterRadiusEnd = (int)(esf * bpsAfter.getRadiusEnd()); + + Rectangle area = new Rectangle(borderRect.x + borderRect.width - endRadiusEnd, + borderRect.y + borderRect.height - afterRadiusEnd, + endRadiusEnd, afterRadiusEnd); + + drawCorner(AFTER_END, area, bpsAfter, bpsEnd, + afterRadiusEnd, endRadiusEnd, innerBackgroundColor); + + } + + if (bpsStart != null && bpsAfter != null) { + int startRadiusEnd = (int)(esf * bpsStart.getRadiusEnd()); + int afterRadiusStart = (int)(esf * bpsAfter.getRadiusStart()); + Rectangle area = new Rectangle(borderRect.x , + borderRect.y + borderRect.height - afterRadiusStart, + startRadiusEnd, afterRadiusStart); + + drawCorner(AFTER_START, area, bpsAfter, bpsStart, + afterRadiusStart, startRadiusEnd, innerBackgroundColor); + } + } + + 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 esf = cornerScaleFactor(borderRect.width, borderRect.height, + bpsBefore, bpsAfter, + bpsStart, bpsEnd); + + final boolean[] roundCorner = new boolean[]{ + bpsBefore != null && bpsStart != null + && bpsBefore.getRadiusStart() > 0 + && bpsStart.getRadiusStart() > 0 + && bpsBefore.mode != BorderProps.COLLAPSE_OUTER + && bpsStart.mode != BorderProps.COLLAPSE_OUTER, + bpsEnd != null && bpsBefore != null + && bpsEnd.getRadiusStart() > 0 + && bpsBefore.getRadiusEnd() > 0 + && bpsEnd.mode != BorderProps.COLLAPSE_OUTER + && bpsBefore.mode != BorderProps.COLLAPSE_OUTER, + bpsEnd != null && bpsAfter != null + && bpsEnd.getRadiusEnd() > 0 + && bpsAfter.getRadiusEnd() > 0 + && bpsEnd.mode != BorderProps.COLLAPSE_OUTER + && bpsAfter.mode != BorderProps.COLLAPSE_OUTER, + bpsStart != null && bpsAfter != null + && bpsStart.getRadiusEnd() > 0 + && bpsAfter.getRadiusStart() > 0 + && bpsStart.mode != BorderProps.COLLAPSE_OUTER + && bpsAfter.mode != BorderProps.COLLAPSE_OUTER + }; + + + if (!roundCorner[BEFORE_START] && !roundCorner[BEFORE_END] + && !roundCorner[AFTER_END] && !roundCorner[AFTER_START]) { + 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(esf, borderRect, + bpsStart, bpsEnd, bpsBefore, bpsAfter, + roundCorner, innerBackgroundColor); + } + paintCornersAsBitmap(painter, borderRect, name); + } + + + 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; + } + + + 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); + float borderWidthRatio = ((float)beforeWidth) / startWidth; + if (beforeWidth * startRadius > startWidth * beforeRadius) { + cut.lineTo(startRadius, borderWidthRatio * startRadius); + cut.lineTo(startRadius, 0); + + } else { + cut.lineTo(startRadius, borderWidthRatio * startRadius); + cut.lineTo(startRadius, 0); + } + + clip.intersect(new Area(cut)); + clip.transform(transform); + return clip; + } + + + 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); + float borderWidthRatio = ((float)beforeWidth) / startWidth; + if (beforeWidth * startRadius > startWidth * beforeRadius) { + cut.lineTo(startRadius, borderWidthRatio * startRadius); + cut.lineTo(startRadius, 0); + + } else { + cut.lineTo(startRadius, borderWidthRatio * startRadius); + cut.lineTo(startRadius, 0); + } + + clip.subtract(new Area(cut)); + clip.transform(transform); + return clip; + } + + + private void paintCorner(final Graphics2D g2d, final int beforeWidth, + final int startWidth, final int beforeRadius, + final int startRadius, final Color innerBackgroundColor, + final Color beforeColor, final Color startColor) { + + //Draw the before-srart corner + Ellipse2D.Double inner = new Ellipse2D.Double(); + inner.x = startWidth; + inner.y = beforeWidth; + inner.width = 2 * (startRadius - startWidth); + inner.height = 2 * (beforeRadius - beforeWidth); + + Ellipse2D.Double outer = new Ellipse2D.Double(); + outer.x = 0; + outer.y = 0; + outer.width = 2 * (startRadius); + outer.height = 2 * (beforeRadius); + + Area border = new Area(outer); + border.subtract(new Area(inner)); + + GeneralPath afterCut = new GeneralPath(); + GeneralPath beforeCut = new GeneralPath(); + afterCut.moveTo(0, 0); + beforeCut.moveTo(0, 0); + float borderWidthRatio = ((float)beforeWidth) / startWidth; + if (beforeWidth * startRadius > startWidth * beforeRadius) { + afterCut.lineTo(startRadius, borderWidthRatio * startRadius); + beforeCut.lineTo(1f / borderWidthRatio * beforeRadius, beforeRadius); + + afterCut.lineTo(startRadius, 0); + beforeCut.lineTo(0, beforeRadius); + } else { + afterCut.lineTo(startRadius, (float)(borderWidthRatio * startRadius)); + beforeCut.lineTo(1f / borderWidthRatio * beforeRadius, beforeRadius); + + afterCut.lineTo(startRadius, 0); + beforeCut.lineTo(0, beforeRadius); + + } + + //start + g2d.setColor(startColor); + g2d.fill(border); + + //before + border = new Area(outer); + border.subtract(new Area(inner)); + border.subtract(new Area(beforeCut)); + + //start + g2d.setColor(beforeColor); + g2d.fill(border); + + //paint background + if (innerBackgroundColor == null) { + g2d.setColor(Color.white); + // log.warn("No background color set"); + + } else { + g2d.setColor(innerBackgroundColor); + } + + g2d.fill(inner); + } + + + private void drawCorner(final int corner, final Rectangle area, + final BorderProps before, final BorderProps start, + final int beforeRadius, final int startRadius, final Color innerBackground) + throws IFException { + + if (beforeRadius > 0 && startRadius > 0) { + String cornerKey = makeCornerKey(corner, before, start, + beforeRadius, startRadius, innerBackground); + + Graphics2DImagePainter painter = null; + + String name = documentHandler.getCachedRoundedCorner(cornerKey); + + // If the corner is not in the cache we construct a Graphics2DImagePainter + // that paints the corner + if (name == null) { + //Cache the name + name = documentHandler.cacheRoundedCorner(cornerKey); + + // create the Graphics2DImagePainter + painter = new Graphics2DImagePainter() { + public void paint(Graphics2D g2d, Rectangle2D area) { + + int beforeWidth = before.width; + int startWidth = start.width; + + Color beforeColor = before.color; + Color startColor = start.color; + + //No transformation + AffineTransform t; + switch(corner) { + case BEFORE_START: + //No transform required + break; + case BEFORE_END: + t = new AffineTransform(-1, 0, 0, 1, startRadius, 0); + g2d.transform(t); + break; + case AFTER_END: + t = new AffineTransform(-1, 0, 0, -1, startRadius, beforeRadius); + g2d.transform(t); + break; + case AFTER_START: + t = new AffineTransform(1, 0, 0, -1, 0, beforeRadius); + g2d.transform(t); + break; + default: break; + } + + paintCorner(g2d, beforeWidth, startWidth, + beforeRadius, startRadius, innerBackground, + beforeColor, startColor); + } + + public Dimension getImageSize() { + return area.getSize(); + } + }; + } + paintCornersAsBitmap(painter, area, name); + } + } + + + private String makeCornerKey(int corner, BorderProps beforeProps, BorderProps startProps, + int beforeRadius, int startRadius, Color innerBackgroundColor) { + + return hash(new StringBuffer() + .append(corner) + .append(":") + .append(beforeRadius) + .append(":") + .append(startRadius) + .append(":") + .append(beforeProps.width) + .append(":") + .append(startProps.width) + .append(":") + .append(beforeProps.color) + .append(":") + .append(startProps.color) + .append(":") + .append(innerBackgroundColor) + .toString()); + } + + + 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); + } + + 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", + new Integer(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 clip() throws IOException { @@ -291,11 +1084,11 @@ public class AFPPainter extends AbstractIFPainter { } public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) - throws IOException { + throws IOException { if (start.y != end.y) { //TODO Support arbitrary lines if necessary throw new UnsupportedOperationException( - "Can only deal with horizontal lines right now"); + "Can only deal with horizontal lines right now"); } //Simply delegates to drawBorderLine() as AFP line painting is not very sophisticated. @@ -304,11 +1097,45 @@ public class AFPPainter extends AbstractIFPainter { true, true, style.getEnumValue(), color); } + + 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"); + + } + + + protected void changeCoords(double a, double b, double c, double d, double e, double f) { + throw new UnsupportedOperationException( + "Can only deal with horizontal lines right now"); + } + + protected void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) + throws IOException { + throw new UnsupportedOperationException( + "Cannot handle cubic Bezier"); + } + + protected void rotateCoordinates(double angle) throws IOException { + throw new UnsupportedOperationException( + "Cannot handle coordinate rotation"); + } + + protected void scaleCoordinates(float xScale, float yScale) throws IOException { + throw new UnsupportedOperationException( + "Cannot handle coordinate scaling"); + } + + protected void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + throw new UnsupportedOperationException( + "Cannot handle coordinate translation"); + } } /** {@inheritDoc} */ 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); } catch (IOException ioe) { @@ -499,4 +1326,19 @@ public class AFPPainter extends AbstractIFPainter { getPaintingState().restore(); } + + /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + + //not supported by AFP + } + + /** {@inheritDoc} */ + public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + return borderPainter.isBackgroundRequired( bpsBefore, bpsAfter, + bpsStart, bpsEnd); + } + } diff --git a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java index 5374c7051..9c4cb6241 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 1b870a7c5..ca907eb0d 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -315,7 +315,7 @@ public abstract class AbstractIFPainter implements IFPainter { /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { + BorderProps start, BorderProps end, Color innerBackgroundColor) throws IFException { if (before != null) { Rectangle b = new Rectangle( rect.x, rect.y, @@ -411,4 +411,10 @@ public abstract class AbstractIFPainter implements IFPainter { 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/BorderPainter.java b/src/java/org/apache/fop/render/intermediate/BorderPainter.java index 20402369a..31218af6b 100644 --- a/src/java/org/apache/fop/render/intermediate/BorderPainter.java +++ b/src/java/org/apache/fop/render/intermediate/BorderPainter.java @@ -32,6 +32,12 @@ import org.apache.fop.traits.RuleStyle; */ public abstract class BorderPainter { + public static final String ROUNDED_CORNERS = "fop.round-corners"; + + protected static final int BEFORE = 0, END = 1, AFTER = 2, START = 3; + protected static final int BEFORE_START = 0, BEFORE_END = 1, AFTER_END = 2, AFTER_START = 3; + + /** * Draws borders. * @param borderRect the border rectangle @@ -39,59 +45,94 @@ public abstract class BorderPainter { * @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 if an I/O error occurs while creating the borders + * @throws IFException if an error occurs while drawing the borders */ public void drawBorders(Rectangle borderRect, // CSOK: MethodLength + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd, Color innerBackgroundColor) + throws IFException { + + try { + if (isRoundedCornersSupported()) { + drawRoundedBorders(borderRect, bpsBefore, bpsAfter, + bpsStart, bpsEnd); + + } else { + drawRectangularBorders(borderRect, bpsBefore, bpsAfter, + bpsStart, bpsEnd); + } + + } 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; + } + + protected void drawRectangularBorders(Rectangle borderRect, BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IOException { + + bpsBefore = sanitizeBorderProps(bpsBefore); + bpsAfter = sanitizeBorderProps(bpsAfter); + bpsStart = sanitizeBorderProps(bpsStart); + bpsEnd = sanitizeBorderProps(bpsEnd); + int startx = borderRect.x; int starty = borderRect.y; int width = borderRect.width; int height = borderRect.height; boolean[] b = new boolean[] { - (bpsBefore != null), (bpsEnd != null), - (bpsAfter != null), (bpsStart != null)}; + (bpsBefore != null), (bpsEnd != null), + (bpsAfter != null), (bpsStart != null)}; if (!b[0] && !b[1] && !b[2] && !b[3]) { return; } int[] bw = new int[] { - (b[0] ? bpsBefore.width : 0), - (b[1] ? bpsEnd.width : 0), - (b[2] ? bpsAfter.width : 0), - (b[3] ? bpsStart.width : 0)}; + (b[BEFORE] ? bpsBefore.width : 0), + (b[END] ? bpsEnd.width : 0), + (b[AFTER] ? bpsAfter.width : 0), + (b[3] ? bpsStart.width : 0)}; int[] clipw = new int[] { - BorderProps.getClippedWidth(bpsBefore), - BorderProps.getClippedWidth(bpsEnd), - BorderProps.getClippedWidth(bpsAfter), - BorderProps.getClippedWidth(bpsStart)}; - starty += clipw[0]; - height -= clipw[0]; - height -= clipw[2]; - startx += clipw[3]; - width -= clipw[3]; - width -= clipw[1]; + BorderProps.getClippedWidth(bpsBefore), + BorderProps.getClippedWidth(bpsEnd), + BorderProps.getClippedWidth(bpsAfter), + BorderProps.getClippedWidth(bpsStart)}; + starty += clipw[BEFORE]; + height -= clipw[BEFORE]; + height -= clipw[AFTER]; + startx += clipw[START]; + width -= clipw[START]; + width -= clipw[END]; boolean[] slant = new boolean[] { - (b[3] && b[0]), (b[0] && b[1]), (b[1] && b[2]), (b[2] && b[3])}; + (b[START] && b[BEFORE]), + (b[BEFORE] && b[END]), + (b[END] && b[AFTER]), + (b[AFTER] && b[START])}; if (bpsBefore != null) { int sx1 = startx; - int sx2 = (slant[0] ? sx1 + bw[3] - clipw[3] : sx1); + int sx2 = (slant[BEFORE_START] ? sx1 + bw[START] - clipw[START] : 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[BEFORE_END] ? ex1 - bw[END] + clipw[END] : ex1); + int outery = starty - clipw[BEFORE]; + int clipy = outery + clipw[BEFORE]; + int innery = outery + bw[BEFORE]; saveGraphicsState(); moveTo(sx1, clipy); + + int sx1a = sx1; int ex1a = ex1; if (bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[3]; + sx1a -= clipw[START]; } if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[1]; + ex1a += clipw[END]; } lineTo(sx1a, outery); lineTo(ex1a, outery); @@ -107,12 +148,12 @@ public abstract class BorderPainter { } if (bpsEnd != null) { int sy1 = starty; - int sy2 = (slant[1] ? sy1 + bw[0] - clipw[0] : sy1); + int sy2 = (slant[BEFORE_END] ? sy1 + bw[BEFORE] - clipw[BEFORE] : 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[AFTER_END] ? ey1 - bw[AFTER] + clipw[AFTER] : ey1); + int outerx = startx + width + clipw[END]; + int clipx = outerx - clipw[END]; + int innerx = outerx - bw[END]; saveGraphicsState(); moveTo(clipx, sy1); @@ -120,10 +161,10 @@ public abstract class BorderPainter { int ey1a = ey1; if (bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[0]; + sy1a -= clipw[BEFORE]; } if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[2]; + ey1a += clipw[AFTER]; } lineTo(outerx, sy1a); lineTo(outerx, ey1a); @@ -138,12 +179,12 @@ public abstract class BorderPainter { } if (bpsAfter != null) { int sx1 = startx; - int sx2 = (slant[3] ? sx1 + bw[3] - clipw[3] : sx1); + int sx2 = (slant[AFTER_START] ? sx1 + bw[START] - clipw[START] : 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[AFTER_END] ? ex1 - bw[END] + clipw[END] : ex1); + int outery = starty + height + clipw[AFTER]; + int clipy = outery - clipw[AFTER]; + int innery = outery - bw[AFTER]; saveGraphicsState(); moveTo(ex1, clipy); @@ -151,10 +192,10 @@ public abstract class BorderPainter { int ex1a = ex1; if (bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[3]; + sx1a -= clipw[START]; } if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[1]; + ex1a += clipw[END]; } lineTo(ex1a, outery); lineTo(sx1a, outery); @@ -169,23 +210,25 @@ public abstract class BorderPainter { } if (bpsStart != null) { int sy1 = starty; - int sy2 = (slant[0] ? sy1 + bw[0] - clipw[0] : sy1); + int sy2 = (slant[BEFORE_START] ? sy1 + bw[BEFORE] - clipw[BEFORE] : 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[AFTER_START] ? ey1 - bw[AFTER] + clipw[AFTER] : ey1); + int outerx = startx - clipw[START]; + int clipx = outerx + clipw[START]; + int innerx = outerx + bw[START]; saveGraphicsState(); + moveTo(clipx, ey1); + int sy1a = sy1; int ey1a = ey1; if (bpsStart.mode == BorderProps.COLLAPSE_OUTER) { if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[0]; + sy1a -= clipw[BEFORE]; } if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[2]; + ey1a += clipw[AFTER]; } lineTo(outerx, ey1a); lineTo(outerx, sy1a); @@ -200,6 +243,459 @@ public abstract class BorderPainter { } } + protected void drawRoundedBorders(Rectangle borderRect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IOException { + + bpsBefore = sanitizeBorderProps(bpsBefore); + bpsAfter = sanitizeBorderProps(bpsAfter); + bpsStart = sanitizeBorderProps(bpsStart); + bpsEnd = sanitizeBorderProps(bpsEnd); + + boolean[] b = new boolean[] { + (bpsBefore != null), (bpsEnd != null), + (bpsAfter != null), (bpsStart != null)}; + if (!b[BEFORE] && !b[END] && !b[AFTER] && !b[START]) { + return; + } + int[] bw = new int[] { + (b[BEFORE] ? bpsBefore.width : 0), + (b[END] ? bpsEnd.width : 0), + (b[AFTER] ? bpsAfter.width : 0), + (b[START] ? bpsStart.width : 0)}; + + int[] clipw = new int[] { + BorderProps.getClippedWidth(bpsBefore), + BorderProps.getClippedWidth(bpsEnd), + BorderProps.getClippedWidth(bpsAfter), + BorderProps.getClippedWidth(bpsStart)}; + + final int startx = borderRect.x + clipw[START]; + final int starty = borderRect.y + clipw[BEFORE]; + final int width = borderRect.width - clipw[START] - clipw[END]; + final int height = borderRect.height - clipw[BEFORE] - clipw[AFTER]; + + boolean[] slant = new boolean[] { + (b[START] && b[BEFORE]), (b[BEFORE] && b[END]), + (b[END] && b[AFTER]), (b[START] && b[AFTER])}; + + //Determine scale factor if any adjacent elliptic corners overlap + double esf = cornerScaleFactor(width, height, bpsBefore, bpsAfter, bpsStart, bpsEnd); + + if (bpsBefore != null) { + //Let x increase in the START->END direction + final int sx2 = (slant[BEFORE_START] ? bw[START] - clipw[START] : 0); + final int ex1 = width; + final int ex2 = (slant[BEFORE_END] ? ex1 - bw[END] + clipw[END] : ex1); + final int outery = -clipw[BEFORE]; + final int innery = outery + bw[BEFORE]; + final int clipy = outery + clipw[BEFORE]; + final int ellipseSBW = bpsStart == null ? 0 : (int)(esf * bpsStart.getRadiusStart()); + final int ellipseSBH = (int)(esf * bpsBefore.getRadiusStart()); + final int ellipseSBX = ellipseSBW; + final int ellipseSBY = clipy + ellipseSBH; + final int ellipseBEW = bpsEnd == null ? 0 : (int)(esf * bpsEnd.getRadiusStart()); + final int ellipseBEH = (int)(esf * bpsBefore.getRadiusEnd()); + final int ellipseBEX = ex1 - ellipseBEW; + final int ellipseBEY = clipy + ellipseBEH; + + saveGraphicsState(); + translateCoordinates(startx, starty); + drawBorderSegment( sx2, ex1, ex2, outery, innery, + clipw[START], clipw[END], + ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH, + ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH, + bpsBefore, bpsStart, bpsEnd + ); + restoreGraphicsState(); + } + + if (bpsStart != null) { + //Let x increase in the AFTER->BEFORE direction + final int sx2 = (slant[AFTER_START] ? bw[AFTER] - clipw[AFTER] : 0); + final int ex1 = height; + final int ex2 = (slant[BEFORE_START] ? ex1 - bw[BEFORE] + clipw[BEFORE] : ex1); + final int outery = -clipw[START]; + final int innery = outery + bw[START]; + final int clipy = outery + clipw[START]; + final int ellipseSBW = bpsAfter == null ? 0 : (int)(esf * bpsAfter.getRadiusStart()); + final int ellipseSBH = (int)(esf * bpsStart.getRadiusEnd()); + final int ellipseSBX = ellipseSBW; + final int ellipseSBY = clipy + ellipseSBH; + final int ellipseBEW = bpsBefore == null ? 0 : (int)(esf * bpsBefore.getRadiusStart()); + final int ellipseBEH = (int)(esf * bpsStart.getRadiusStart()); + final int ellipseBEX = ex1 - ellipseBEW; + final int ellipseBEY = clipy + ellipseBEH; + + saveGraphicsState(); + translateCoordinates(startx, starty + height); + rotateCoordinates(Math.PI * 3d / 2d); + drawBorderSegment( sx2, ex1, ex2, outery, innery, + clipw[AFTER], clipw[BEFORE], + ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH, + ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH, + bpsStart, bpsAfter, bpsBefore + ); + restoreGraphicsState(); + + } + + + if (bpsAfter != null) { + //Let x increase in the START->END direction + final int sx2 = (slant[AFTER_START] ? bw[START] - clipw[START] : 0); + final int ex1 = width; + final int ex2 = (slant[AFTER_END] ? ex1 - bw[END] + clipw[END] : ex1); + final int outery = -clipw[AFTER]; + final int innery = outery + bw[AFTER]; + final int clipy = outery + clipw[AFTER]; + final int ellipseSBW = bpsStart == null ? 0 : (int)(esf * bpsStart.getRadiusEnd()); + final int ellipseSBH = (int)(esf * bpsAfter.getRadiusStart()); + final int ellipseSBX = ellipseSBW; + final int ellipseSBY = clipy + ellipseSBH; + final int ellipseBEW = bpsEnd == null ? 0 : (int)(esf * bpsEnd.getRadiusEnd()); + final int ellipseBEH = (int)(esf * bpsAfter.getRadiusEnd()); + final int ellipseBEX = ex1 - ellipseBEW; + final int ellipseBEY = clipy + ellipseBEH; + + saveGraphicsState(); + translateCoordinates(startx, starty + height); + scaleCoordinates(1, -1); + drawBorderSegment( sx2, ex1, ex2, outery, innery, + clipw[START], clipw[END], + ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH, + ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH, + bpsAfter, bpsStart, bpsEnd + ); + restoreGraphicsState(); + } + + if (bpsEnd != null) { + //Let x increase in the BEFORE-> AFTER direction + final int sx2 = (slant[BEFORE_END] ? bw[BEFORE] - clipw[BEFORE] : 0); + final int ex1 = height; + final int ex2 = (slant[AFTER_END] ? ex1 - bw[AFTER] + clipw[AFTER] : ex1); + final int outery = -clipw[END]; + final int innery = outery + bw[END]; + final int clipy = outery + clipw[END]; + final int ellipseSBW = bpsBefore == null ? 0 : (int)(esf * bpsBefore.getRadiusEnd()); + final int ellipseSBH = (int)(esf * bpsEnd.getRadiusStart()); + final int ellipseSBX = ellipseSBW; + final int ellipseSBY = clipy + ellipseSBH; + final int ellipseBEW = bpsAfter == null ? 0 : (int)(esf * bpsAfter.getRadiusEnd()); + final int ellipseBEH = (int)(esf * bpsEnd.getRadiusEnd()); + final int ellipseBEX = ex1 - ellipseBEW; + final int ellipseBEY = clipy + ellipseBEH; + + saveGraphicsState(); + translateCoordinates(startx + width, starty); + rotateCoordinates(Math.PI / 2d); + drawBorderSegment( sx2, ex1, ex2, outery, innery, + clipw[BEFORE], clipw[AFTER], + ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH, + ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH, + bpsEnd, bpsBefore, bpsAfter + ); + restoreGraphicsState(); + } + } + + private void drawBorderSegment(final int sx2, final int ex1, final int ex2, + final int outery, final int innery, + final int clipWidthStart, final int clipWidthEnd, + final int ellipseSBX, final int ellipseSBY, + final int ellipseSBRadiusX, final int ellipseSBRadiusY, + final int ellipseBEX, final int ellipseBEY, + final int ellipseBERadiusX, final int ellipseBERadiusY, + final BorderProps bpsThis, final BorderProps bpsStart, final BorderProps bpsEnd ) + throws IOException { + + int sx1a = 0; + int ex1a = ex1; + + + if (ellipseSBRadiusX != 0 && ellipseSBRadiusY != 0 ) { + + final double[] joinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX, + ellipseSBRadiusY, (double)innery / sx2); + + 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 (bpsThis.mode == BorderProps.COLLAPSE_OUTER) { + + if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { + sx1a -= clipWidthStart; + } + if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { + ex1a += clipWidthEnd; + } + + lineTo(sx1a, outery); + lineTo(ex1a, outery); + } + } + + if (ellipseBERadiusX != 0) { + + final double[] outerJoinMetrics = getCornerBorderJoinMetrics( + ellipseBERadiusX, ellipseBERadiusY, (double)innery / (ex1 - ex2)); + final double beJoinAngle = 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, + (double)innery / (ex1 - ex2)); + 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, (double)innery / sx2); + + 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, + bpsThis.style, bpsThis.color); + + } else { + int innerFillY = Math.max(Math.max(ellipseBEY, ellipseSBY), innery); + drawBorderLine(sx1a, outery, ex1a, innerFillY, true, true, + bpsThis.style, bpsThis.color); + } + } + + private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, + double borderWidthRatio) { + + //TODO decide on implementation + boolean invert = System.getProperty("fop.round-corners.border-invert") != null; + if (invert) { + borderWidthRatio = 1d / borderWidthRatio; + } + String cornerJoinStyle = System.getProperty("fop.round-corners.corner-join-style"); + if ("css".equals(cornerJoinStyle)) { + return getCSSCornerBorderJoinMetrics(ellipseCenterX, ellipseCenterY, borderWidthRatio); + } else { + if (invert) { throw new RuntimeException("non css AND bw inverted!"); } + return getDefaultCornerBorderJoinMetrics( + ellipseCenterX, ellipseCenterY, borderWidthRatio); + } + + } + + private double[] getCSSCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, + double borderWidthRatio) { + + double angle = Math.atan(borderWidthRatio); + double x = ellipseCenterX * Math.cos(Math.atan(ellipseCenterX + / ellipseCenterY * borderWidthRatio)); + double y = ellipseCenterY * Math.sqrt(1d - x * x / ellipseCenterX / ellipseCenterX); + + return new double[]{ellipseCenterX - x, ellipseCenterY - y, angle}; + } + private double[] getDefaultCornerBorderJoinMetrics(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))}; + } + + /** + * Clip the background to the inner border + * @param rect + * @param bpsBefore + * @param bpsAfter + * @param bpsStart + * @param bpsEnd + * @throws IOException if an I/O error occurs + */ + public void clipBackground(Rectangle rect, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IOException { + int startx = rect.x; + int starty = rect.y; + int width = rect.width; + int height = rect.height; + + + int fullWidth = width + ( bpsStart == null ? 0 : bpsStart.width ) + + (bpsStart == null ? 0 : bpsStart.width); + int fullHeight = height + ( bpsBefore == null ? 0 : bpsBefore.width ) + + (bpsAfter == null ? 0 : bpsAfter.width); + + double esf = cornerScaleFactor( fullWidth, fullHeight, bpsBefore, bpsAfter, + bpsStart, bpsEnd); + + int ellipseSS = 0; + int ellipseBS = 0; + int ellipseBE = 0; + int ellipseES = 0; + int ellipseEE = 0; + int ellipseAE = 0; + int ellipseAS = 0; + int ellipseSE = 0; + + if (bpsBefore != null && bpsBefore.getRadiusStart() > 0 + && bpsStart != null && bpsStart.getRadiusStart() > 0) { + ellipseSS = Math.max((int)(bpsStart.getRadiusStart() * esf) - bpsStart.width, 0); + ellipseBS = Math.max((int)(bpsBefore.getRadiusStart() * esf) - bpsBefore.width, 0); + } + + if (bpsBefore != null && bpsBefore.getRadiusEnd() > 0 + && bpsEnd != null && bpsEnd.getRadiusStart() > 0) { + ellipseBE = Math.max((int)(bpsBefore.getRadiusEnd() * esf) - bpsBefore.width, 0); + ellipseES = Math.max((int)(bpsEnd.getRadiusStart() * esf) - bpsEnd.width, 0); + } + + if (bpsEnd != null && bpsEnd.getRadiusEnd() > 0 + && bpsAfter != null && bpsAfter.getRadiusEnd() > 0) { + ellipseEE = Math.max((int)(bpsEnd.getRadiusEnd() * esf) - bpsEnd.width, 0); + ellipseAE = Math.max((int)(bpsAfter.getRadiusEnd() * esf) - bpsAfter.width, 0); + } + + if (bpsAfter != null && bpsAfter.getRadiusStart() > 0 + && bpsStart != null && bpsStart.getRadiusEnd() > 0) { + ellipseAS = Math.max((int)(bpsAfter.getRadiusStart() * esf) - bpsAfter.width, 0); + ellipseSE = Math.max((int)(bpsStart.getRadiusEnd() * esf) - bpsStart.width, 0); + } + + // Draw clipping region in the order: Before->End->After->Start + moveTo(startx + ellipseSS, starty); + + lineTo(startx + width - ellipseES, starty); + + if (ellipseBE > 0 && ellipseES > 0) { + arcTo(Math.PI * 3 / 2, Math.PI * 2, + startx + width - ellipseES, starty + ellipseBE, ellipseES, ellipseBE); + } + + lineTo(startx + width, starty + height - ellipseAE); + + if (ellipseEE > 0 && ellipseAE > 0) { + arcTo(0, Math.PI / 2, startx + width - ellipseEE, + starty + height - ellipseAE, ellipseEE, ellipseAE); + } + + lineTo(startx + ellipseSE, starty + height); + + if (ellipseSE > 0 && ellipseSE > 0) { + arcTo( Math.PI / 2, Math.PI, startx + ellipseSE, + starty + height - ellipseAS, ellipseSE, ellipseAS); + } + + lineTo( startx, starty + ellipseBS); + + if (ellipseSS > 0 && ellipseBS > 0) { + arcTo( Math.PI, Math.PI * 3 / 2, + startx + ellipseSS, starty + ellipseBS, ellipseSS, ellipseBS); + } + + clip(); + + } + + /* + * If the ellipse radii exceed the border edge length, the ellipses are rescaled. + */ + protected double cornerScaleFactor(int width, int height, + BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + // Ellipse scale factor + double esf = 1d; + + if (bpsBefore != null) { + if (bpsStart != null && bpsEnd != null + && bpsStart.getRadiusStart() + bpsEnd.getRadiusStart() > 0) { + + double f = (double)width / (bpsStart.getRadiusStart() + bpsEnd.getRadiusStart()); + if (f < esf) { + esf = f; + } + } + } + if (bpsStart != null) { + if (bpsAfter != null && bpsBefore != null + && bpsAfter.getRadiusStart() + bpsBefore.getRadiusStart() > 0) { + double f = (double)height / (bpsAfter.getRadiusStart() + + bpsBefore.getRadiusStart()); + if ( f < esf) { + esf = f; + } + } + } + if (bpsAfter != null) { + if (bpsStart != null && bpsEnd != null + && bpsStart.getRadiusEnd() + bpsEnd.getRadiusEnd() > 0) { + double f = (double)width / (bpsStart.getRadiusEnd() + bpsEnd.getRadiusEnd()); + if (f < esf) { + esf = f; + } + } + } + + if (bpsEnd != null) { + if (bpsAfter != null && bpsBefore != null + && bpsAfter.getRadiusEnd() + bpsBefore.getRadiusEnd() > 0) { + double f = (double)height / (bpsAfter.getRadiusEnd() + bpsBefore.getRadiusEnd()); + if (f < esf) { + esf = f; + } + } + } + + return esf; + } /** * Draws a border line. @@ -249,6 +745,118 @@ public abstract class BorderPainter { */ protected abstract void lineTo(int x, int y) throws IOException; + /** + * 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 + */ + protected abstract void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) + 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 + */ + protected 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 + 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; + } + } + } + + /** + * Rotate the coordinate frame + * @param angle angle in radians to rotate the coordinate frame + * @throws IOException if an I/O error occurs + */ + protected abstract 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 + */ + protected abstract 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 + */ + protected abstract void scaleCoordinates(float xScale, float yScale) throws IOException; + + /** * Closes the current path. * @throws IOException if an I/O error occurs @@ -273,4 +881,8 @@ public abstract class BorderPainter { */ protected abstract void restoreGraphicsState() throws IOException; + public static boolean isRoundedCornersSupported() { + return "true".equalsIgnoreCase(System.getProperty(ROUNDED_CORNERS, "true")); + } + } diff --git a/src/java/org/apache/fop/render/intermediate/IFException.java b/src/java/org/apache/fop/render/intermediate/IFException.java index 52c650765..7e12c301b 100644 --- a/src/java/org/apache/fop/render/intermediate/IFException.java +++ b/src/java/org/apache/fop/render/intermediate/IFException.java @@ -43,4 +43,16 @@ public class IFException extends Exception { super(message, cause); } + /** + * Constructs a new exception with the cause. + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + /*public IFException( Exception cause) { + super(cause); + }*/ + } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 00fd74209..b0ddf462d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -166,6 +166,32 @@ 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; + + + /** + * + * @param bpsBefore + * @param bpsAfter + * @param bpsStart + * @param bpsEnd + * @return true if the background needs to be painted + */ + boolean isBackgroundRequired( BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd); + /** * Fills a rectangular area. * @param rect the rectangle's coordinates and extent @@ -182,11 +208,12 @@ public interface IFPainter { * @param after the border segment on the after-side (bottom) * @param start the border segment on the start-side (left) * @param end the border segment on the end-side (right) + * @param innerBackgroundColor the color of the inner background * @throws IFException if an error occurs while handling this event */ void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException; + BorderProps start, BorderProps end, 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 da5d8a623..0abf3839a 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -642,9 +642,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 bdeb1e9a1..1d20da519 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -1231,21 +1231,49 @@ 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) { + BorderProps bpsStart, BorderProps bpsEnd, Color innerBackgroundColor) { + //TODO lose scale? Rectangle rect = toMillipointRectangle(startx, starty, width, height); try { - painter.drawBorderRect(rect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + painter.drawBorderRect(rect, bpsBefore, bpsAfter, bpsStart, bpsEnd, + 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 edeef9766..0b3b24110 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -33,6 +33,7 @@ import java.util.Map; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; + import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -470,6 +471,14 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } } + /** {@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) { @@ -490,7 +499,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { + BorderProps start, BorderProps end, Color innerBackgroundColor) throws IFException { if (before == null && after == null && start == null && end == null) { return; } @@ -512,6 +521,12 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (end != null) { addAttribute(atts, "end", end.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); @@ -775,4 +790,13 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } } + /** {@inheritDoc} */ + public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) { + return true; + } + + + + } diff --git a/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java b/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java index b2b29188b..b3ad5ff7a 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java @@ -26,6 +26,7 @@ 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; @@ -315,4 +316,42 @@ public class Java2DBorderPainter extends BorderPainter { this.currentPath = null; } + /** {@inheritDoc} */ + protected void arcTo(double startAngle, double endAngle, int cx, int cy, int width, int height) + throws IOException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + protected void changeCoords(double a, double b, double c, double d, double e, double f) { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + protected void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) + throws IOException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + protected void rotateCoordinates(double angle) throws IOException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + protected void scaleCoordinates(float xScale, float yScale) throws IOException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + protected void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + // TODO Auto-generated method stub + + } + } diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 396645768..461b6e5bf 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -172,6 +172,13 @@ public class Java2DPainter extends AbstractIFPainter { getState().updateClip(rect); } + /** {@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) { @@ -188,10 +195,10 @@ public class Java2DPainter extends AbstractIFPainter { BorderProps start, BorderProps end) throws IFException { if (before != null || after != null || start != null || end != null) { try { - this.borderPainter.drawBorders(rect, before, after, start, end); - } catch (IOException e) { + this.borderPainter.drawBorders(rect, before, after, start, end, null); + } catch (IFException e) { //Won't happen with Java2D - throw new IllegalStateException("Unexpected I/O error"); + throw new IllegalStateException("Unexpected IF error"); } } } @@ -261,4 +268,6 @@ public class Java2DPainter extends AbstractIFPainter { 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 afae8ac27..8adb0f12d 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -99,7 +99,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { /** @return the target resolution */ protected int getResolution() { - int resolution = (int)Math.round(getUserAgent().getTargetResolution()); + int resolution = Math.round(getUserAgent().getTargetResolution()); if (resolution <= 300) { return 300; } else { @@ -181,6 +181,14 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { //If you need clipping support, switch to RenderingMode.BITMAP. } + /** {@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) { @@ -209,7 +217,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { final BorderProps before, final BorderProps after, final BorderProps start, final BorderProps end) throws IFException { if (isSpeedOptimized()) { - super.drawBorderRect(rect, before, after, start, end); + super.drawBorderRect(rect, before, after, start, end, null); return; } if (before != null || after != null || start != null || end != null) { @@ -529,4 +537,6 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { 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 index f8090fdeb..4bfba9df3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java @@ -22,6 +22,7 @@ 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.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +54,7 @@ public class PDFBorderPainter extends BorderPainter { protected void drawBorderLine( // CSOK: ParameterNumber int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore, int style, Color col) { + //TODO lose scale? drawBorderLine(generator, x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f, horz, startOrBefore, style, col); } @@ -68,11 +70,12 @@ public class PDFBorderPainter extends BorderPainter { 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); @@ -291,6 +294,7 @@ public class PDFBorderPainter extends BorderPainter { } static final String format(int coordinate) { + //TODO lose scale? return format(coordinate / 1000f); } @@ -328,4 +332,38 @@ public class PDFBorderPainter extends BorderPainter { generator.add("Q\n"); } + /** {@inheritDoc} */ + protected void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) { + generator.add(format(p1x) + " " + format(p1y) + " " + format(p2x) + " " + format(p2y) + + " " + format(p3x) + " " + format(p3y) + " c "); + } + + + private void transformCoordinates(int a, int b, int c, int d, int e, int f) { + generator.add( "" + format(a) + " " + format(b) + " " + format(c) + " " + format(d) + + " " + format(e) + " " + format(f) + " cm "); + } + + private void transformCoordinates2(float a, float b, float c, float d, float e, float f) { + generator.add( "" + format(a) + " " + format(b) + " " + format(c) + " " + format(d) + + " " + format(e) + " " + format(f) + " cm "); + } + + /** {@inheritDoc} */ + protected void rotateCoordinates(double angle) throws IOException { + float s = (float)Math.sin(angle); + float c = (float)Math.cos(angle); + transformCoordinates2(c, s, -s, c, 0, 0); + } + + /** {@inheritDoc} */ + protected void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + transformCoordinates(1000, 0, 0, 1000, xTranslate, yTranslate); + } + + /** {@inheritDoc} */ + protected void scaleCoordinates(float xScale, float yScale) throws IOException { + transformCoordinates2(xScale, 0, 0, yScale, 0, 0); + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index 161b46617..273426c0b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -223,6 +223,20 @@ public class PDFPainter extends AbstractIFPainter { generator.clipRect(rect); } + /** {@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) { @@ -256,17 +270,21 @@ public class PDFPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { + BorderProps start, BorderProps end, Color innerBackgroundColor) throws IFException { if (before != null || after != null || start != null || end != null) { generator.endTextObject(); try { - this.borderPainter.drawBorders(rect, before, after, start, end); - } catch (IOException ioe) { - throw new IFException("I/O error while drawing borders", ioe); + this.borderPainter.drawBorders(rect, before, after, start, end, + innerBackgroundColor); + } catch (IFException ioe) { + throw new IFException("IF error while drawing borders", ioe); } } } + + + /** {@inheritDoc} */ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { diff --git a/src/java/org/apache/fop/render/ps/PSBorderPainter.java b/src/java/org/apache/fop/render/ps/PSBorderPainter.java index 1e2964eed..916289eda 100644 --- a/src/java/org/apache/fop/render/ps/PSBorderPainter.java +++ b/src/java/org/apache/fop/render/ps/PSBorderPainter.java @@ -327,4 +327,58 @@ public class PSBorderPainter extends BorderPainter { generator.restoreGraphicsState(); } + + + + /** {@inheritDoc} */ + protected void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) + throws IOException { + StringBuffer sb = new StringBuffer(); + sb.append(generator.formatDouble(toPoints(p1x))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(p1y))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(p2x))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(p2y))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(p3x))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(p3y))); + sb.append(" curveto "); + generator.writeln(sb.toString()); + + } + + /** {@inheritDoc} */ + protected void rotateCoordinates(double angle) throws IOException { + StringBuffer sb = new StringBuffer(); + sb.append(generator.formatDouble(angle * 180d / Math.PI)); + sb.append(" "); + sb.append(" rotate "); + generator.writeln(sb.toString()); + } + + /** {@inheritDoc} */ + protected void translateCoordinates(int xTranslate, int yTranslate) throws IOException { + StringBuffer sb = new StringBuffer(); + sb.append(generator.formatDouble(toPoints(xTranslate))); + sb.append(" "); + sb.append(generator.formatDouble(toPoints(yTranslate))); + sb.append(" "); + sb.append(" translate "); + generator.writeln(sb.toString()); + } + + /** {@inheritDoc} */ + protected void scaleCoordinates(float xScale, float yScale) throws IOException { + StringBuffer sb = new StringBuffer(); + sb.append(generator.formatDouble(xScale)); + sb.append(" "); + sb.append(generator.formatDouble(yScale)); + sb.append(" "); + sb.append(" scale "); + 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 b8a6d40f6..72501528e 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -208,6 +208,20 @@ public class PSPainter extends AbstractIFPainter { } } + /** {@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) { @@ -235,11 +249,12 @@ public class PSPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { + BorderProps start, BorderProps end, Color innerBackgroundColor) throws IFException { if (before != null || after != null || start != null || end != null) { try { endTextObject(); - this.borderPainter.drawBorders(rect, before, after, start, end); + this.borderPainter.drawBorders(rect, before, after, start, end, + innerBackgroundColor); } catch (IOException ioe) { throw new IFException("I/O error in drawBorderRect()", ioe); } diff --git a/src/java/org/apache/fop/traits/BorderProps.java b/src/java/org/apache/fop/traits/BorderProps.java index 25ac86ced..dc60bb0ad 100644 --- a/src/java/org/apache/fop/traits/BorderProps.java +++ b/src/java/org/apache/fop/traits/BorderProps.java @@ -47,6 +47,12 @@ public class BorderProps implements Serializable { public Color color; // CSOK: VisibilityModifier /** Border width */ public int width; // CSOK: VisibilityModifier + + private int radiusStart = 0; + private int radiusEnd = 0; + + + /** Border mode (one of SEPARATE, COLLAPSE_INNER and COLLAPSE_OUTER) */ public int mode; // CSOK: VisibilityModifier @@ -75,6 +81,37 @@ public class BorderProps implements Serializable { this(getConstantForStyle(style), width, color, mode); } + /** + * + * @return the radius of the corner adjacent to the before or start border + */ + public int getRadiusStart() { + return radiusStart; + } + + /** + * + * @param radiusStart the radius of the corner adjacent to the before or start border + */ + public void setRadiusStart(int radiusStart) { + this.radiusStart = radiusStart; + } + + /** + * @return the radius of the corner adjacent to the after or end border + */ + public int getRadiusEnd() { + return radiusEnd; + } + + /** + * + * @param radiusEnd the radius of the corner adjacent to the after or end border + */ + public void setRadiusEnd(int radiusEnd) { + this.radiusEnd = radiusEnd; + } + /** * @param bp the border properties or null * @return the effective width of the clipped part of the border @@ -112,7 +149,9 @@ public class BorderProps implements Serializable { return (style == other.style) && color.equals(other.color) && width == other.width - && mode == other.mode; + && mode == other.mode + && radiusStart == other.radiusStart + && radiusEnd == other.radiusEnd; } } return false; @@ -154,7 +193,18 @@ public class BorderProps implements Serializable { throw new IllegalArgumentException(e.getMessage()); } - return new BorderProps(style, width, c, mode); + BorderProps bp = new BorderProps(style, width, c, mode); + + found = m.find(); + if (found) { + int startRadius = Integer.parseInt(m.group()); + m.find(); + int endRadius = Integer.parseInt(m.group()); + bp.setRadiusStart(startRadius); + bp.setRadiusEnd(endRadius); + } + + return bp; } else { throw new IllegalArgumentException("BorderProps must be surrounded by parentheses"); } @@ -170,12 +220,24 @@ public class BorderProps implements Serializable { sbuf.append(','); sbuf.append(width); if (mode != SEPARATE) { - sbuf.append(','); if (mode == COLLAPSE_INNER) { - sbuf.append("collapse-inner"); + sbuf.append(",collapse-inner"); } else { - sbuf.append("collapse-outer"); + sbuf.append(",collapse-outer"); + } + } + + if (radiusStart != 0 || radiusEnd != 0) { + if (mode == SEPARATE) { + // Because of the corner radii properties the mode must be set + // so that the parameter index is consistent + sbuf.append(",separate"); } + sbuf.append(','); + sbuf.append(radiusStart); + + sbuf.append(','); + sbuf.append(radiusEnd); } sbuf.append(')'); return sbuf.toString(); diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java index bcc3a6913..43590da1f 100644 --- a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java +++ b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java @@ -30,7 +30,6 @@ import java.io.IOException; import java.util.Map; import org.w3c.dom.Document; - import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -267,6 +266,13 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants { //TODO Implement me!!! } + /** {@inheritDoc} */ + public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, + BorderProps bpsStart, BorderProps bpsEnd) throws IFException { + //TODO Implement me!!! + + } + /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { @@ -406,4 +412,6 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants { } } + + }