--- /dev/null
+/*
+ * Copyright (c) 2009, Takeyuki Nagao
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+package jp.sourceforge.dvibrowser.dvicore.ctx;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.AccessControlException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.logging.Logger;
+
+import jp.sourceforge.dvibrowser.dvicore.DviException;
+import jp.sourceforge.dvibrowser.dvicore.DviFontSpec;
+import jp.sourceforge.dvibrowser.dvicore.DviPaperSize;
+import jp.sourceforge.dvibrowser.dvicore.DviResolution;
+import jp.sourceforge.dvibrowser.dvicore.MetafontMode;
+import jp.sourceforge.dvibrowser.dvicore.api.CharacterCodeMapper;
+import jp.sourceforge.dvibrowser.dvicore.api.DevicePainter;
+import jp.sourceforge.dvibrowser.dvicore.api.DviContext;
+import jp.sourceforge.dvibrowser.dvicore.api.DviData;
+import jp.sourceforge.dvibrowser.dvicore.api.DviDocument;
+import jp.sourceforge.dvibrowser.dvicore.api.DviExecutor;
+import jp.sourceforge.dvibrowser.dvicore.api.DviExecutorHandler;
+import jp.sourceforge.dvibrowser.dvicore.api.DviFont;
+import jp.sourceforge.dvibrowser.dvicore.api.FullMetrics;
+import jp.sourceforge.dvibrowser.dvicore.api.Glyph;
+import jp.sourceforge.dvibrowser.dvicore.api.SimpleMetrics;
+import jp.sourceforge.dvibrowser.dvicore.doc.DirectFileDviDocument;
+import jp.sourceforge.dvibrowser.dvicore.doc.StreamDviDocument;
+import jp.sourceforge.dvibrowser.dvicore.doc.URLDviDocument;
+import jp.sourceforge.dvibrowser.dvicore.font.DviFontResolver;
+import jp.sourceforge.dvibrowser.dvicore.font.FullMetricsResolver;
+import jp.sourceforge.dvibrowser.dvicore.font.LogicalFont;
+import jp.sourceforge.dvibrowser.dvicore.gs.GhostscriptUtils;
+import jp.sourceforge.dvibrowser.dvicore.render.BasicExecutor;
+import jp.sourceforge.dvibrowser.dvicore.render.DefaultDevicePainter;
+import jp.sourceforge.dvibrowser.dvicore.util.DviCache;
+import jp.sourceforge.dvibrowser.dvicore.util.DviUtils;
+import jp.sourceforge.dvibrowser.dvicore.util.concurrent.Computation;
+import jp.sourceforge.dvibrowser.dvicore.util.progress.ManagedProgressItem;
+import jp.sourceforge.dvibrowser.dvicore.util.progress.ProgressRecorder;
+
+
+public class DefaultDviContext
+implements DviContext
+{
+ private static final Logger LOGGER = Logger.getLogger(DefaultDviContext.class.getName());
+
+ private static final String DVICORE_INTERNAL_FILENAME_PREFIX = "dvicore-";
+
+ private final Map<String, Glyph> glyphCache = new DviCache<String, Glyph>(16384);
+ private final CharacterCodeMapper characterCodeMapper
+ = new SimpleJisToUnicodeMapper();
+
+ private boolean recordResources = false;
+ private final Set<URL> resources = new TreeSet<URL>(new Comparator<URL>() {
+ public int compare(URL arg0, URL arg1) {
+ return arg0.toString().compareTo(arg1.toString());
+ }
+ });
+
+ private final ProgressRecorder recorder = new ProgressRecorder(this) {
+ @Override
+ protected boolean removeEldestElement(ManagedProgressItem item)
+ {
+ List<ManagedProgressItem> list = getProgressItems();
+ if (list.size() > 10) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private AsyncComputers asyncComputers;
+
+
+ public DefaultDviContext()
+ throws DviException
+ {
+ this(null);
+ }
+
+ public DefaultDviContext(Properties prop)
+ throws DviException
+ {
+ if (prop == null)
+ prop = getDefaultProperties();
+ this.prop = prop;
+ initializeComputers();
+ initializeFontMapper();
+ populatePaperSizes();
+ initializeDirs();
+ }
+
+ protected void initializeComputers() {
+ this.setAsyncComputers(new AsyncComputers(prop));
+ }
+
+ protected void initializeDirs()
+ throws DviException
+ {
+ File cacheDir = getCacheDirectory();
+ if (cacheDir != null && !cacheDir.exists()) {
+ if (!cacheDir.mkdirs()) {
+ throw new DviException("Failed to create cache directory: " + cacheDir);
+ }
+ }
+ File tmpDir = getTemporaryDirectory();
+ if (tmpDir != null && !tmpDir.exists()) {
+ if (!tmpDir.mkdirs()) {
+ throw new DviException("Failed to create temporary directory: " + tmpDir);
+ }
+ }
+ }
+
+ protected Properties getDefaultProperties()
+ throws DviException
+ {
+ try {
+ URL url = DefaultDviContext.class.getResource("default-context.properties");
+ Properties prop = new Properties();
+ if (null != url) {
+ prop.load(url.openStream());
+ } else {
+ LOGGER.warning("Failed to load default-context.properties");
+ }
+ return prop;
+ } catch (Exception e) {
+ throw new DviException(e);
+ }
+ }
+
+ private final Properties prop;
+
+ public Properties getProperties()
+ {
+ return prop;
+ }
+
+ public void execute(DviData data, DviExecutorHandler handler)
+ throws DviException
+ {
+ newDviExecutor().execute(data, handler);
+ }
+
+// TODO: delete
+// private static final CachedComputer<String, Collection<DviFont>> dviFontComputer
+// = new CachedComputer<String, Collection<DviFont>>
+// (new ThreadedComputer<String, Collection<DviFont>>(1)) {
+// @Override
+// protected boolean removeEldestEntry(Map.Entry<String, CacheEntry<String, Collection<DviFont>>> entry)
+// {
+// boolean remove = getCache().size() > 64;
+// return remove;
+// }
+// };
+
+ public DviFont findDviFont(LogicalFont logicalFont) throws DviException
+ {
+ Computation<String, Collection<DviFont>> computation
+ = new DviFontResolver(this, logicalFont);
+ try {
+ Collection<DviFont> fonts = getAsyncComputers().getDviFontComputer()
+ .compute(computation).get();
+ for (DviFont font : fonts) {
+ return font;
+ }
+ return null;
+ } catch (InterruptedException e) {
+ throw new DviException(e);
+ } catch (ExecutionException e) {
+ throw new DviException(e);
+ }
+ }
+
+ public SimpleMetrics findDviSimpleMetrics(DviFontSpec fs) throws DviException
+ {
+ return findDviFullMetrics(fs);
+ }
+
+// private static final CachedComputer<String, Collection<FullMetrics>> fullMetricsComputer
+// = new CachedComputer<String, Collection<FullMetrics>>
+// (new ThreadedComputer<String, Collection<FullMetrics>>(1));
+
+ public FullMetrics findDviFullMetrics(DviFontSpec fs) throws DviException
+ {
+ Computation<String, Collection<FullMetrics>> computation = new FullMetricsResolver(
+ this, fs);
+ try {
+ Collection<FullMetrics> fonts = getAsyncComputers().getFullMetricsComputer()
+ .compute(computation).get();
+ if (fonts != null) {
+ for (FullMetrics font : fonts) {
+ return font;
+ }
+ }
+ return null;
+ } catch (InterruptedException e) {
+ throw new DviException(e);
+ } catch (ExecutionException e) {
+ throw new DviException(e);
+ }
+ }
+
+ public CharacterCodeMapper getCharacterCodeMapper(LogicalFont logicalFont) throws DviException
+ {
+ return characterCodeMapper;
+ }
+
+ public DviContext getDviContext() { return this; }
+
+ public Map<String, Glyph> getGlyphCache() throws DviException
+ {
+ return Collections.synchronizedMap(glyphCache);
+ }
+
+ protected void initializeFontMapper()
+ {
+ }
+
+ public DevicePainter newDevicePainter() throws DviException
+ {
+ return new DefaultDevicePainter(this);
+ }
+
+ public DviExecutor newDviExecutor() throws DviException
+ {
+ return new BasicExecutor(this);
+ }
+
+ public DviDocument openDviDocument(File file) throws DviException
+ {
+ return new DirectFileDviDocument(this, file);
+ }
+
+ public DviDocument openDviDocument(InputStream is) throws DviException
+ {
+ return new StreamDviDocument(this, is);
+ }
+
+ // TODO: implement async resource loading
+ public DviDocument openDviDocument(URL url) throws DviException
+ {
+ return new URLDviDocument(this, url);
+ }
+
+ public ProgressRecorder getProgressRecorder()
+ {
+ return recorder;
+ }
+
+ private static final Map<String, DviPaperSize> paperSizes = new TreeMap<String, DviPaperSize>();
+
+ protected void populatePaperSizes()
+ {
+ // ISO 216 sizes
+ // TODO: implement B and C serieses. A4 Japanese, too.
+ // TOOD: outsource the configuration.
+ addPaperSize(new DviPaperSize(841.0, 1189.0, "A0"));
+ addPaperSize(new DviPaperSize(594.0, 841.0, "A1"));
+ addPaperSize(new DviPaperSize(420.0, 594.0, "A2"));
+ addPaperSize(new DviPaperSize(297.0, 420.0, "A3"));
+ addPaperSize(new DviPaperSize(210.0, 297.0, "A4"));
+ addPaperSize(new DviPaperSize(148.0, 210.0, "A5"));
+ addPaperSize(new DviPaperSize(105.0, 148.0, "A6"));
+ addPaperSize(new DviPaperSize(74.0, 105.0, "A7"));
+ addPaperSize(new DviPaperSize(52.0, 74.0, "A8"));
+ addPaperSize(new DviPaperSize(37.0, 52.0, "A9"));
+ addPaperSize(new DviPaperSize(26.0, 37.0, "A10"));
+ }
+
+ protected void addPaperSize(DviPaperSize dviPaperSize)
+ {
+ if (dviPaperSize == null) return;
+ paperSizes.put(dviPaperSize.description().toLowerCase(), dviPaperSize);
+ }
+
+ public DviPaperSize findPaperSizeByName(String name) throws DviException
+ {
+ if (name == null) return null;
+ return paperSizes.get(name.toLowerCase());
+ }
+
+ public DviPaperSize getDefaultPaperSize() throws DviException
+ {
+ return findPaperSizeByName("A4");
+ }
+
+ public DviPaperSize [] listPaperSizes() throws DviException
+ {
+ return paperSizes.values().toArray(new DviPaperSize[0]);
+ }
+
+ private static final DviResolution defaultResolution = new DviResolution(2400, 20);
+
+ public DviResolution getDefaultResolution() throws DviException
+ {
+ return defaultResolution;
+ }
+
+// TODO: delete
+// private static final CachedComputer<String, Collection<URL>> dviResourceComputer
+// = new CachedComputer<String, Collection<URL>>
+// (new ThreadedComputer<String, Collection<URL>>(1));
+
+ public URL getDviResource(String filename) throws DviException
+ {
+ if (filename.startsWith(DVICORE_INTERNAL_FILENAME_PREFIX)) {
+ // The resource name starting with "dvicore-" are only for internal use.
+ // So there is no local file corresponding to such a filename.
+ // We answer null.
+ return null;
+ }
+
+ Computation<String, Collection<URL>> c
+ = new FileLocationResolver(this, "/dvi/builtin", filename);
+ final Future<Collection<URL>> future
+ = getAsyncComputers().getDviResourceComputer().compute(c);
+ try {
+ final Collection<URL> list = future.get();
+ for (URL url : list) {
+ if (recordResources) {
+ LOGGER.info("resolved resource: filename=" + filename + " url=" + url);
+ resources.add(url);
+ } else {
+ LOGGER.finest("resolved resource: filename=" + filename + " url=" + url);
+ }
+ return url;
+ }
+ return null;
+ } catch (InterruptedException e) {
+ LOGGER.warning(e.toString());
+ throw new DviException(e);
+ } catch (ExecutionException e) {
+ LOGGER.warning(e.toString());
+ throw new DviException(e);
+ }
+ }
+
+ public LogicalFont mapLogicalFont(LogicalFont logicalFont)
+ throws DviException
+ {
+ LogicalFont mapped = logicalFont;
+ if (logicalFont != null) {
+ String prefix = getClass().getName();
+ String faceKey = prefix + ".fontMap." + logicalFont.fontSpec().name();
+ LOGGER.info("Face key: " + faceKey);
+ String face = getProperties().getProperty(faceKey);
+ LOGGER.info("Properties: " + getProperties());
+ if (face != null) {
+ mapped = logicalFont.renameTo(face);
+ LOGGER.info("Rename logical font: " + logicalFont + " => " + mapped);
+ }
+ }
+ LOGGER.info("Map logical font: " + logicalFont + " => " + mapped);
+ return mapped;
+ }
+
+ // N.B. System.getProperty() throws an exception when invoked
+ // from inside an applet.
+ private static String getSystemProperty(String key) {
+ try {
+ return System.getProperty(key);
+ } catch (AccessControlException ex) {
+ return null;
+ }
+ }
+
+ private static final String userDir = getSystemProperty("user.dir");
+ private static final String ioTmpDir = getSystemProperty("java.io.tmpdir");
+
+ // TODO: externalize the string "dvibrowser.jar".
+ public File getApplicationHomeDir()
+ throws DviException
+ {
+ if (userDir != null) {
+ File home = new File(userDir);
+ File markFile = new File(home, "dvibrowser.jar");
+ if (markFile.exists()) {
+ // It seems that we are using DviContext from within dvibrowser.
+ return home;
+ }
+ }
+ return null;
+ }
+
+ public File getCacheDirectory() throws DviException
+ {
+ File appHome = getApplicationHomeDir();
+ if (appHome == null) {
+ File tmpDir = getTemporaryDirectory();
+ if (tmpDir != null) {
+ return new File(tmpDir, "cache");
+ }
+ } else {
+ if (userDir != null) {
+ File var = new File(userDir, "var");
+ return new File(var, "cache");
+ }
+ }
+ return null;
+ }
+
+ public File getTemporaryDirectory() throws DviException
+ {
+ File appHome = getApplicationHomeDir();
+ if (appHome == null) {
+ if (ioTmpDir != null) {
+ return new File(ioTmpDir, "dvicontext");
+ }
+ } else {
+ return new File(appHome, "tmp");
+ }
+ return null;
+ }
+
+ private volatile String [] ghostscriptExecutables = null;
+
+ public String getExecutableName(String name) throws DviException
+ {
+ if ("gs".equals(name)) {
+ if (ghostscriptExecutables == null) {
+ // The following code might be called in pararell when we invoke
+ // this method from within multiple threads.
+ // It might be better to use some atomic classes.
+ ghostscriptExecutables = GhostscriptUtils.listGhostscriptExecutables();
+ if (ghostscriptExecutables.length == 0) {
+ LOGGER.warning("You don't seem to have a Ghostscript installed. dvibrowser needs it to render PS, EPS, and PDF.");
+ } else {
+ LOGGER.info("Ghostscript executables found: " + DviUtils.join(" ", ghostscriptExecutables));
+ }
+ }
+ if (ghostscriptExecutables.length > 0) {
+ return ghostscriptExecutables[0];
+ }
+ }
+
+ return name;
+ }
+
+ private final DviToolkit dviToolkit = new DviToolkit(this);
+ public DviToolkit getDviToolkit()
+ {
+ return dviToolkit;
+ }
+
+ private final MetafontMode libraryDefaultMetafontMode = MetafontMode.FALLBACK;
+ public MetafontMode getDefaultMetafontMode() throws DviException {
+ return libraryDefaultMetafontMode;
+ }
+
+ public void setRecordResources(boolean recordResources) {
+ this.recordResources = recordResources;
+ }
+
+ public boolean wantRecordResources() {
+ return recordResources;
+ }
+
+ public Set<URL> getRecordedResources() {
+ return resources;
+ }
+
+ protected void setAsyncComputers(AsyncComputers asyncComputers) {
+ this.asyncComputers = asyncComputers;
+ }
+
+ public AsyncComputers getAsyncComputers() {
+ return asyncComputers;
+ }
+}