import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
-import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.fop.afp.modca.ResourceGroup;
import org.apache.fop.afp.modca.StreamedResourceGroup;
+import org.apache.fop.apps.io.TempResourceURIGenerator;
import org.apache.fop.apps.io.URIResolverWrapper;
/**
private static final String DEFAULT_EXTERNAL_RESOURCE_FILENAME = "resources.afp";
+ private static final TempResourceURIGenerator TEMP_URI_GENERATOR
+ = new TempResourceURIGenerator("AFPDataStream_");
+
private final Factory factory;
private final URIResolverWrapper uriResolverWrapper;
public AFPStreamer(Factory factory, URIResolverWrapper uriResolverWrapper) {
this.factory = factory;
this.uriResolverWrapper = uriResolverWrapper;
- this.tempUri = TempUriGenerator.INSTANCE.generate();
+ this.tempUri = TEMP_URI_GENERATOR.generate();
defaultResourceGroupUri = URI.create(DEFAULT_EXTERNAL_RESOURCE_FILENAME);
}
tempInputStream.close();
os.flush();
}
-
- private static final class TempUriGenerator {
-
- private static final TempUriGenerator INSTANCE = new TempUriGenerator();
-
- private static final String AFPDATASTREAM_TEMP_URL_PREFIX = "tmp:///AFPDataStream_";
-
- private final AtomicLong counter;
-
- private TempUriGenerator() {
- counter = new AtomicLong();
- }
-
- public URI generate() {
- try {
- return new URI(AFPDATASTREAM_TEMP_URL_PREFIX + counter.getAndIncrement());
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
- }
}
* @return an inputStream
* @throws IOException in the event that an I/O exception of some sort has occurred
*/
- protected InputStream openInputStream(AFPResourceAccessor accessor, String uriStr,
+ private InputStream openInputStream(AFPResourceAccessor accessor, String uriStr,
AFPEventProducer eventProducer)
throws IOException {
URI uri;
*
* @param inputStream the inputstream to close
*/
- protected void closeInputStream(InputStream inputStream) {
+ private void closeInputStream(InputStream inputStream) {
try {
if (inputStream != null) {
inputStream.close();
* @return a class representing the font descriptor
* @throws IOException if an I/O exception of some sort has occurred.
*/
- protected static FontDescriptor processFontDescriptor(
- StructuredFieldReader structuredFieldReader)
- throws IOException {
+ private static FontDescriptor processFontDescriptor(
+ StructuredFieldReader structuredFieldReader) throws IOException {
byte[] fndData = structuredFieldReader.getNext(FONT_DESCRIPTOR_SF);
return new FontDescriptor(fndData);
* @return the FontControl
* @throws IOException if an I/O exception of some sort has occurred.
*/
- protected FontControl processFontControl(StructuredFieldReader structuredFieldReader)
- throws IOException {
+ private FontControl processFontControl(StructuredFieldReader structuredFieldReader)
+ throws IOException {
byte[] fncData = structuredFieldReader.getNext(FONT_CONTROL_SF);
* @return CharacterSetOrientation array
* @throws IOException if an I/O exception of some sort has occurred.
*/
- protected CharacterSetOrientation[] processFontOrientation(
+ private CharacterSetOrientation[] processFontOrientation(
StructuredFieldReader structuredFieldReader) throws IOException {
byte[] data = structuredFieldReader.getNext(FONT_ORIENTATION_SF);
* font metric values
* @throws IOException if an I/O exception of some sort has occurred.
*/
- protected void processFontPosition(StructuredFieldReader structuredFieldReader,
+ private void processFontPosition(StructuredFieldReader structuredFieldReader,
CharacterSetOrientation[] characterSetOrientations, double metricNormalizationFactor)
throws IOException {
* font metric values
* @throws IOException if an I/O exception of some sort has occurred.
*/
- protected void processFontIndex(StructuredFieldReader structuredFieldReader,
+ private void processFontIndex(StructuredFieldReader structuredFieldReader,
CharacterSetOrientation cso, Map<String, String> codepage,
double metricNormalizationFactor)
throws IOException {
return INSTANCE;
}
+ @Override
protected Map<String, String> loadCodePage(String codePage, String encoding,
AFPResourceAccessor accessor, AFPEventProducer eventProducer) throws IOException {
// Create the HashMap to store code page information
Map<String, String> codePages = new HashMap<String, String>();
InputStream inputStream = null;
try {
- inputStream = openInputStream(accessor, codePage.trim(), eventProducer);
+ inputStream = super.openInputStream(accessor, codePage.trim(), eventProducer);
} catch (IOException e) {
eventProducer.codePageNotFound(this, e);
throw e;
}
}
} finally {
- closeInputStream(inputStream);
+ super.closeInputStream(inputStream);
}
return codePages;
}
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
-import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.Map;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext;
import org.apache.xmlgraphics.util.UnitConv;
-import org.apache.xmlgraphics.util.uri.CommonURIResolver;
import org.apache.fop.Version;
import org.apache.fop.accessibility.Accessibility;
import org.apache.fop.fo.ElementMappingRegistry;
import org.apache.fop.fo.FOEventHandler;
import org.apache.fop.fonts.FontManager;
-import org.apache.fop.hyphenation.HyphenationTreeResolver;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
import org.apache.fop.render.ImageHandlerRegistry;
import org.apache.fop.render.Renderer;
setStructureTreeEventHandler(documentHandler.getStructureTreeEventHandler());
}
this.documentHandlerOverride = documentHandler;
-
}
/**
public Source resolveURI(String uri) {
// TODO: What do we want to do when resources aren't found???
try {
- Source src;
// Have to do this so we can resolve data URIs
- if (uri.startsWith("data:")) {
- CommonURIResolver uriResolver = new CommonURIResolver();
- src = uriResolver.resolve(uri, "");
- } else {
- URI actualUri = URIResolverWrapper.cleanURI(uri);
- src = new StreamSource(newUriResolver.resolveIn(actualUri));
- src.setSystemId(uri);
- }
+ Source src = new StreamSource(newUriResolver.resolveIn(uri));
+ src.setSystemId(uri);
return src;
} catch (URISyntaxException use) {
return null;
return factory.getColorSpaceCache();
}
- /** @return the HyphenationTreeResolver for resolving user-supplied hyphenation patterns. */
- public HyphenationTreeResolver getHyphenationTreeResolver() {
- return factory.getHyphenationTreeResolver();
- }
-
public Map<String, String> getHyphPatNames() {
return factory.getHyphPatNames();
}
}
}
- if (cfg.getChild("hyphenation-base", false) != null) {
- String path = cfg.getChild("hyphenation-base").getValue(null);
- if (defaultBaseURI != null) {
- try {
- URI hyphBaseUri = URIResolverWrapper.getBaseURI(path);
- fopFactoryBuilder.setHyphenationBaseURI(defaultBaseURI.resolve(hyphBaseUri));
- } catch (URISyntaxException use) {
- LogUtil.handleException(log, use, strict);
- }
- }
- }
-
// renderer options
if (cfg.getChild("source-resolution", false) != null) {
float srcRes = cfg.getChild("source-resolution").getValueAsFloat(
import java.util.Map;
import java.util.Set;
-import javax.xml.transform.Source;
-import javax.xml.transform.TransformerException;
-
import org.xml.sax.SAXException;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.fop.fo.ElementMapping;
import org.apache.fop.fo.ElementMappingRegistry;
import org.apache.fop.fonts.FontManager;
-import org.apache.fop.hyphenation.HyphenationTreeResolver;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
import org.apache.fop.render.ImageHandlerRegistry;
import org.apache.fop.render.RendererConfig;
this.config = config;
this.uriResolverWrapper = new URIResolverWrapper(config.getBaseURI(), config.getNewURIResolver());
this.elementMappingRegistry = new ElementMappingRegistry(this);
- this.colorSpaceCache = new ColorSpaceCache(config.getURIResolver());
+ this.colorSpaceCache = new ColorSpaceCache(uriResolverWrapper);
this.rendererFactory = new RendererFactory(config.preferRenderer());
this.xmlHandlers = new XMLHandlerRegistry();
this.imageHandlers = new ImageHandlerRegistry();
return config.getLayoutManagerMakerOverride();
}
- /** @return the hyphen base URI */
- public String getHyphenBaseURI() {
- return config.getHyphenationBaseURI().toASCIIString();
- }
-
- /** @return the HyphenationTreeResolver for resolving user-supplied hyphenation patterns. */
- public HyphenationTreeResolver getHyphenationTreeResolver() {
- return config.getHyphenationTreeResolver();
- }
public Map<String, String> getHyphPatNames() {
return config.getHyphPatNames();
return config.getFontManager();
}
- /**
- * Attempts to resolve the given URI.
- * Will use the configured resolver and if not successful fall back
- * to the default resolver.
- * @param href URI to access
- * @param baseUri the base URI to resolve against
- * @return A {@link javax.xml.transform.Source} object, or null if the URI
- * cannot be resolved.
- * @see org.apache.fop.apps.io.FOURIResolver
- */
- public Source resolveURI(String href, String baseUri) {
- Source source = null;
- try {
- source = config.getURIResolver().resolve(href, baseUri);
- } catch (TransformerException e) {
- log.error("Attempt to resolve URI '" + href + "' failed: ", e);
- }
- return source;
- }
-
/**
* Returns the color space cache for this instance.
* <p>
import org.apache.xmlgraphics.image.loader.ImageContext;
import org.apache.xmlgraphics.image.loader.ImageManager;
-import org.apache.fop.apps.io.FOURIResolver;
import org.apache.fop.apps.io.ResourceResolver;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.fonts.FontManager;
-import org.apache.fop.hyphenation.HyphenationTreeResolver;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
/**
return this;
}
- /**
- * Sets the base URI for hyphenation data.
- *
- * @param hyphenationBase the hyphenation-base-URI
- * @return <code>this</code>
- * @deprecated this will be phased out in favour of a unified URI resolution mechanism
- */
- public FopFactoryBuilder setHyphenationBaseURI(URI hyphenationBase) {
- fopFactoryConfigBuilder.setHyphenationBaseURI(hyphenationBase);
- return this;
- }
-
- /**
- * Sets the URI resolver specific to Hyphenation data.
- *
- * @param hyphResolver the hyphenation-URI-resolver
- * @return <code>this</code>
- * @deprecated this will be phased out in favour of a unified URI resolution mechanism
- */
- public FopFactoryBuilder setHyphenationTreeResolver(
- HyphenationTreeResolver hyphResolver) {
- fopFactoryConfigBuilder.setHyphenationTreeResolver(hyphResolver);
- return this;
-
- }
-
/**
* Sets whether to perform strict validation on the FO used.
*
private URI baseURI;
- private URI hyphenationBaseURI;
-
- private HyphenationTreeResolver hyphenationTreeResolver;
-
private boolean hasStrictFOValidation = true;
private boolean hasStrictUserValidation = FopFactoryConfig.DEFAULT_STRICT_USERCONFIG_VALIDATION;
this.enviro = enviro;
this.baseURI = enviro.getDefaultBaseURI();
this.imageManager = new ImageManager(new ImageContextImpl(this));
- this.resolver = new FOURIResolver();
}
/** {@inheritDoc} */
return baseURI;
}
- /** {@inheritDoc} */
- public URI getHyphenationBaseURI() {
- return hyphenationBaseURI;
- }
-
- /** {@inheritDoc} */
- public HyphenationTreeResolver getHyphenationTreeResolver() {
- return hyphenationTreeResolver;
- }
-
/** {@inheritDoc} */
public boolean validateStrictly() {
return hasStrictFOValidation;
void setBaseURI(URI baseURI);
- void setHyphenationBaseURI(URI hyphenationBase);
-
- void setHyphenationTreeResolver(HyphenationTreeResolver hyphResolver);
-
void setStrictFOValidation(boolean validateStrictly);
void setStrictUserConfigValidation(boolean validateStrictly);
throwIllegalStateException();
}
- public void setHyphenationTreeResolver(
- HyphenationTreeResolver hyphResolver) {
- throwIllegalStateException();
- }
-
public void setStrictFOValidation(boolean validateStrictly) {
throwIllegalStateException();
}
config.baseURI = baseURI;
}
- public void setHyphenationBaseURI(URI hyphenationBase) {
- config.hyphenationBaseURI = hyphenationBase;
- }
-
- public void setHyphenationTreeResolver(HyphenationTreeResolver hyphResolver) {
- config.hyphenationTreeResolver = hyphResolver;
- }
-
public void setStrictFOValidation(boolean validateStrictly) {
config.hasStrictFOValidation = validateStrictly;
}
import java.util.Map;
import java.util.Set;
-import javax.xml.transform.URIResolver;
-
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.fop.apps.io.ResourceResolver;
import org.apache.fop.fonts.FontManager;
-import org.apache.fop.hyphenation.HyphenationTreeResolver;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
/**
*/
ResourceResolver getNewURIResolver();
- /**
- * The URI resolver for controlling file access.
- *
- * @return the URI resolver
- * @deprecated please use the {@link #getNewURIResolver()} method.
- */
- URIResolver getURIResolver();
-
/**
* The base URI from which URIs are resolved against.
*
*/
URI getBaseURI();
- /**
- * The base URI of hyphenation data.
- *
- * @return the hyphenation-base-URI
- * @deprecated this intelligence can be configured in the URI resolver set in
- * {@link #getNewURIResolver()}
- */
- URI getHyphenationBaseURI();
-
- /**
- * The URI resolver for resolving hyphenation data.
- *
- * @return the hyphenation-URI-resolver
- * @deprecated this intelligence can be configured in the URI resolver set in
- * {@link #getNewURIResolver()}
- */
- HyphenationTreeResolver getHyphenationTreeResolver();
-
/** @see {@link FopFactory#validateStrictly()} */
boolean validateStrictly();
}
private static boolean isTempUri(URI uri) {
- return "tmp".equals(uri.getScheme());
+ return TempResourceURIGenerator.isTempUri(uri);
}
public Resource getResource(URI uri) throws IOException {
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.apache.fop.apps.io;
+
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Creates a URI for any temporary resource used within FOP.
+ */
+public final class TempResourceURIGenerator {
+
+ private static final String TMP_SCHEMA = "tmp";
+
+ private final String tempURIPrefix;
+
+ private final AtomicLong counter;
+
+ /**
+ * @param uriPrefix a prefix used to name the unique URI
+ */
+ public TempResourceURIGenerator(String uriPrefix) {
+ counter = new AtomicLong();
+ tempURIPrefix = URI.create(TMP_SCHEMA + ":///" + uriPrefix).toASCIIString();
+ }
+
+ /**
+ * Generate a unique URI for a temporary resource
+ * @return the URI
+ */
+ public URI generate() {
+ return URI.create(tempURIPrefix + getUniqueId());
+ }
+
+ private String getUniqueId() {
+ return Long.toHexString(counter.getAndIncrement());
+ }
+
+ public static boolean isTempUri(URI uri) {
+ return TMP_SCHEMA.equals(uri.getScheme());
+ }
+}
import java.net.URI;
import java.net.URISyntaxException;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.xmlgraphics.util.uri.DataURIResolver;
public class URIResolverWrapper {
private final URI baseUri;
private final ResourceResolver uriResolver;
+ private final DataURIResolver dataSchemeResolver = new DataURIResolver();
public URIResolverWrapper(URI baseUri, ResourceResolver uriResolver) {
this.baseUri = baseUri;
}
public InputStream resolveIn(String stringUri) throws IOException, URISyntaxException {
+ if (stringUri.startsWith("data:")) {
+ return resolveDataURI(stringUri);
+ }
return resolveIn(cleanURI(stringUri));
}
public InputStream resolveIn(URI uri) throws IOException {
+ if (uri.getScheme() != null && uri.getScheme().startsWith("data")) {
+ return resolveDataURI(uri.toASCIIString());
+ }
return uriResolver.getResource(resolveFromBase(uri));
}
return baseUri.resolve(uri);
}
- public static URI cleanURI(String base) throws URISyntaxException {
+ public static URI cleanURI(String uriStr) throws URISyntaxException {
// replace back slash with forward slash to ensure windows file:/// URLS are supported
- if (base == null) {
+ if (uriStr == null) {
return null;
}
- String fixedUri = base.replace('\\', '/');
+ String fixedUri = uriStr.replace('\\', '/');
fixedUri = fixedUri.replace(" ", "%20");
URI baseURI = new URI(fixedUri);
return baseURI;
return cleanURI(path);
}
+ private InputStream resolveDataURI(String dataURI) {
+ try {
+ Source src = dataSchemeResolver.resolve(dataURI, "");
+ return src == null ? null : ((StreamSource) src).getInputStream();
+ } catch (TransformerException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
+import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.io.IOUtils;
-import org.apache.fop.ResourceEventProducer;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.io.TempResourceURIGenerator;
import org.apache.fop.fonts.FontInfo;
/**
*/
public class CachedRenderPagesModel extends RenderPagesModel {
- private Map<PageViewport, String> pageMap = new HashMap<PageViewport, String>();
+ private Map<PageViewport, URI> pageMap = new HashMap<PageViewport, URI>();
/** Base directory to save temporary file in, typically points to the user's temp dir. */
- protected File baseDir;
+ private final URI tempBaseURI;
+ private static final TempResourceURIGenerator TEMP_URI_GENERATOR
+ = new TempResourceURIGenerator("cached-pages");
/**
* Main Constructor
public CachedRenderPagesModel (FOUserAgent userAgent, String outputFormat,
FontInfo fontInfo, OutputStream stream) throws FOPException {
super(userAgent, outputFormat, fontInfo, stream);
- //TODO: Avoid System.getProperty()?
- this.baseDir = new File(System.getProperty("java.io.tmpdir"));
+ tempBaseURI = TEMP_URI_GENERATOR.generate();
}
/** {@inheritDoc} */
if (pageViewport != newpage) {
try {
// load page from cache
- String name = pageMap.get(pageViewport);
- File tempFile = new File(baseDir, name);
- log.debug("Loading page from: " + tempFile);
- ObjectInputStream in = new ObjectInputStream(
- new BufferedInputStream(
- new FileInputStream(tempFile)));
+ URI tempURI = pageMap.get(pageViewport);
+ log.debug("Loading page from: " + tempURI);
+ InputStream inStream = renderer.getUserAgent().getNewURIResolver().resolveIn(tempURI);
+ ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(inStream));
try {
pageViewport.loadPage(in);
} finally {
+ IOUtils.closeQuietly(inStream);
IOUtils.closeQuietly(in);
}
- if (!tempFile.delete()) {
- ResourceEventProducer eventProducer
- = ResourceEventProducer.Provider.get(
- renderer.getUserAgent().getEventBroadcaster());
- eventProducer.cannotDeleteTempFile(this, tempFile);
- }
pageMap.remove(pageViewport);
} catch (Exception e) {
- AreaEventProducer eventProducer
- = AreaEventProducer.Provider.get(
+ AreaEventProducer eventProducer = AreaEventProducer.Provider.get(
renderer.getUserAgent().getEventBroadcaster());
eventProducer.pageLoadError(this, pageViewport.getPageNumberString(), e);
}
// save page to cache
ObjectOutputStream tempstream;
String fname = "fop-page-" + page.getPageIndex() + ".ser";
- File tempFile = new File(baseDir, fname);
- tempFile.deleteOnExit();
- tempstream = new ObjectOutputStream(new BufferedOutputStream(
- new FileOutputStream(tempFile)));
+ URI tempURI = tempBaseURI.resolve(fname);
+ OutputStream outStream = renderer.getUserAgent().getNewURIResolver().resolveOut(tempURI);
+ tempstream = new ObjectOutputStream(new BufferedOutputStream(outStream));
try {
page.savePage(tempstream);
} finally {
IOUtils.closeQuietly(tempstream);
}
- pageMap.put(page, fname);
+ pageMap.put(page, tempURI);
if (log.isDebugEnabled()) {
- log.debug("Page saved to temporary file: " + tempFile);
+ log.debug("Page saved to temporary file: " + tempURI);
}
} catch (IOException ioe) {
AreaEventProducer eventProducer
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
-import java.io.File;
import java.net.URI;
import org.w3c.dom.Element;
try {
URI baseUri = getUserAgent().getNewURIResolver().getBaseURI();
- baseUri = baseUri == null ? new File("").toURI() : baseUri;
if (baseUri != null) {
SVGOMDocument svgdoc = (SVGOMDocument)doc;
svgdoc.setURLObject(baseUri.toURL());
+++ /dev/null
-/*
- * 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.hyphenation;
-
-import javax.xml.transform.Source;
-
-/**
- * <p>This interface is used to resolve relative URIs pointing to hyphenation tree files.</p>
- */
-public interface HyphenationTreeResolver {
-
- /**
- * Called to resolve an URI to a Source instance. The base URI needed by the URIResolver's
- * resolve() method is defined to be implicitely available in this case. If the URI cannot
- * be resolved, null is returned.
- * @param href An href attribute, which may be relative or absolute.
- * @return A Source object, or null if the href could not resolved.
- */
- Source resolve(String href);
-
-}
package org.apache.fop.hyphenation;
import java.io.BufferedInputStream;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
+import java.net.URISyntaxException;
import java.util.Map;
-import javax.xml.transform.Source;
-import javax.xml.transform.stream.StreamSource;
-
import org.xml.sax.InputSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.fop.apps.io.URIResolverWrapper;
+
/**
* <p>This class is the main entry point to the hyphenation package.
* You can use only the static methods or create an instance.</p>
* @return the hyphenation tree
*/
public static HyphenationTree getHyphenationTree(String lang,
- String country, HyphenationTreeResolver resolver, Map hyphPatNames) {
+ String country, URIResolverWrapper resolver, Map hyphPatNames) {
String llccKey = HyphenationTreeCache.constructLlccKey(lang, country);
HyphenationTreeCache cache = getHyphenationTreeCache();
* @param hyphPatNames the map with user-configured hyphenation pattern file names
* @return the hyphenation tree
*/
- private static HyphenationTree getHyphenationTree2(String lang,
- String country, HyphenationTreeResolver resolver, Map hyphPatNames) {
+ public static HyphenationTree getHyphenationTree2(String lang,
+ String country, URIResolverWrapper resolver, Map hyphPatNames) {
String llccKey = HyphenationTreeCache.constructLlccKey(lang, country);
HyphenationTreeCache cache = getHyphenationTreeCache();
return hTree;
}
- /**
- * Load tree from serialized file or xml file
- * using configuration settings
- * @param key language key for the requested hyphenation file
- * @param hyphenDir base directory to find hyphenation files in
- * @return the requested HypenationTree or null if it is not available
- */
- public static HyphenationTree getUserHyphenationTree(String key,
- String hyphenDir) {
- final File baseDir = new File(hyphenDir);
- HyphenationTreeResolver resolver = new HyphenationTreeResolver() {
- public Source resolve(String href) {
- File f = new File(baseDir, href);
- return new StreamSource(f);
- }
- };
- return getUserHyphenationTree(key, resolver);
- }
-
/**
* Load tree from serialized file or xml file
* using configuration settings
* @return the requested HypenationTree or null if it is not available
*/
public static HyphenationTree getUserHyphenationTree(String key,
- HyphenationTreeResolver resolver) {
+ URIResolverWrapper resolver) {
HyphenationTree hTree = null;
// I use here the following convention. The file name specified in
// the configuration is taken as the base name. First we try
// first try serialized object
String name = key + ".hyp";
- Source source = resolver.resolve(name);
- if (source != null) {
+ try {
+ InputStream in = getHyphenationTreeStream(name, resolver);
try {
- InputStream in = null;
- if (source instanceof StreamSource) {
- in = ((StreamSource) source).getInputStream();
- }
- if (in == null) {
- if (source.getSystemId() != null) {
- in = new java.net.URL(source.getSystemId()).openStream();
- } else {
- throw new UnsupportedOperationException
- ("Cannot load hyphenation pattern file"
- + " with the supplied Source object: " + source);
- }
- }
- in = new BufferedInputStream(in);
- try {
- hTree = readHyphenationTree(in);
- } finally {
- IOUtils.closeQuietly(in);
- }
- return hTree;
- } catch (IOException ioe) {
- if (log.isDebugEnabled()) {
- log.debug("I/O problem while trying to load " + name, ioe);
- }
+ hTree = readHyphenationTree(in);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ return hTree;
+ } catch (IOException ioe) {
+ if (log.isDebugEnabled()) {
+ log.debug("I/O problem while trying to load " + name, ioe);
}
}
// try the raw XML file
name = key + ".xml";
- source = resolver.resolve(name);
- if (source != null) {
- hTree = new HyphenationTree();
+ hTree = new HyphenationTree();
+ try {
+ InputStream in = getHyphenationTreeStream(name, resolver);
try {
- InputStream in = null;
- if (source instanceof StreamSource) {
- in = ((StreamSource) source).getInputStream();
- }
- if (in == null) {
- if (source.getSystemId() != null) {
- in = new java.net.URL(source.getSystemId()).openStream();
- } else {
- throw new UnsupportedOperationException(
- "Cannot load hyphenation pattern file"
- + " with the supplied Source object: " + source);
- }
- }
- if (!(in instanceof BufferedInputStream)) {
- in = new BufferedInputStream(in);
- }
- try {
- InputSource src = new InputSource(in);
- src.setSystemId(source.getSystemId());
- hTree.loadPatterns(src);
- } finally {
- IOUtils.closeQuietly(in);
- }
- if (statisticsDump) {
- System.out.println("Stats: ");
- hTree.printStats();
- }
- return hTree;
- } catch (HyphenationException ex) {
- log.error("Can't load user patterns from XML file " + source.getSystemId()
- + ": " + ex.getMessage());
- return null;
- } catch (IOException ioe) {
- if (log.isDebugEnabled()) {
- log.debug("I/O problem while trying to load " + name, ioe);
- }
- return null;
+ InputSource src = new InputSource(in);
+ src.setSystemId(name);
+ hTree.loadPatterns(src);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ if (statisticsDump) {
+ System.out.println("Stats: ");
+ hTree.printStats();
}
- } else {
+ return hTree;
+ } catch (HyphenationException ex) {
+ log.error("Can't load user patterns from XML file " + name + ": " + ex.getMessage());
+ return null;
+ } catch (IOException ioe) {
if (log.isDebugEnabled()) {
- log.debug("Could not load user hyphenation file for '" + key + "'.");
+ log.debug("I/O problem while trying to load " + name, ioe);
}
return null;
}
}
+ private static InputStream getHyphenationTreeStream(String name, URIResolverWrapper resolver)
+ throws IOException {
+ try {
+ return new BufferedInputStream(resolver.resolveIn(name));
+ } catch (URISyntaxException use) {
+ log.debug("An exception was thrown while attempting to load " + name, use);
+ }
+ return null;
+ }
+
/**
* Hyphenates a word.
* @param lang the language
* @return the hyphenation result
*/
public static Hyphenation hyphenate(String lang, String country,
- HyphenationTreeResolver resolver,
- Map hyphPatNames,
- String word,
- int leftMin, int rightMin) {
+ URIResolverWrapper resolver, Map hyphPatNames, String word, int leftMin, int rightMin) {
HyphenationTree hTree = getHyphenationTree(lang, country, resolver, hyphPatNames);
if (hTree == null) {
return null;
}
return hTree.hyphenate(word, leftMin, rightMin);
}
-
}
// TextLM which generate the hyphenation buffer,
// since these properties inherit and could be specified
// on an inline or wrapper below the block level.
- Hyphenation hyph
- = Hyphenator.hyphenate(hyphenationProperties.language.getString(),
+ Hyphenation hyph = Hyphenator.hyphenate(hyphenationProperties.language.getString(),
hyphenationProperties.country.getString(),
- getFObj().getUserAgent().getHyphenationTreeResolver(),
+ getFObj().getUserAgent().getNewURIResolver(),
getFObj().getUserAgent().getHyphPatNames(),
sbChars.toString(),
hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
// Java
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
return this.fontMap;
}
- /**
- * Resolve a URI.
- *
- * @param uri the uri to resolve
- * @throws java.io.FileNotFoundException if the URI could not be resolved
- * @return the InputStream from the URI.
- */
- protected InputStream resolveURI(String uri)
- throws java.io.FileNotFoundException {
- try {
- /* TODO: Temporary hack to compile, improve later */
- return new java.net.URL(uri).openStream();
- } catch (Exception e) {
- throw new java.io.FileNotFoundException(
- "URI could not be resolved (" + e.getMessage() + "): " + uri);
- }
- }
-
/**
* Get an image from the image map.
*
/**
* This class is serves as a factory from
*/
-public class StreamCacheFactory {
+public final class StreamCacheFactory {
- private static boolean defaultCacheToFile = false;
- private static StreamCacheFactory fileInstance = null;
- private static StreamCacheFactory memoryInstance = null;
-
- private boolean cacheToFile = false;
+ private static StreamCacheFactory memoryInstance = new StreamCacheFactory();
/**
* Returns an instance of a StreamCacheFactory with the requested features.
* @param cacheToFile True if file shall be cached using a temporary file
* @return StreamCacheFactory the requested factory
*/
- public static StreamCacheFactory getInstance(boolean cacheToFile) {
- if (cacheToFile) {
- if (fileInstance == null) {
- fileInstance = new StreamCacheFactory(true);
- }
- return fileInstance;
- } else {
- if (memoryInstance == null) {
- memoryInstance = new StreamCacheFactory(false);
- }
- return memoryInstance;
- }
- }
-
- /**
- * Returns an instance of a StreamCacheFactory depending on the default
- * setting for cacheToFile.
- * @return StreamCacheFactory the requested factory
- */
public static StreamCacheFactory getInstance() {
- return getInstance(defaultCacheToFile);
- }
-
- /**
- * Sets the global default for cacheToFile
- * @param cacheToFile True if stream caches should be held in files.
- */
- public static void setDefaultCacheToFile(boolean cacheToFile) {
- defaultCacheToFile = cacheToFile;
+ return memoryInstance;
}
/**
* Creates a new StreamCacheFactory.
* @param cacheToFile True if file shall be cached using a temporary file
*/
- public StreamCacheFactory(boolean cacheToFile) {
- this.cacheToFile = cacheToFile;
+ private StreamCacheFactory() {
}
/**
* @return a new StreamCache for caching streams
*/
public StreamCache createStreamCache() throws IOException {
- if (this.cacheToFile) {
- return new TempFileStreamCache();
- } else {
- return new InMemoryStreamCache();
- }
+ return new InMemoryStreamCache();
}
/**
* @return a new StreamCache for caching streams
*/
public StreamCache createStreamCache(int hintSize) throws IOException {
- if (this.cacheToFile) {
- return new TempFileStreamCache();
- } else {
- return new InMemoryStreamCache(hintSize);
- }
- }
-
- /**
- * Get the value of the global cacheToFile flag.
- * @return the current cache to file flag
- */
- public boolean getCacheToFile() {
- return this.cacheToFile;
+ return new InMemoryStreamCache(hintSize);
}
-
-
}
package org.apache.fop.render.intermediate;
-import java.io.File;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.net.URL;
+import java.net.URI;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
-import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.fop.fonts.FontCollection;
/** {@inheritDoc} */
public void setResult(Result result) throws IFException {
if (result instanceof StreamResult) {
- StreamResult streamResult = (StreamResult)result;
+ StreamResult streamResult = (StreamResult) result;
OutputStream out = streamResult.getOutputStream();
if (out == null) {
if (streamResult.getWriter() != null) {
"FOP cannot use a Writer. Please supply an OutputStream!");
}
try {
- URL url = new URL(streamResult.getSystemId());
- File f = FileUtils.toFile(url);
- if (f != null) {
- out = new java.io.FileOutputStream(f);
- } else {
- out = url.openConnection().getOutputStream();
- }
+ URI resultURI = URI.create(streamResult.getSystemId());
+ out = new BufferedOutputStream(getUserAgent().getNewURIResolver().resolveOut(resultURI));
} catch (IOException ioe) {
throw new IFException("I/O error while opening output stream" , ioe);
}
- out = new java.io.BufferedOutputStream(out);
this.ownOutputStream = true;
}
- if (out == null) {
- throw new IllegalArgumentException("Need a StreamResult with an OutputStream");
- }
this.outputStream = out;
} else {
throw new UnsupportedOperationException(
public void endPageContent() throws IFException {
if (this.currentImage != null) {
try {
- //ImageWriterUtil.saveAsPNG(this.currentImage, new java.io.File("D:/page.png"));
Rectangle printArea = this.currentPageDefinition.getLogicalPageRect();
gen.setCursorPos(0, 0);
gen.paintBitmap(this.currentImage, printArea.getSize(), true);
} finally {
g2d.dispose();
}
- /*
- try {
- BatchDiffer.saveAsPNG(alpha, new java.io.File("D:/out-alpha.png"));
- BatchDiffer.saveAsPNG(mask, new java.io.File("D:/out-mask.png"));
- } catch (IOException e) {
- e.printStackTrace();
- }*/
return mask;
} else {
return null;
(int)Math.ceil(UnitConv.mpt2px(targetDim.height, effResolution)));
}
boolean scaled = !orgDim.equals(effDim);
- //ImageWriterUtil.saveAsPNG(img, new java.io.File("D:/text-0-org.png"));
boolean monochrome = isMonochromeImage(img);
if (!monochrome) {
import java.awt.Dimension;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
-import java.io.File;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox;
import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.apps.io.TempResourceURIGenerator;
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
protected PSGenerator gen;
/** the temporary file in case of two-pass processing */
- private File tempFile;
+ private URI tempURI;
+ private static final TempResourceURIGenerator TEMP_URI_GENERATOR
+ = new TempResourceURIGenerator("ps-optimize");
private int currentPageNumber = 0;
private PageDefinition currentPageDefinition;
super.startDocument();
this.fontResources = new FontResourceCache(getFontInfo());
try {
- OutputStream out;
+ final OutputStream out;
if (psUtil.isOptimizeResources()) {
- this.tempFile = File.createTempFile("fop", null);
- out = new java.io.FileOutputStream(this.tempFile);
- out = new java.io.BufferedOutputStream(out);
+ tempURI = TEMP_URI_GENERATOR.generate();
+ out = new BufferedOutputStream(getUserAgent().getNewURIResolver().resolveOut(tempURI));
} else {
out = this.outputStream;
}
log.debug("Processing PostScript resources...");
long startTime = System.currentTimeMillis();
ResourceTracker resTracker = gen.getResourceTracker();
- InputStream in = new java.io.FileInputStream(this.tempFile);
- in = new java.io.BufferedInputStream(in);
+ InputStream in = new BufferedInputStream(getUserAgent().getNewURIResolver().resolveIn(tempURI));
try {
try {
ResourceHandler handler = new ResourceHandler(getUserAgent(), this.fontInfo,
}
} finally {
IOUtils.closeQuietly(in);
- if (!this.tempFile.delete()) {
- this.tempFile.deleteOnExit();
- log.warn("Could not delete temporary file: " + this.tempFile);
- }
}
if (log.isDebugEnabled()) {
long duration = System.currentTimeMillis() - startTime;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
-import java.net.URI;
-import java.net.URISyntaxException;
+import java.io.InputStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
-import javax.xml.transform.Source;
-import javax.xml.transform.URIResolver;
-import javax.xml.transform.stream.StreamSource;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.java2d.color.ICCColorSpaceWithIntent;
import org.apache.xmlgraphics.java2d.color.RenderingIntent;
-import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;
import org.apache.fop.apps.io.URIResolverWrapper;
/** logger instance */
private static Log log = LogFactory.getLog(ColorSpaceCache.class);
- private URIResolver resolver;
- private Map<String, ColorSpace> colorSpaceMap
- = Collections.synchronizedMap(new java.util.HashMap<String, ColorSpace>());
+ private URIResolverWrapper resolver;
+ private Map<String, ColorSpace> colorSpaceMap = Collections.synchronizedMap(new HashMap<String, ColorSpace>());
/**
* Default constructor
* @param resolver uri resolver
*/
- public ColorSpaceCache(URIResolver resolver) {
+ public ColorSpaceCache(URIResolverWrapper resolver) {
this.resolver = resolver;
}
* @param renderingIntent overriding rendering intent
* @return ICC ColorSpace object or null if ColorSpace could not be created
*/
- public ColorSpace get(String profileName, String base, String iccProfileSrc,
+ public ColorSpace get(String profileName, String iccProfileSrc,
RenderingIntent renderingIntent) {
- String key = profileName + ":" + base + iccProfileSrc;
+ String key = profileName + ":" + iccProfileSrc;
// TODO: This stuff needs some TLC, fix it!!
- try {
- URI uri = URIResolverWrapper.getBaseURI(base);
- key = uri.resolve(URIResolverWrapper.cleanURI(iccProfileSrc)).toASCIIString();
- } catch (URISyntaxException e) {
- // TODO: handle this
- }
ColorSpace colorSpace = null;
if (!colorSpaceMap.containsKey(key)) {
try {
ICC_Profile iccProfile = null;
// First attempt to use the FOP URI resolver to locate the ICC
// profile
- Source src = resolver.resolve(iccProfileSrc, base);
- if (src != null && src instanceof StreamSource) {
+ InputStream stream = resolver.resolveIn(iccProfileSrc);
+ if (stream != null) {
// FOP URI resolver found ICC profile - create ICC profile
// from the Source
- iccProfile = ColorProfileUtil.getICC_Profile(((StreamSource) src)
- .getInputStream());
+ iccProfile = ICC_Profile.getInstance(stream);
} else {
// TODO - Would it make sense to fall back on VM ICC
// resolution
RenderingIntent renderingIntent = RenderingIntent.AUTO;
//TODO connect to fo:color-profile/@rendering-intent
colorSpace = foUserAgent.getColorSpaceCache().get(
- iccProfileName,
- foUserAgent.getNewURIResolver().getBaseURI().toASCIIString(),
- iccProfileSrc, renderingIntent);
+ iccProfileName, iccProfileSrc, renderingIntent);
}
if (colorSpace != null) {
// ColorSpace is available
RenderingIntent renderingIntent = RenderingIntent.AUTO;
//TODO connect to fo:color-profile/@rendering-intent
colorSpace = (ICC_ColorSpace)foUserAgent.getColorSpaceCache().get(
- iccProfileName,
- foUserAgent.getNewURIResolver().getBaseURI().toASCIIString(),
- iccProfileSrc, renderingIntent);
+ iccProfileName, iccProfileSrc, renderingIntent);
}
if (colorSpace != null) {
ICC_Profile profile = colorSpace.getProfile();
assertFalse(buildFactory().isAccessibilityEnabled());
}
- @Test
- public void testHyphenationBaseURI() {
- String hyphBaseURI = "./test/base/uri/";
- builder.setHyphenationBaseURI(hyphBaseURI);
- assertEquals(baseURI.resolve(hyphBaseURI),
- URI.create(buildFactory().getHyphenBaseURI()));
- }
-
@Test
public void testSourceResolution() {
float srcRes = 123.456f;
+++ /dev/null
-/*
- * 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.config;
-
-import static org.junit.Assert.fail;
-
-import java.net.MalformedURLException;
-
-import org.apache.fop.apps.io.FOURIResolver;
-import org.junit.Test;
-
-/**
- * This tests some aspects of the {@link FOURIResolver} class.
- */
-public class FOURIResolverTestCase {
-
- /**
- * Checks the {@link FOURIResolver#checkBaseURL(String)} method.
- * @throws Exception if an error occurs
- */
- @Test
- public void testCheckBaseURI() throws Exception {
- FOURIResolver resolver = new FOURIResolver(true);
- System.out.println(resolver.checkBaseURL("./test/config"));
- System.out.println(resolver.checkBaseURL("file:test/config"));
- System.out.println(resolver.checkBaseURL("fantasy:myconfig"));
- System.out.println(resolver.checkBaseURL("file:test\\config\\"));
- try {
- resolver.checkBaseURL("./doesnotexist");
- fail("Expected an exception for a inexistent base directory");
- } catch (MalformedURLException mfue) {
- //expected
- }
- try {
- resolver.checkBaseURL("file:doesnotexist");
- fail("Expected an exception for a inexistent base URI");
- } catch (MalformedURLException mfue) {
- //expected
- }
- }
-
-}
FontMetricsUrlMalformedTestCase.class,
FontsDirectoryRecursiveTestCase.class,
FontsAutoDetectTestCase.class,
- FontsSubstitutionTestCase.class,
- FOURIResolverTestCase.class
+ FontsSubstitutionTestCase.class
})
public class UserConfigTestSuite {
}
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
-import javax.xml.transform.URIResolver;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.fop.apps.io.ResourceResolver;
import org.apache.fop.fonts.FontManager;
import org.apache.fop.fotreetest.ext.TestElementMapping;
-import org.apache.fop.hyphenation.HyphenationTreeResolver;
import org.apache.fop.layoutengine.LayoutEngineTestUtils;
import org.apache.fop.layoutengine.TestFilesConfiguration;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
return delegate.getNewURIResolver();
}
- public URIResolver getURIResolver() {
- return delegate.getURIResolver();
- }
-
public URI getBaseURI() {
return delegate.getBaseURI();
}
- public URI getHyphenationBaseURI() {
- return delegate.getHyphenationBaseURI();
- }
-
- public HyphenationTreeResolver getHyphenationTreeResolver() {
- return delegate.getHyphenationTreeResolver();
- }
-
public boolean validateStrictly() {
return delegate.validateStrictly();
}
*/
public class ColorUtilTestCase {
private FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
- private final String thisDirectory = "file:" + new File("./").getAbsolutePath();
/**
* Test serialization to String.
FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
URI sRGBLoc = new URI("src/java/org/apache/fop/pdf/sRGB%20Color%20Space%20Profile.icm");
ColorSpace cs = fopFactory.getColorSpaceCache().get(
- "sRGBAlt", thisDirectory, sRGBLoc.toASCIIString(), RenderingIntent.AUTO);
+ "sRGBAlt", sRGBLoc.toASCIIString(), RenderingIntent.AUTO);
assertNotNull("Color profile not found", cs);
FOUserAgent ua = fopFactory.newFOUserAgent();
FopFactory fopFactory = FopFactory.newInstance(new File("./").toURI());
URI ncpLoc = new URI("test/resources/color/ncp-example.icc");
ColorSpace cs = fopFactory.getColorSpaceCache().get(
- "NCP", thisDirectory, ncpLoc.toASCIIString(), RenderingIntent.AUTO);
+ "NCP", ncpLoc.toASCIIString(), RenderingIntent.AUTO);
assertNotNull("Color profile not found", cs);
FOUserAgent ua = fopFactory.newFOUserAgent();
<fo:flow flow-name="xsl-region-body">
<fo:block>Image referenced by RFC 2397 "data" URL:</fo:block>
<fo:block>
- <fo:external-graphic src="url("data:image/gif;base64, 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
")"/>EOG
+ <fo:external-graphic src="url("data:image/gif;base64,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
")"/>EOG
</fo:block>
<fo:block>EOF</fo:block>
</fo:flow>