Initial code drop for support of rounded corners. Submitted by: Peter Hancock <peter.hancock.at.gmail.com> 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-ffa450edef68pull/29/head
@@ -99,6 +99,7 @@ | |||
<xs:attribute name="end" type="mf:borderDef"/> | |||
<xs:attribute name="before" type="mf:borderDef"/> | |||
<xs:attribute name="after" type="mf:borderDef"/> | |||
<xs:attribute name="inner-background-color" type="mf:colorType"/> | |||
</xs:complexType> | |||
</xs:element> | |||
<xs:element name="image"> |
@@ -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/*<AFPResourceInfo,String>*/ includeNameMap | |||
= new java.util.HashMap()/*<AFPResourceInfo,String>*/; | |||
private Map pageSegmentMap = new java.util.HashMap(); | |||
/** Mapping of resourceInfo to AbstractCachedObject */ | |||
private final Map/*<AFPResourceInfo, AbstractCachedObject>*/ includeObjectCache | |||
= new java.util.HashMap()/*<AFPResourceInfo,String>*/; | |||
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 | |||
} |
@@ -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(); |
@@ -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]; | |||
@@ -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 | |||
@@ -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); |
@@ -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"); | |||
} | |||
/** |
@@ -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; | |||
} | |||
} |
@@ -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; |
@@ -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 |
@@ -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), |
@@ -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/*<String,String>*/pageSegmentMap | |||
= new java.util.HashMap/*<String,String>*/(); | |||
// Rounded corners are cached at the document level | |||
private Map/*<String, String>*/ roundedCornerNameCache | |||
= new HashMap()/*<String, String>*/; | |||
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} */ |
@@ -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; | |||
} | |||
/** |
@@ -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( |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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)); |
@@ -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; | |||
} | |||
} |
@@ -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")); | |||
} | |||
} |
@@ -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 <code>null</code> value is | |||
* permitted, and indicates that the cause is nonexistent or | |||
* unknown.) | |||
*/ | |||
/*public IFException( Exception cause) { | |||
super(cause); | |||
}*/ | |||
} |
@@ -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! |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 { |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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(); |
@@ -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 { | |||
} | |||
} | |||
} |