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

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