org.apache.fop.afp.AFPResourceManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: AFPResourceManager.java 1825861 2018-03-05 09:39:38Z ssteiner $ */
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.afp.AFPResourceLevel.ResourceType;
import org.apache.fop.afp.fonts.AFPFont;
import org.apache.fop.afp.fonts.CharacterSet;
import org.apache.fop.afp.modca.AbstractNamedAFPObject;
import org.apache.fop.afp.modca.AbstractPageObject;
import org.apache.fop.afp.modca.ActiveEnvironmentGroup;
import org.apache.fop.afp.modca.IncludeObject;
import org.apache.fop.afp.modca.IncludedResourceObject;
import org.apache.fop.afp.modca.ObjectContainer;
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.modca.triplets.EncodingTriplet;
import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet;
import org.apache.fop.afp.util.AFPResourceAccessor;
import org.apache.fop.afp.util.AFPResourceUtil;
import org.apache.fop.apps.io.InternalResourceResolver;
import org.apache.fop.fonts.FontType;
import org.apache.fop.render.afp.AFPFontConfig;
/**
* Manages the creation and storage of document resources
*/
public class AFPResourceManager {
/** logging instance */
private static Log log = LogFactory.getLog(AFPResourceManager.class);
/** The AFP datastream (document tree) */
private DataStream dataStream;
/** Resource creation factory */
private final Factory factory;
private final AFPStreamer streamer;
private final AFPDataObjectFactory dataObjectFactory;
/** Maintain a reference count of instream objects for referencing purposes */
private int instreamObjectCount;
/** Mapping of resourceInfo to AbstractCachedObject */
private final Map> includeObjectCache =
new HashMap>();
private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults();
protected boolean includeCached = true;
/**
* Main constructor
*
* @param resourceResolver the associated {@link InternalResourceResolver} instance
*/
public AFPResourceManager(InternalResourceResolver resourceResolver) {
this.factory = new Factory();
this.streamer = new AFPStreamer(factory, resourceResolver);
this.dataObjectFactory = new AFPDataObjectFactory(factory);
}
/**
* Sets the outputstream
*
* @param paintingState the AFP painting state
* @param outputStream the outputstream
* @return a new AFP DataStream
* @throws IOException thrown if an I/O exception of some sort has occurred
*/
public DataStream createDataStream(AFPPaintingState paintingState, OutputStream outputStream)
throws IOException {
this.dataStream = streamer.createDataStream(paintingState);
streamer.setOutputStream(outputStream);
return this.dataStream;
}
/**
* Returns the AFP DataStream
*
* @return the AFP DataStream
*/
public DataStream getDataStream() {
return this.dataStream;
}
/**
* Tells the streamer to write
*
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public void writeToStream() throws IOException {
streamer.close();
}
/**
* Sets the default resource group URI.
*
* @param uri the default resource group URI
*/
public void setDefaultResourceGroupUri(URI uri) {
streamer.setDefaultResourceGroupUri(uri);
}
/**
* Tries to create an include of a data object that has been previously added to the
* AFP data stream. If no such object was available, the method returns false which serves
* as a signal that the object has to be created.
* @param dataObjectInfo the data object info
* @return true if the inclusion succeeded, false if the object was not available
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
updateResourceInfoUri(resourceInfo);
return includeCachedObject(resourceInfo, dataObjectInfo.getObjectAreaInfo());
}
/**
* Creates a new data object in the AFP datastream
*
* @param dataObjectInfo the data object info
*
* @throws IOException thrown if an I/O exception of some sort has occurred.
*/
public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
if (tryIncludeObject(dataObjectInfo)) {
//Object has already been produced and is available by inclusion, so return early.
return;
}
AbstractNamedAFPObject namedObj = null;
AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
boolean useInclude = true;
Registry.ObjectType objectType = null;
// new resource so create
if (dataObjectInfo instanceof AFPImageObjectInfo) {
AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
namedObj = dataObjectFactory.createImage(imageObjectInfo);
} else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)dataObjectInfo;
namedObj = dataObjectFactory.createGraphic(graphicsObjectInfo);
} else {
// natively embedded data object
namedObj = dataObjectFactory.createObjectContainer(dataObjectInfo);
objectType = dataObjectInfo.getObjectType();
useInclude = objectType != null && objectType.isIncludable();
}
AFPResourceLevel resourceLevel = resourceInfo.getLevel();
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
useInclude &= resourceGroup != null;
if (useInclude) {
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()) {
if (usePageSegment) {
String pageSegmentName = "S10" + namedObj.getName().substring(3);
namedObj.setName(pageSegmentName);
PageSegment seg = new PageSegment(pageSegmentName);
seg.addObject(namedObj);
namedObj = seg;
}
// wrap newly created data object in a resource object
namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
}
// add data object into its resource group destination
resourceGroup.addObject(namedObj);
includeObject(namedObj, dataObjectInfo);
} else {
// not to be included so inline data object directly into the current page
dataStream.getCurrentPage().addObject(namedObj);
}
}
private abstract class AbstractCachedObject {
protected String objectName;
protected AFPDataObjectInfo dataObjectInfo;
public AbstractCachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
this.objectName = objectName;
this.dataObjectInfo = dataObjectInfo;
}
protected abstract void includeObject();
}
private class CachedPageSegment extends AbstractCachedObject {
public CachedPageSegment(String objectName, AFPDataObjectInfo dataObjectInfo) {
super(objectName, dataObjectInfo);
}
protected void includeObject() {
includePageSegment(dataObjectInfo, objectName);
}
}
private class CachedObject extends AbstractCachedObject {
public CachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
super(objectName, dataObjectInfo);
}
protected void includeObject() {
AFPResourceManager.this.includeObject(dataObjectInfo, objectName);
}
}
private void includeObject(AbstractNamedAFPObject namedObj, AFPDataObjectInfo dataObjectInfo) {
// create the include object
String objectName = namedObj.getName();
AbstractCachedObject cachedObject;
if (dataObjectInfo.isCreatePageSegment()) {
cachedObject = new CachedPageSegment(objectName, dataObjectInfo);
} else {
cachedObject = new CachedObject(objectName, dataObjectInfo);
}
cachedObject.includeObject();
addToCache(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);
}
private void addToCache(AFPResourceInfo resourceInfo, AbstractCachedObject cachedObject) {
List objs = includeObjectCache.get(resourceInfo);
if (objs == null) {
objs = new ArrayList();
includeObjectCache.put(resourceInfo, objs);
}
objs.add(cachedObject);
}
/**
* Returns {@code true} if the passed {@link AFPResourceInfo} instance is already cached.
*
* @param resourceInfo the resource info to check
* @return {@code true} if the object is cached
*/
public boolean isObjectCached(AFPResourceInfo resourceInfo) {
return includeObjectCache.containsKey(resourceInfo);
}
/**
*
* @param resourceInfo the resource info to check
* @param areaInfo the area info to check
* @return {@code true} if ...
*/
public boolean includeCachedObject(AFPResourceInfo resourceInfo, AFPObjectAreaInfo areaInfo) {
List cachedObjectList = includeObjectCache.get(resourceInfo);
if (cachedObjectList != null && includeCached) {
for (AbstractCachedObject cachedObject : cachedObjectList) {
if (areaInfo != null && cachedObjectList.size() == 1) {
cachedObject.dataObjectInfo.setObjectAreaInfo(areaInfo);
}
cachedObject.includeObject();
}
return true;
} else {
return false;
}
}
private void updateResourceInfoUri(AFPResourceInfo resourceInfo) {
String uri = resourceInfo.getUri();
if (uri == null) {
uri = "/";
}
// if this is an instream data object adjust the uri to ensure that its unique
if (uri.endsWith("/")) {
uri += "#" + (++instreamObjectCount);
resourceInfo.setUri(uri);
}
}
private void includeObject(AFPDataObjectInfo dataObjectInfo,
String objectName) {
IncludeObject includeObject = dataObjectFactory.createInclude(objectName, dataObjectInfo);
dataStream.getCurrentPage().addObject(includeObject);
}
/**
* Handles font embedding. If a font is embeddable and has not already been embedded it will be.
* @param afpFont the AFP font to be checked for embedding
* @param charSet the associated character set
* @throws IOException if there's a problem while embedding the external resources
*/
public void embedFont(AFPFont afpFont, CharacterSet charSet)
throws IOException {
if (afpFont.isEmbeddable()) {
//Embed fonts (char sets and code pages)
if (charSet.getResourceAccessor() != null) {
AFPResourceAccessor accessor = charSet.getResourceAccessor();
if (afpFont.getFontType() == FontType.TRUETYPE) {
createIncludedResource(afpFont.getFontName(),
((AFPFontConfig.AFPTrueTypeFont) afpFont).getUri(), accessor,
ResourceObject.TYPE_OBJECT_CONTAINER, true,
((AFPFontConfig.AFPTrueTypeFont) afpFont).getTTC());
} else {
createIncludedResource(
charSet.getName(), accessor,
ResourceObject.TYPE_FONT_CHARACTER_SET);
createIncludedResource(
charSet.getCodePage(), accessor,
ResourceObject.TYPE_CODE_PAGE);
}
}
}
}
private void includePageSegment(AFPDataObjectInfo dataObjectInfo,
String pageSegmentName) {
int x = dataObjectInfo.getObjectAreaInfo().getX();
int y = dataObjectInfo.getObjectAreaInfo().getY();
AbstractPageObject currentPage = dataStream.getCurrentPage();
boolean createHardPageSegments = true;
currentPage.createIncludePageSegment(pageSegmentName, x, y, createHardPageSegments);
}
/**
* Creates an included resource object by loading the contained object from a file.
* @param resourceName the name of the resource
* @param accessor resource accessor to access the resource with
* @param resourceObjectType the resource object type ({@link ResourceObject}.*)
* @throws IOException if an I/O error occurs while loading the resource
*/
public void createIncludedResource(String resourceName, AFPResourceAccessor accessor,
byte resourceObjectType) throws IOException {
URI uri;
try {
uri = new URI(resourceName.trim());
} catch (URISyntaxException e) {
throw new IOException("Could not create URI from resource name: " + resourceName
+ " (" + e.getMessage() + ")");
}
createIncludedResource(resourceName, uri, accessor, resourceObjectType, false, null);
}
/**
* Creates an included resource object by loading the contained object from a file.
* @param resourceName the name of the resource
* @param uri the URI for the resource
* @param accessor resource accessor to access the resource with
* @param resourceObjectType the resource object type ({@link ResourceObject}.*)
* @throws IOException if an I/O error occurs while loading the resource
*/
public void createIncludedResource(String resourceName, URI uri, AFPResourceAccessor accessor,
byte resourceObjectType, boolean truetype, String ttc) throws IOException {
AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
AFPResourceInfo resourceInfo = new AFPResourceInfo();
resourceInfo.setLevel(resourceLevel);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
List cachedObject = includeObjectCache.get(resourceInfo);
if (cachedObject == null) {
if (log.isDebugEnabled()) {
log.debug("Adding included resource: " + resourceName);
}
ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
if (truetype) {
ResourceObject res = factory.createResource();
res.setType(ResourceObject.TYPE_OBJECT_CONTAINER);
ActiveEnvironmentGroup.setupTruetypeMDR(res, false);
ObjectContainer oc = factory.createObjectContainer();
InputStream is = accessor.createInputStream(uri);
if (ttc != null) {
oc.setData(extractTTC(ttc, is));
} else {
oc.setData(IOUtils.toByteArray(is));
}
ActiveEnvironmentGroup.setupTruetypeMDR(oc, true);
res.addTriplet(new EncodingTriplet(1200));
res.setFullyQualifiedName(FullyQualifiedNameTriplet.TYPE_REPLACE_FIRST_GID_NAME,
FullyQualifiedNameTriplet.FORMAT_CHARSTR, resourceName, true);
res.setDataObject(oc);
resourceGroup.addObject(res);
} else {
ResourceObject resourceObject = factory.createResource(resourceName);
IncludedResourceObject resourceContent = new IncludedResourceObject(
resourceName, accessor, uri);
resourceObject.setDataObject(resourceContent);
resourceObject.setType(resourceObjectType);
resourceGroup.addObject(resourceObject);
}
//TODO what is the data object?
CachedObject newcachedObject = new CachedObject(resourceName, null);
// record mapping of resource info to data object resource name
addToCache(resourceInfo, newcachedObject);
} else {
//skip, already created
}
}
private byte[] extractTTC(String ttc, InputStream is) throws IOException {
// TrueTypeCollection trueTypeCollection = new TrueTypeCollection(is);
// for (TrueTypeFont ttf : trueTypeCollection.getFonts()) {
// String name = ttf.getNaming().getFontFamily();
// if (name.equals(ttc)) {
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// TTFSubsetter s = new TTFSubsetter(ttf, null);
// for (int i = 0; i < 256 * 256; i++) {
// s.addCharCode(i);
// }
// s.writeToStream(bos);
// return bos.toByteArray();
// }
// }
throw new IOException(ttc + " not supported");
}
/**
* 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 AFPResourceAccessor accessor) throws IOException {
AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
AFPResourceInfo resourceInfo = new AFPResourceInfo();
resourceInfo.setLevel(resourceLevel);
resourceInfo.setName(resourceName);
resourceInfo.setUri(uri.toASCIIString());
List resource = includeObjectCache.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) {
@Override
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
@Override
protected void writeStart(OutputStream os) throws IOException { }
//bypass super.writeEnd
@Override
protected void writeEnd(OutputStream os) throws IOException { }
};
resourceGroup.addObject(resourceObject);
CachedObject newresource = new CachedObject(resourceName, null);
addToCache(resourceInfo, newresource);
}
}
/**
* Sets resource level defaults. The existing defaults over merged with the ones passed in
* as parameter.
* @param defaults the new defaults
*/
public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) {
this.resourceLevelDefaults.mergeFrom(defaults);
}
/**
* Returns the resource level defaults in use with this resource manager.
* @return the resource level defaults
*/
public AFPResourceLevelDefaults getResourceLevelDefaults() {
return this.resourceLevelDefaults;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy