Added ability to embed an external AFP page segment resource file (AFP output only). Submitted by: Peter Hancock <peter.hancock.at.gmail.com> Changes to patch: - Replaced AFP parsing code with a more universal MO:DCA parser (as discussed). - AFPDocumentHandler: addressed a TODO (clearing of the page segment map was not necessary for each page) - AFPDocumentHandler: merged pageSegmentMap and pageSegmentUriMap by creating a combined object: PageSegmentDescriptor git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1005350 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_1rc1old
@@ -885,6 +885,13 @@ Note that the value of the encoding attribute in the example is the double-byte | |||
segment in the generated file. Please also note that page segments cannot be scaled. | |||
They are always rendered in their intrinsic size. | |||
</p> | |||
<p> | |||
The include-page-segment extension element has the optional attribute | |||
<i>resource-file</i>. The value of this is a URI to a resource containing a page | |||
segment with the declared name. In this case FOP embeds the page segment into the | |||
generated document so that the external resource does not have to be supplied in the | |||
print job. | |||
</p> | |||
</section> | |||
<section id="afp-tag-logical-element"> | |||
<title>Tag Logical Element (TLE) Extension</title> |
@@ -19,12 +19,15 @@ | |||
package org.apache.fop.afp; | |||
import java.io.BufferedInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.net.URI; | |||
import java.net.URISyntaxException; | |||
import java.util.Map; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
@@ -38,6 +41,7 @@ import org.apache.fop.afp.modca.PageSegment; | |||
import org.apache.fop.afp.modca.Registry; | |||
import org.apache.fop.afp.modca.ResourceGroup; | |||
import org.apache.fop.afp.modca.ResourceObject; | |||
import org.apache.fop.afp.util.AFPResourceUtil; | |||
import org.apache.fop.afp.util.ResourceAccessor; | |||
/** | |||
@@ -313,6 +317,57 @@ public class AFPResourceManager { | |||
} | |||
} | |||
/** | |||
* Creates an included resource extracting the named resource from an external source. | |||
* @param resourceName the name of the resource | |||
* @param uri the URI for the resource | |||
* @param accessor resource accessor to access the resource with | |||
* @throws IOException if an I/O error occurs while loading the resource | |||
*/ | |||
public void createIncludedResourceFromExternal(final String resourceName, | |||
final URI uri, final ResourceAccessor accessor) throws IOException { | |||
AFPResourceLevel resourceLevel = new AFPResourceLevel(AFPResourceLevel.PRINT_FILE); | |||
AFPResourceInfo resourceInfo = new AFPResourceInfo(); | |||
resourceInfo.setLevel(resourceLevel); | |||
resourceInfo.setName(resourceName); | |||
resourceInfo.setUri(uri.toASCIIString()); | |||
String resource = (String)includeNameMap.get(resourceInfo); | |||
if (resource == null) { | |||
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel); | |||
//resourceObject delegates write commands to copyNamedResource() | |||
//The included resource may already be wrapped in a resource object | |||
AbstractNamedAFPObject resourceObject = new AbstractNamedAFPObject(null) { | |||
protected void writeContent(OutputStream os) throws IOException { | |||
InputStream inputStream = null; | |||
try { | |||
inputStream = accessor.createInputStream(uri); | |||
BufferedInputStream bin = new BufferedInputStream(inputStream); | |||
AFPResourceUtil.copyNamedResource(resourceName, bin, os); | |||
} finally { | |||
IOUtils.closeQuietly(inputStream); | |||
} | |||
} | |||
//bypass super.writeStart | |||
protected void writeStart(OutputStream os) throws IOException { } | |||
//bypass super.writeEnd | |||
protected void writeEnd(OutputStream os) throws IOException { } | |||
}; | |||
resourceGroup.addObject(resourceObject); | |||
includeNameMap.put(resourceInfo, resourceName); | |||
} | |||
} | |||
/** | |||
* Sets resource level defaults. The existing defaults over merged with the ones passed in | |||
* as parameter. |
@@ -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(); |
@@ -26,6 +26,7 @@ import java.net.URI; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.fop.afp.util.AFPResourceUtil; | |||
import org.apache.fop.afp.util.ResourceAccessor; | |||
@@ -54,7 +55,7 @@ public class IncludedResourceObject extends AbstractNamedAFPObject { | |||
public void writeToStream(OutputStream os) throws IOException { | |||
InputStream in = resourceAccessor.createInputStream(this.uri); | |||
try { | |||
IOUtils.copy(in, os); | |||
AFPResourceUtil.copyResourceFile(in, os); | |||
} finally { | |||
IOUtils.closeQuietly(in); | |||
} |
@@ -29,6 +29,9 @@ import java.io.InputStream; | |||
*/ | |||
public class MODCAParser { | |||
/** The carriage control character (0x5A) used to indicate the start of a structured field. */ | |||
public static final byte CARRIAGE_CONTROL_CHAR = (byte)(0x5A & 0xFF); | |||
private DataInputStream din; | |||
/** | |||
@@ -61,10 +64,11 @@ public class MODCAParser { | |||
try { | |||
while (true) { | |||
byte b = din.readByte(); //Skip 0x5A character if necessary (ex. AFP) | |||
if (b == 0x0D || b == 0x0A) { //CR and LF may be used as field delimiters | |||
if (b == 0x0D || b == 0x0A) { | |||
//CR and LF may be used as field delimiters | |||
continue; | |||
} else if (b == 0x5A) { //Carriage Control Character | |||
break; | |||
} else if (b == CARRIAGE_CONTROL_CHAR) { | |||
break; //Signals the start of a new structured field | |||
} | |||
} | |||
} catch (EOFException eof) { |
@@ -0,0 +1,214 @@ | |||
/* | |||
* 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.afp.util; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.Collection; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.fop.afp.AFPConstants; | |||
import org.apache.fop.afp.modca.ResourceObject; | |||
import org.apache.fop.afp.modca.AbstractAFPObject.Category; | |||
import org.apache.fop.afp.parser.MODCAParser; | |||
import org.apache.fop.afp.parser.UnparsedStructuredField; | |||
/** | |||
* TODO better docs | |||
* Utility for AFP resource handling | |||
* | |||
* | |||
* A utility class to read structured fields from a MO:DCA document. Each | |||
* component of a mixed object document is explicitly defined and delimited | |||
* in the data. This is accomplished through the use of MO:DCA data structures, | |||
* called structured fields. Structured fields are used to envelop document | |||
* components and to provide commands and information to applications using | |||
* the data. Structured fields may contain one or more parameters. Each | |||
* parameter provides one value from a set of values defined by the architecture. | |||
* <p/> | |||
* MO:DCA structured fields consist of two parts: an introducer that identifies | |||
* the length and type of the structured field, and data that provides the | |||
* structured field's effect. The data is contained in a set of parameters, | |||
* which can consist of other data structures and data elements. The maximum | |||
* length of a structured field is 32767 bytes. | |||
* <p/> | |||
*/ | |||
public final class AFPResourceUtil { | |||
private static final byte TYPE_CODE_BEGIN = (byte)(0xA8 & 0xFF); | |||
private static final byte TYPE_CODE_END = (byte)(0xA9 & 0xFF); | |||
private static final Log LOG = LogFactory.getLog(AFPResourceUtil.class); | |||
private AFPResourceUtil() { | |||
//nop | |||
} | |||
/** | |||
* Get the next structured field as identified by the identifier | |||
* parameter (this must be a valid MO:DCA structured field). | |||
* @param identifier the three byte identifier | |||
* @param inputStream the inputStream | |||
* @throws IOException if an I/O exception occurred | |||
* @return the next structured field or null when there are no more | |||
*/ | |||
public static byte[] getNext(byte[] identifier, InputStream inputStream) throws IOException { | |||
MODCAParser parser = new MODCAParser(inputStream); | |||
while (true) { | |||
UnparsedStructuredField field = parser.readNextStructuredField(); | |||
if (field == null) { | |||
return null; | |||
} | |||
if (field.getSfClassCode() == identifier[0] | |||
&& field.getSfTypeCode() == identifier[1] | |||
&& field.getSfCategoryCode() == identifier[2]) { | |||
return field.getCompleteFieldAsBytes(); | |||
} | |||
} | |||
} | |||
private static String getResourceName(UnparsedStructuredField field) | |||
throws UnsupportedEncodingException { | |||
//The first 8 bytes of the field data represent the resource name | |||
byte[] nameBytes = new byte[8]; | |||
System.arraycopy(field.getData(), 0, nameBytes, 0, 8); | |||
String asciiName; | |||
asciiName = new String(nameBytes, AFPConstants.EBCIDIC_ENCODING); | |||
return asciiName; | |||
} | |||
/** | |||
* Copy a complete resource file to a given {@link OutputStream}. | |||
* @param in external resource input | |||
* @param out output destination | |||
* @throws IOException if an I/O error occurs | |||
*/ | |||
public static void copyResourceFile(final InputStream in, OutputStream out) | |||
throws IOException { | |||
MODCAParser parser = new MODCAParser(in); | |||
while (true) { | |||
UnparsedStructuredField field = parser.readNextStructuredField(); | |||
if (field == null) { | |||
break; | |||
} | |||
out.write(MODCAParser.CARRIAGE_CONTROL_CHAR); | |||
field.writeTo(out); | |||
} | |||
} | |||
/** | |||
* Copy a named resource to a given {@link OutputStream}. The MO:DCA fields read from the | |||
* {@link InputStream} are scanned for the resource with the given name. | |||
* @param name name of structured field | |||
* @param in external resource input | |||
* @param out output destination | |||
* @throws IOException if an I/O error occurs | |||
*/ | |||
public static void copyNamedResource(String name, | |||
final InputStream in, final OutputStream out) throws IOException { | |||
final MODCAParser parser = new MODCAParser(in); | |||
Collection resourceNames = new java.util.HashSet(); | |||
//Find matching "Begin" field | |||
final UnparsedStructuredField fieldBegin; | |||
while (true) { | |||
UnparsedStructuredField field = parser.readNextStructuredField(); | |||
if (field == null) { | |||
throw new IOException("Requested resource '" + name | |||
+ "' not found. Encountered resource names: " + resourceNames); | |||
} | |||
if (field.getSfTypeCode() != TYPE_CODE_BEGIN) { //0xA8=Begin | |||
continue; //Not a "Begin" field | |||
} | |||
String resourceName = getResourceName(field); | |||
resourceNames.add(resourceName); | |||
if (resourceName.equals(name)) { | |||
if (LOG.isDebugEnabled()) { | |||
LOG.debug("Start of requested structured field found:\n" | |||
+ field); | |||
} | |||
fieldBegin = field; | |||
break; //Name doesn't match | |||
} | |||
} | |||
//Decide whether the resource file has to be wrapped in a resource object | |||
boolean wrapInResource; | |||
if (fieldBegin.getSfCategoryCode() == Category.PAGE_SEGMENT) { | |||
//A naked page segment must be wrapped in a resource object | |||
wrapInResource = true; | |||
} else if (fieldBegin.getSfCategoryCode() == Category.NAME_RESOURCE) { | |||
//A resource object can be copied directly | |||
wrapInResource = false; | |||
} else { | |||
throw new IOException("Cannot handle resource: " + fieldBegin); | |||
} | |||
//Copy structured fields (wrapped or as is) | |||
if (wrapInResource) { | |||
ResourceObject resourceObject = new ResourceObject(name) { | |||
protected void writeContent(OutputStream os) throws IOException { | |||
copyStructuredFields(name, fieldBegin, parser, out); | |||
} | |||
}; | |||
resourceObject.setType(ResourceObject.TYPE_PAGE_SEGMENT); | |||
resourceObject.writeToStream(out); | |||
} else { | |||
copyStructuredFields(name, fieldBegin, parser, out); | |||
} | |||
} | |||
private static void copyStructuredFields(String name, UnparsedStructuredField fieldBegin, | |||
MODCAParser parser, OutputStream out) throws IOException { | |||
boolean inRequestedResource; | |||
//The "Begin" field first | |||
out.write(MODCAParser.CARRIAGE_CONTROL_CHAR); | |||
fieldBegin.writeTo(out); | |||
UnparsedStructuredField field; | |||
//Then the rest of the fields until the corresponding "End" field | |||
inRequestedResource = true; | |||
do { | |||
field = parser.readNextStructuredField(); | |||
if (field == null) { | |||
break; //Unexpected EOF | |||
} | |||
if (field.getSfTypeCode() == TYPE_CODE_END) { | |||
String resourceName = getResourceName(field); | |||
if (resourceName.equals(name)) { | |||
inRequestedResource = false; //Signal end of loop | |||
} | |||
} | |||
out.write(MODCAParser.CARRIAGE_CONTROL_CHAR); | |||
field.writeTo(out); | |||
} while (inRequestedResource); | |||
if (inRequestedResource) { | |||
throw new IOException("Ending structured field not found for resource " + name); | |||
} | |||
} | |||
} |
@@ -54,80 +54,27 @@ public class StructuredFieldReader { | |||
} | |||
/** | |||
* Get the next structured field as identified by the identifer | |||
* parameter (this must be a valid MO:DCA structured field. | |||
* Get the next structured field as identified by the identifier | |||
* parameter (this must be a valid MO:DCA structured field). | |||
* Note: The returned data does not include the field length and identifier! | |||
* @param identifier the three byte identifier | |||
* @throws IOException if an I/O exception occurred | |||
* @return the next structured field or null when there are no more | |||
*/ | |||
public byte[] getNext(byte[] identifier) throws IOException { | |||
int bufferPointer = 0; | |||
byte[] bufferData = new byte[identifier.length + 2]; | |||
for (int x = 0; x < identifier.length; x++) { | |||
bufferData[x] = 0x00; | |||
} | |||
int c; | |||
while ((c = inputStream.read()) > -1) { | |||
bufferData[bufferPointer] = (byte) c; | |||
// Check the last characters in the buffer | |||
int index = 0; | |||
boolean found = true; | |||
for (int i = identifier.length - 1; i > -1; i--) { | |||
int p = bufferPointer - index; | |||
if (p < 0) { | |||
p = bufferData.length + p; | |||
} | |||
index++; | |||
if (identifier[i] != bufferData[p]) { | |||
found = false; | |||
break; | |||
} | |||
} | |||
if (found) { | |||
byte[] length = new byte[2]; | |||
int a = bufferPointer - identifier.length; | |||
if (a < 0) { | |||
a = bufferData.length + a; | |||
} | |||
int b = bufferPointer - identifier.length - 1; | |||
if (b < 0) { | |||
b = bufferData.length + b; | |||
} | |||
length[0] = bufferData[b]; | |||
length[1] = bufferData[a]; | |||
int reclength = ((length[0] & 0xFF) << 8) | |||
+ (length[1] & 0xFF) - identifier.length - 2; | |||
byte[] retval = new byte[reclength]; | |||
inputStream.read(retval, 0, reclength); | |||
return retval; | |||
} | |||
bufferPointer++; | |||
if (bufferPointer >= bufferData.length) { | |||
bufferPointer = 0; | |||
} | |||
byte[] bytes = AFPResourceUtil.getNext(identifier, this.inputStream); | |||
if (bytes != null) { | |||
//Users of this class expect the field data without length and identifier | |||
int srcPos = 2 + identifier.length; | |||
byte[] tmp = new byte[bytes.length - srcPos]; | |||
System.arraycopy(bytes, srcPos, tmp, 0, tmp.length); | |||
bytes = tmp; | |||
} | |||
return null; | |||
return bytes; | |||
} | |||
} |
@@ -47,6 +47,7 @@ import org.apache.fop.render.afp.extensions.AFPElementMapping; | |||
import org.apache.fop.render.afp.extensions.AFPIncludeFormMap; | |||
import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap; | |||
import org.apache.fop.render.afp.extensions.AFPPageOverlay; | |||
import org.apache.fop.render.afp.extensions.AFPPageSegmentElement; | |||
import org.apache.fop.render.afp.extensions.AFPPageSetup; | |||
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; | |||
import org.apache.fop.render.intermediate.IFDocumentHandler; | |||
@@ -76,8 +77,8 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
private DataStream dataStream; | |||
/** the map of page segments */ | |||
private Map/*<String,String>*/pageSegmentMap | |||
= new java.util.HashMap/*<String,String>*/(); | |||
private Map/*<String,PageSegmentDescriptor>*/pageSegmentMap | |||
= new java.util.HashMap/*<String,PageSegmentDescriptor>*/(); | |||
/** Medium Map referenced on previous page **/ | |||
private String lastMediumMap; | |||
@@ -213,7 +214,6 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
throws IFException { | |||
this.location = LOC_ELSEWHERE; | |||
paintingState.clear(); | |||
pageSegmentMap.clear(); | |||
AffineTransform baseTransform = getBaseTransform(); | |||
paintingState.concatenate(baseTransform); | |||
@@ -288,9 +288,12 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
null); | |||
} | |||
if (AFPElementMapping.INCLUDE_PAGE_SEGMENT.equals(element)) { | |||
String name = aps.getName(); | |||
String source = aps.getValue(); | |||
pageSegmentMap.put(source, name); | |||
AFPPageSegmentElement.AFPPageSegmentSetup apse | |||
= (AFPPageSegmentElement.AFPPageSegmentSetup)aps; | |||
String name = apse.getName(); | |||
String source = apse.getValue(); | |||
String uri = apse.getResourceSrc(); | |||
pageSegmentMap.put(source, new PageSegmentDescriptor(name, uri)); | |||
} else if (AFPElementMapping.NO_OPERATION.equals(element)) { | |||
String content = aps.getContent(); | |||
if (content != null) { | |||
@@ -392,13 +395,13 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
} | |||
/** | |||
* Returns the page segment name for a given URI if it actually represents a page segment. | |||
* Returns the page segment descriptor for a given URI if it actually represents a page segment. | |||
* Otherwise, it just returns null. | |||
* @param uri the URI that identifies the page segment | |||
* @return the page segment name or null if there's no page segment for the given URI | |||
* @return the page segment descriptor or null if there's no page segment for the given URI | |||
*/ | |||
String getPageSegmentNameFor(String uri) { | |||
return (String)pageSegmentMap.get(uri); | |||
PageSegmentDescriptor getPageSegmentNameFor(String uri) { | |||
return (PageSegmentDescriptor)pageSegmentMap.get(uri); | |||
} | |||
} |
@@ -26,6 +26,8 @@ import java.awt.Point; | |||
import java.awt.Rectangle; | |||
import java.awt.geom.AffineTransform; | |||
import java.io.IOException; | |||
import java.net.URI; | |||
import java.net.URISyntaxException; | |||
import java.util.Map; | |||
import org.w3c.dom.Document; | |||
@@ -48,6 +50,8 @@ import org.apache.fop.afp.modca.AbstractPageObject; | |||
import org.apache.fop.afp.modca.PresentationTextObject; | |||
import org.apache.fop.afp.ptoca.PtocaBuilder; | |||
import org.apache.fop.afp.ptoca.PtocaProducer; | |||
import org.apache.fop.afp.util.DefaultFOPResourceAccessor; | |||
import org.apache.fop.afp.util.ResourceAccessor; | |||
import org.apache.fop.fonts.Font; | |||
import org.apache.fop.fonts.FontInfo; | |||
import org.apache.fop.fonts.FontTriplet; | |||
@@ -183,14 +187,34 @@ public class AFPPainter extends AbstractIFPainter { | |||
/** {@inheritDoc} */ | |||
public void drawImage(String uri, Rectangle rect) throws IFException { | |||
String name = documentHandler.getPageSegmentNameFor(uri); | |||
if (name != null) { | |||
PageSegmentDescriptor pageSegment = documentHandler.getPageSegmentNameFor(uri); | |||
if (pageSegment != null) { | |||
float[] srcPts = {rect.x, rect.y}; | |||
int[] coords = unitConv.mpts2units(srcPts); | |||
int width = Math.round(unitConv.mpt2units(rect.width)); | |||
int height = Math.round(unitConv.mpt2units(rect.height)); | |||
getDataStream().createIncludePageSegment(name, coords[X], coords[Y], width, height); | |||
getDataStream().createIncludePageSegment(pageSegment.getName(), | |||
coords[X], coords[Y], width, height); | |||
//Do we need to embed an external page segment? | |||
if (pageSegment.getURI() != null) { | |||
ResourceAccessor accessor = new DefaultFOPResourceAccessor ( | |||
documentHandler.getUserAgent(), null, null); | |||
try { | |||
URI resourceUri = new URI(pageSegment.getURI()); | |||
documentHandler.getResourceManager().createIncludedResourceFromExternal( | |||
pageSegment.getName(), resourceUri, accessor); | |||
} catch (URISyntaxException urie) { | |||
throw new IFException("Could not handle resource url" | |||
+ pageSegment.getURI(), urie); | |||
} catch (IOException ioe) { | |||
throw new IFException("Could not handle resource" + pageSegment.getURI(), ioe); | |||
} | |||
} | |||
} else { | |||
drawImageUsingURI(uri, rect); | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||
* contributor license agreements. See the NOTICE file distributed with | |||
* this work for additional information regarding copyright ownership. | |||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||
* (the "License"); you may not use this file except in compliance with | |||
* the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.render.afp; | |||
/** | |||
* Class holding information on a page segment. | |||
*/ | |||
class PageSegmentDescriptor { | |||
private String name; | |||
private String uri; | |||
/** | |||
* Creates a new page segment descriptor. | |||
* @param name the page segment name | |||
* @param uri the URI identifying the external resource file (may be null if the page segment | |||
* shall be referenced rather than embedded) | |||
*/ | |||
public PageSegmentDescriptor(String name, String uri) { | |||
this.name = name; | |||
this.uri = uri; | |||
} | |||
/** | |||
* Returns the name of the page segment (usually 8 upper case letters). | |||
* @return the name of the page segment | |||
*/ | |||
public String getName() { | |||
return this.name; | |||
} | |||
/** | |||
* Returns the URI of the external resource containing the page segment. | |||
* @return the URI of the external resource (or null if the resource is not to be embedded) | |||
*/ | |||
public String getURI() { | |||
return this.uri; | |||
} | |||
} |
@@ -103,7 +103,7 @@ public class AFPElementMapping extends ElementMapping { | |||
static class AFPIncludePageSegmentMaker extends ElementMapping.Maker { | |||
public FONode make(FONode parent) { | |||
return new AFPPageSetupElement(parent, INCLUDE_PAGE_SEGMENT); | |||
return new AFPPageSegmentElement(parent, INCLUDE_PAGE_SEGMENT); | |||
} | |||
} | |||
@@ -29,6 +29,7 @@ import org.xml.sax.helpers.DefaultHandler; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.fop.render.afp.extensions.AFPPageSegmentElement.AFPPageSegmentSetup; | |||
import org.apache.fop.util.ContentHandlerFactory; | |||
import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; | |||
@@ -96,6 +97,30 @@ public class AFPExtensionHandler extends DefaultHandler | |||
if (name != null) { | |||
returnedObject.setName(name); | |||
} | |||
} else if (AFPElementMapping.INCLUDE_PAGE_SEGMENT.equals(localName)) { | |||
AFPPageSegmentSetup pageSetupExtn = null; | |||
pageSetupExtn = new AFPPageSegmentSetup(localName); | |||
this.returnedObject = pageSetupExtn; | |||
String name = lastAttributes.getValue("name"); | |||
if (name != null) { | |||
returnedObject.setName(name); | |||
} | |||
String value = lastAttributes.getValue("value"); | |||
if (value != null && pageSetupExtn != null) { | |||
pageSetupExtn.setValue(value); | |||
} | |||
String resourceSrc = lastAttributes.getValue("resource-file"); | |||
if (resourceSrc != null && pageSetupExtn != null) { | |||
pageSetupExtn.setResourceSrc(resourceSrc); | |||
} | |||
if (content.length() > 0 && pageSetupExtn != null) { | |||
pageSetupExtn.setContent(content.toString()); | |||
content.setLength(0); //Reset text buffer (see characters()) | |||
} | |||
} else { | |||
AFPPageSetup pageSetupExtn = null; | |||
if (AFPElementMapping.INVOKE_MEDIUM_MAP.equals(localName)) { | |||
@@ -117,6 +142,7 @@ public class AFPExtensionHandler extends DefaultHandler | |||
content.setLength(0); //Reset text buffer (see characters()) | |||
} | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,149 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||
* contributor license agreements. See the NOTICE file distributed with | |||
* this work for additional information regarding copyright ownership. | |||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||
* (the "License"); you may not use this file except in compliance with | |||
* the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.render.afp.extensions; | |||
import org.xml.sax.Attributes; | |||
import org.xml.sax.ContentHandler; | |||
import org.xml.sax.Locator; | |||
import org.xml.sax.SAXException; | |||
import org.xml.sax.helpers.AttributesImpl; | |||
import org.apache.fop.apps.FOPException; | |||
import org.apache.fop.fo.FONode; | |||
import org.apache.fop.fo.PropertyList; | |||
import org.apache.fop.fo.extensions.ExtensionAttachment; | |||
/** | |||
* This class extends the org.apache.fop.extensions.ExtensionObj class. The | |||
* object faciliates extraction of elements from formatted objects based on | |||
* the static list as defined in the AFPElementMapping implementation. | |||
* <p/> | |||
*/ | |||
public class AFPPageSegmentElement extends AFPPageSetupElement { | |||
private static final String ATT_RESOURCE_SRC = "resource-file"; | |||
/** | |||
* Constructs an AFP object (called by Maker). | |||
* | |||
* @param parent the parent formatting object | |||
* @param name the name of the afp element | |||
*/ | |||
public AFPPageSegmentElement(FONode parent, String name) { | |||
super(parent, name); | |||
} | |||
private AFPPageSegmentSetup getPageSetupAttachment() { | |||
return (AFPPageSegmentSetup)getExtensionAttachment(); | |||
} | |||
/** {@inheritDoc} */ | |||
public void processNode(String elementName, Locator locator, | |||
Attributes attlist, PropertyList propertyList) | |||
throws FOPException { | |||
AFPPageSegmentSetup pageSetup = getPageSetupAttachment(); | |||
super.processNode(elementName, locator, attlist, propertyList); | |||
String attr = attlist.getValue(ATT_RESOURCE_SRC); | |||
if (attr != null && attr.length() > 0) { | |||
pageSetup.setResourceSrc(attr); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
protected ExtensionAttachment instantiateExtensionAttachment() { | |||
return new AFPPageSegmentSetup(getLocalName()); | |||
} | |||
/** | |||
* This is the pass-through value object for the AFP extension. | |||
*/ | |||
public static class AFPPageSegmentSetup extends AFPPageSetup { | |||
private static final long serialVersionUID = 1L; | |||
private String resourceSrc; | |||
/** | |||
* Default constructor. | |||
* | |||
* @param elementName the name of the setup code object, may be null | |||
*/ | |||
public AFPPageSegmentSetup(String elementName) { | |||
super(elementName); | |||
} | |||
/** | |||
* Returns the source URI for the page segment. | |||
* @return the source URI | |||
*/ | |||
public String getResourceSrc() { | |||
return resourceSrc; | |||
} | |||
/** | |||
* Sets the source URI for the page segment. | |||
* @param resourceSrc the source URI | |||
*/ | |||
public void setResourceSrc(String resourceSrc) { | |||
this.resourceSrc = resourceSrc.trim(); | |||
} | |||
/** {@inheritDoc} */ | |||
public void toSAX(ContentHandler handler) throws SAXException { | |||
AttributesImpl atts = new AttributesImpl(); | |||
if (name != null && name.length() > 0) { | |||
atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name); | |||
} | |||
if (value != null && value.length() > 0) { | |||
atts.addAttribute(null, ATT_VALUE, ATT_VALUE, "CDATA", value); | |||
} | |||
if (resourceSrc != null && resourceSrc.length() > 0) { | |||
atts.addAttribute(null, ATT_RESOURCE_SRC, ATT_RESOURCE_SRC, "CDATA", resourceSrc); | |||
} | |||
handler.startElement(CATEGORY, elementName, elementName, atts); | |||
if (content != null && content.length() > 0) { | |||
char[] chars = content.toCharArray(); | |||
handler.characters(chars, 0, chars.length); | |||
} | |||
handler.endElement(CATEGORY, elementName, elementName); | |||
} | |||
/** {@inheritDoc} */ | |||
public String toString() { | |||
return "AFPPageSegmentSetup(element-name=" + getElementName() | |||
+ " name=" + getName() | |||
+ " value=" + getValue() | |||
+ " resource=" + getResourceSrc() + ")"; | |||
} | |||
} | |||
} |
@@ -58,6 +58,9 @@ | |||
documents. Example: the fix of marks layering will be such a case when it's done. | |||
--> | |||
<release version="FOP Trunk" date="TBD"> | |||
<action context="Renderers" dev="JM" type="add" fixes-bug="49379" due-to="Peter Hancock"> | |||
Added ability to embed an external AFP page segment resource file (AFP output only). | |||
</action> | |||
<action context="Renderers" dev="JM" type="fix" fixes-bug="46360" due-to="Alexis Giotis"> | |||
Fixed a multi-threading issue when rendering SVG. | |||
</action> |