You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AFPResourceManager.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.afp;
  19. import java.io.BufferedInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.net.URI;
  24. import java.net.URISyntaxException;
  25. import java.util.ArrayList;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. import org.apache.commons.io.IOUtils;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. import org.apache.fop.afp.AFPResourceLevel.ResourceType;
  33. import org.apache.fop.afp.fonts.AFPFont;
  34. import org.apache.fop.afp.fonts.CharacterSet;
  35. import org.apache.fop.afp.modca.AbstractNamedAFPObject;
  36. import org.apache.fop.afp.modca.AbstractPageObject;
  37. import org.apache.fop.afp.modca.ActiveEnvironmentGroup;
  38. import org.apache.fop.afp.modca.IncludeObject;
  39. import org.apache.fop.afp.modca.IncludedResourceObject;
  40. import org.apache.fop.afp.modca.ObjectContainer;
  41. import org.apache.fop.afp.modca.PageSegment;
  42. import org.apache.fop.afp.modca.Registry;
  43. import org.apache.fop.afp.modca.ResourceGroup;
  44. import org.apache.fop.afp.modca.ResourceObject;
  45. import org.apache.fop.afp.modca.triplets.EncodingTriplet;
  46. import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet;
  47. import org.apache.fop.afp.util.AFPResourceAccessor;
  48. import org.apache.fop.afp.util.AFPResourceUtil;
  49. import org.apache.fop.apps.io.InternalResourceResolver;
  50. import org.apache.fop.fonts.FontType;
  51. import org.apache.fop.render.afp.AFPFontConfig;
  52. /**
  53. * Manages the creation and storage of document resources
  54. */
  55. public class AFPResourceManager {
  56. /** logging instance */
  57. private static Log log = LogFactory.getLog(AFPResourceManager.class);
  58. /** The AFP datastream (document tree) */
  59. private DataStream dataStream;
  60. /** Resource creation factory */
  61. private final Factory factory;
  62. private final AFPStreamer streamer;
  63. private final AFPDataObjectFactory dataObjectFactory;
  64. /** Maintain a reference count of instream objects for referencing purposes */
  65. private int instreamObjectCount;
  66. /** Mapping of resourceInfo to AbstractCachedObject */
  67. private final Map<AFPResourceInfo, List<AbstractCachedObject>> includeObjectCache =
  68. new HashMap<AFPResourceInfo, List<AbstractCachedObject>>();
  69. private AFPResourceLevelDefaults resourceLevelDefaults = new AFPResourceLevelDefaults();
  70. protected boolean includeCached = true;
  71. /**
  72. * Main constructor
  73. *
  74. * @param resourceResolver the associated {@link InternalResourceResolver} instance
  75. */
  76. public AFPResourceManager(InternalResourceResolver resourceResolver) {
  77. this.factory = new Factory();
  78. this.streamer = new AFPStreamer(factory, resourceResolver);
  79. this.dataObjectFactory = new AFPDataObjectFactory(factory);
  80. }
  81. /**
  82. * Sets the outputstream
  83. *
  84. * @param paintingState the AFP painting state
  85. * @param outputStream the outputstream
  86. * @return a new AFP DataStream
  87. * @throws IOException thrown if an I/O exception of some sort has occurred
  88. */
  89. public DataStream createDataStream(AFPPaintingState paintingState, OutputStream outputStream)
  90. throws IOException {
  91. this.dataStream = streamer.createDataStream(paintingState);
  92. streamer.setOutputStream(outputStream);
  93. return this.dataStream;
  94. }
  95. /**
  96. * Returns the AFP DataStream
  97. *
  98. * @return the AFP DataStream
  99. */
  100. public DataStream getDataStream() {
  101. return this.dataStream;
  102. }
  103. /**
  104. * Tells the streamer to write
  105. *
  106. * @throws IOException thrown if an I/O exception of some sort has occurred.
  107. */
  108. public void writeToStream() throws IOException {
  109. streamer.close();
  110. }
  111. /**
  112. * Sets the default resource group URI.
  113. *
  114. * @param uri the default resource group URI
  115. */
  116. public void setDefaultResourceGroupUri(URI uri) {
  117. streamer.setDefaultResourceGroupUri(uri);
  118. }
  119. /**
  120. * Tries to create an include of a data object that has been previously added to the
  121. * AFP data stream. If no such object was available, the method returns false which serves
  122. * as a signal that the object has to be created.
  123. * @param dataObjectInfo the data object info
  124. * @return true if the inclusion succeeded, false if the object was not available
  125. * @throws IOException thrown if an I/O exception of some sort has occurred.
  126. */
  127. public boolean tryIncludeObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
  128. AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
  129. updateResourceInfoUri(resourceInfo);
  130. return includeCachedObject(resourceInfo, dataObjectInfo.getObjectAreaInfo());
  131. }
  132. /**
  133. * Creates a new data object in the AFP datastream
  134. *
  135. * @param dataObjectInfo the data object info
  136. *
  137. * @throws IOException thrown if an I/O exception of some sort has occurred.
  138. */
  139. public void createObject(AFPDataObjectInfo dataObjectInfo) throws IOException {
  140. if (tryIncludeObject(dataObjectInfo)) {
  141. //Object has already been produced and is available by inclusion, so return early.
  142. return;
  143. }
  144. AbstractNamedAFPObject namedObj = null;
  145. AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
  146. boolean useInclude = true;
  147. Registry.ObjectType objectType = null;
  148. // new resource so create
  149. if (dataObjectInfo instanceof AFPImageObjectInfo) {
  150. AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
  151. namedObj = dataObjectFactory.createImage(imageObjectInfo);
  152. } else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
  153. AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)dataObjectInfo;
  154. namedObj = dataObjectFactory.createGraphic(graphicsObjectInfo);
  155. } else {
  156. // natively embedded data object
  157. namedObj = dataObjectFactory.createObjectContainer(dataObjectInfo);
  158. objectType = dataObjectInfo.getObjectType();
  159. useInclude = objectType != null && objectType.isIncludable();
  160. }
  161. AFPResourceLevel resourceLevel = resourceInfo.getLevel();
  162. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  163. useInclude &= resourceGroup != null;
  164. if (useInclude) {
  165. final boolean usePageSegment = dataObjectInfo.isCreatePageSegment();
  166. // if it is to reside within a resource group at print-file or external level
  167. if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) {
  168. if (usePageSegment) {
  169. String pageSegmentName = "S10" + namedObj.getName().substring(3);
  170. namedObj.setName(pageSegmentName);
  171. PageSegment seg = new PageSegment(pageSegmentName);
  172. seg.addObject(namedObj);
  173. namedObj = seg;
  174. }
  175. // wrap newly created data object in a resource object
  176. namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType);
  177. }
  178. // add data object into its resource group destination
  179. resourceGroup.addObject(namedObj);
  180. includeObject(namedObj, dataObjectInfo);
  181. } else {
  182. // not to be included so inline data object directly into the current page
  183. dataStream.getCurrentPage().addObject(namedObj);
  184. }
  185. }
  186. private abstract class AbstractCachedObject {
  187. protected String objectName;
  188. protected AFPDataObjectInfo dataObjectInfo;
  189. public AbstractCachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
  190. this.objectName = objectName;
  191. this.dataObjectInfo = dataObjectInfo;
  192. }
  193. protected abstract void includeObject();
  194. }
  195. private class CachedPageSegment extends AbstractCachedObject {
  196. public CachedPageSegment(String objectName, AFPDataObjectInfo dataObjectInfo) {
  197. super(objectName, dataObjectInfo);
  198. }
  199. protected void includeObject() {
  200. includePageSegment(dataObjectInfo, objectName);
  201. }
  202. }
  203. private class CachedObject extends AbstractCachedObject {
  204. public CachedObject(String objectName, AFPDataObjectInfo dataObjectInfo) {
  205. super(objectName, dataObjectInfo);
  206. }
  207. protected void includeObject() {
  208. AFPResourceManager.this.includeObject(dataObjectInfo, objectName);
  209. }
  210. }
  211. private void includeObject(AbstractNamedAFPObject namedObj, AFPDataObjectInfo dataObjectInfo) {
  212. // create the include object
  213. String objectName = namedObj.getName();
  214. AbstractCachedObject cachedObject;
  215. if (dataObjectInfo.isCreatePageSegment()) {
  216. cachedObject = new CachedPageSegment(objectName, dataObjectInfo);
  217. } else {
  218. cachedObject = new CachedObject(objectName, dataObjectInfo);
  219. }
  220. cachedObject.includeObject();
  221. addToCache(dataObjectInfo.getResourceInfo(), cachedObject);
  222. //The data field of dataObjectInfo is not further required
  223. // therefore we are safe to null the reference, saving memory
  224. dataObjectInfo.setData(null);
  225. }
  226. private void addToCache(AFPResourceInfo resourceInfo, AbstractCachedObject cachedObject) {
  227. List<AbstractCachedObject> objs = includeObjectCache.get(resourceInfo);
  228. if (objs == null) {
  229. objs = new ArrayList<AbstractCachedObject>();
  230. includeObjectCache.put(resourceInfo, objs);
  231. }
  232. objs.add(cachedObject);
  233. }
  234. /**
  235. * Returns {@code true} if the passed {@link AFPResourceInfo} instance is already cached.
  236. *
  237. * @param resourceInfo the resource info to check
  238. * @return {@code true} if the object is cached
  239. */
  240. public boolean isObjectCached(AFPResourceInfo resourceInfo) {
  241. return includeObjectCache.containsKey(resourceInfo);
  242. }
  243. /**
  244. *
  245. * @param resourceInfo the resource info to check
  246. * @param areaInfo the area info to check
  247. * @return {@code true} if ...
  248. */
  249. public boolean includeCachedObject(AFPResourceInfo resourceInfo, AFPObjectAreaInfo areaInfo) {
  250. List<AbstractCachedObject> cachedObjectList = includeObjectCache.get(resourceInfo);
  251. if (cachedObjectList != null && includeCached) {
  252. for (AbstractCachedObject cachedObject : cachedObjectList) {
  253. if (areaInfo != null && cachedObjectList.size() == 1) {
  254. cachedObject.dataObjectInfo.setObjectAreaInfo(areaInfo);
  255. }
  256. cachedObject.includeObject();
  257. }
  258. return true;
  259. } else {
  260. return false;
  261. }
  262. }
  263. private void updateResourceInfoUri(AFPResourceInfo resourceInfo) {
  264. String uri = resourceInfo.getUri();
  265. if (uri == null) {
  266. uri = "/";
  267. }
  268. // if this is an instream data object adjust the uri to ensure that its unique
  269. if (uri.endsWith("/")) {
  270. uri += "#" + (++instreamObjectCount);
  271. resourceInfo.setUri(uri);
  272. }
  273. }
  274. private void includeObject(AFPDataObjectInfo dataObjectInfo,
  275. String objectName) {
  276. IncludeObject includeObject = dataObjectFactory.createInclude(objectName, dataObjectInfo);
  277. dataStream.getCurrentPage().addObject(includeObject);
  278. }
  279. /**
  280. * Handles font embedding. If a font is embeddable and has not already been embedded it will be.
  281. * @param afpFont the AFP font to be checked for embedding
  282. * @param charSet the associated character set
  283. * @throws IOException if there's a problem while embedding the external resources
  284. */
  285. public void embedFont(AFPFont afpFont, CharacterSet charSet)
  286. throws IOException {
  287. if (afpFont.isEmbeddable()) {
  288. //Embed fonts (char sets and code pages)
  289. if (charSet.getResourceAccessor() != null) {
  290. AFPResourceAccessor accessor = charSet.getResourceAccessor();
  291. if (afpFont.getFontType() == FontType.TRUETYPE) {
  292. createIncludedResource(afpFont.getFontName(),
  293. ((AFPFontConfig.AFPTrueTypeFont) afpFont).getUri(), accessor,
  294. ResourceObject.TYPE_OBJECT_CONTAINER, true,
  295. ((AFPFontConfig.AFPTrueTypeFont) afpFont).getTTC());
  296. } else {
  297. createIncludedResource(
  298. charSet.getName(), accessor,
  299. ResourceObject.TYPE_FONT_CHARACTER_SET);
  300. createIncludedResource(
  301. charSet.getCodePage(), accessor,
  302. ResourceObject.TYPE_CODE_PAGE);
  303. }
  304. }
  305. }
  306. }
  307. private void includePageSegment(AFPDataObjectInfo dataObjectInfo,
  308. String pageSegmentName) {
  309. int x = dataObjectInfo.getObjectAreaInfo().getX();
  310. int y = dataObjectInfo.getObjectAreaInfo().getY();
  311. AbstractPageObject currentPage = dataStream.getCurrentPage();
  312. boolean createHardPageSegments = true;
  313. currentPage.createIncludePageSegment(pageSegmentName, x, y, createHardPageSegments);
  314. }
  315. /**
  316. * Creates an included resource object by loading the contained object from a file.
  317. * @param resourceName the name of the resource
  318. * @param accessor resource accessor to access the resource with
  319. * @param resourceObjectType the resource object type ({@link ResourceObject}.*)
  320. * @throws IOException if an I/O error occurs while loading the resource
  321. */
  322. public void createIncludedResource(String resourceName, AFPResourceAccessor accessor,
  323. byte resourceObjectType) throws IOException {
  324. URI uri;
  325. try {
  326. uri = new URI(resourceName.trim());
  327. } catch (URISyntaxException e) {
  328. throw new IOException("Could not create URI from resource name: " + resourceName
  329. + " (" + e.getMessage() + ")");
  330. }
  331. createIncludedResource(resourceName, uri, accessor, resourceObjectType, false, null);
  332. }
  333. /**
  334. * Creates an included resource object by loading the contained object from a file.
  335. * @param resourceName the name of the resource
  336. * @param uri the URI for the resource
  337. * @param accessor resource accessor to access the resource with
  338. * @param resourceObjectType the resource object type ({@link ResourceObject}.*)
  339. * @throws IOException if an I/O error occurs while loading the resource
  340. */
  341. public void createIncludedResource(String resourceName, URI uri, AFPResourceAccessor accessor,
  342. byte resourceObjectType, boolean truetype, String ttc) throws IOException {
  343. AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
  344. AFPResourceInfo resourceInfo = new AFPResourceInfo();
  345. resourceInfo.setLevel(resourceLevel);
  346. resourceInfo.setName(resourceName);
  347. resourceInfo.setUri(uri.toASCIIString());
  348. List<AbstractCachedObject> cachedObject = includeObjectCache.get(resourceInfo);
  349. if (cachedObject == null) {
  350. if (log.isDebugEnabled()) {
  351. log.debug("Adding included resource: " + resourceName);
  352. }
  353. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  354. if (truetype) {
  355. ResourceObject res = factory.createResource();
  356. res.setType(ResourceObject.TYPE_OBJECT_CONTAINER);
  357. ActiveEnvironmentGroup.setupTruetypeMDR(res, false);
  358. ObjectContainer oc = factory.createObjectContainer();
  359. InputStream is = accessor.createInputStream(uri);
  360. if (ttc != null) {
  361. oc.setData(extractTTC(ttc, is));
  362. } else {
  363. oc.setData(IOUtils.toByteArray(is));
  364. }
  365. ActiveEnvironmentGroup.setupTruetypeMDR(oc, true);
  366. res.addTriplet(new EncodingTriplet(1200));
  367. res.setFullyQualifiedName(FullyQualifiedNameTriplet.TYPE_REPLACE_FIRST_GID_NAME,
  368. FullyQualifiedNameTriplet.FORMAT_CHARSTR, resourceName, true);
  369. res.setDataObject(oc);
  370. resourceGroup.addObject(res);
  371. } else {
  372. ResourceObject resourceObject = factory.createResource(resourceName);
  373. IncludedResourceObject resourceContent = new IncludedResourceObject(
  374. resourceName, accessor, uri);
  375. resourceObject.setDataObject(resourceContent);
  376. resourceObject.setType(resourceObjectType);
  377. resourceGroup.addObject(resourceObject);
  378. }
  379. //TODO what is the data object?
  380. CachedObject newcachedObject = new CachedObject(resourceName, null);
  381. // record mapping of resource info to data object resource name
  382. addToCache(resourceInfo, newcachedObject);
  383. } else {
  384. //skip, already created
  385. }
  386. }
  387. private byte[] extractTTC(String ttc, InputStream is) throws IOException {
  388. // TrueTypeCollection trueTypeCollection = new TrueTypeCollection(is);
  389. // for (TrueTypeFont ttf : trueTypeCollection.getFonts()) {
  390. // String name = ttf.getNaming().getFontFamily();
  391. // if (name.equals(ttc)) {
  392. // ByteArrayOutputStream bos = new ByteArrayOutputStream();
  393. // TTFSubsetter s = new TTFSubsetter(ttf, null);
  394. // for (int i = 0; i < 256 * 256; i++) {
  395. // s.addCharCode(i);
  396. // }
  397. // s.writeToStream(bos);
  398. // return bos.toByteArray();
  399. // }
  400. // }
  401. throw new IOException(ttc + " not supported");
  402. }
  403. /**
  404. * Creates an included resource extracting the named resource from an external source.
  405. * @param resourceName the name of the resource
  406. * @param uri the URI for the resource
  407. * @param accessor resource accessor to access the resource with
  408. * @throws IOException if an I/O error occurs while loading the resource
  409. */
  410. public void createIncludedResourceFromExternal(final String resourceName,
  411. final URI uri, final AFPResourceAccessor accessor) throws IOException {
  412. AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
  413. AFPResourceInfo resourceInfo = new AFPResourceInfo();
  414. resourceInfo.setLevel(resourceLevel);
  415. resourceInfo.setName(resourceName);
  416. resourceInfo.setUri(uri.toASCIIString());
  417. List<AbstractCachedObject> resource = includeObjectCache.get(resourceInfo);
  418. if (resource == null) {
  419. ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
  420. //resourceObject delegates write commands to copyNamedResource()
  421. //The included resource may already be wrapped in a resource object
  422. AbstractNamedAFPObject resourceObject = new AbstractNamedAFPObject(null) {
  423. @Override
  424. protected void writeContent(OutputStream os) throws IOException {
  425. InputStream inputStream = null;
  426. try {
  427. inputStream = accessor.createInputStream(uri);
  428. BufferedInputStream bin = new BufferedInputStream(inputStream);
  429. AFPResourceUtil.copyNamedResource(resourceName, bin, os);
  430. } finally {
  431. IOUtils.closeQuietly(inputStream);
  432. }
  433. }
  434. //bypass super.writeStart
  435. @Override
  436. protected void writeStart(OutputStream os) throws IOException { }
  437. //bypass super.writeEnd
  438. @Override
  439. protected void writeEnd(OutputStream os) throws IOException { }
  440. };
  441. resourceGroup.addObject(resourceObject);
  442. CachedObject newresource = new CachedObject(resourceName, null);
  443. addToCache(resourceInfo, newresource);
  444. }
  445. }
  446. /**
  447. * Sets resource level defaults. The existing defaults over merged with the ones passed in
  448. * as parameter.
  449. * @param defaults the new defaults
  450. */
  451. public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) {
  452. this.resourceLevelDefaults.mergeFrom(defaults);
  453. }
  454. /**
  455. * Returns the resource level defaults in use with this resource manager.
  456. * @return the resource level defaults
  457. */
  458. public AFPResourceLevelDefaults getResourceLevelDefaults() {
  459. return this.resourceLevelDefaults;
  460. }
  461. }